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, thread_rng};
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 sum_tree::TreeMap;
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::{
215 code_context_menus::CompletionsMenuSource,
216 editor_settings::MultiCursorModifier,
217 hover_links::{find_url, find_url_from_range},
218 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
219};
220
221pub const FILE_HEADER_HEIGHT: u32 = 2;
222pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 Closed,
257}
258
259impl ReportEditorEvent {
260 pub fn event_type(&self) -> &'static str {
261 match self {
262 Self::Saved { .. } => "Editor Saved",
263 Self::EditorOpened => "Editor Opened",
264 Self::Closed => "Editor Closed",
265 }
266 }
267}
268
269struct InlineValueCache {
270 enabled: bool,
271 inlays: Vec<InlayId>,
272 refresh_task: Task<Option<()>>,
273}
274
275impl InlineValueCache {
276 fn new(enabled: bool) -> Self {
277 Self {
278 enabled,
279 inlays: Vec::new(),
280 refresh_task: Task::ready(None),
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
286pub enum InlayId {
287 EditPrediction(usize),
288 DebuggerValue(usize),
289 // LSP
290 Hint(usize),
291 Color(usize),
292}
293
294impl InlayId {
295 fn id(&self) -> usize {
296 match self {
297 Self::EditPrediction(id) => *id,
298 Self::DebuggerValue(id) => *id,
299 Self::Hint(id) => *id,
300 Self::Color(id) => *id,
301 }
302 }
303}
304
305pub enum ActiveDebugLine {}
306pub enum DebugStackFrameLine {}
307enum DocumentHighlightRead {}
308enum DocumentHighlightWrite {}
309enum InputComposition {}
310pub enum PendingInput {}
311enum SelectedTextHighlight {}
312
313pub enum ConflictsOuter {}
314pub enum ConflictsOurs {}
315pub enum ConflictsTheirs {}
316pub enum ConflictsOursMarker {}
317pub enum ConflictsTheirsMarker {}
318
319#[derive(Debug, Copy, Clone, PartialEq, Eq)]
320pub enum Navigated {
321 Yes,
322 No,
323}
324
325impl Navigated {
326 pub fn from_bool(yes: bool) -> Navigated {
327 if yes { Navigated::Yes } else { Navigated::No }
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq)]
332enum DisplayDiffHunk {
333 Folded {
334 display_row: DisplayRow,
335 },
336 Unfolded {
337 is_created_file: bool,
338 diff_base_byte_range: Range<usize>,
339 display_row_range: Range<DisplayRow>,
340 multi_buffer_range: Range<Anchor>,
341 status: DiffHunkStatus,
342 },
343}
344
345pub enum HideMouseCursorOrigin {
346 TypingAction,
347 MovementAction,
348}
349
350pub fn init_settings(cx: &mut App) {
351 EditorSettings::register(cx);
352}
353
354pub fn init(cx: &mut App) {
355 init_settings(cx);
356
357 cx.set_global(GlobalBlameRenderer(Arc::new(())));
358
359 workspace::register_project_item::<Editor>(cx);
360 workspace::FollowableViewRegistry::register::<Editor>(cx);
361 workspace::register_serializable_item::<Editor>(cx);
362
363 cx.observe_new(
364 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
365 workspace.register_action(Editor::new_file);
366 workspace.register_action(Editor::new_file_vertical);
367 workspace.register_action(Editor::new_file_horizontal);
368 workspace.register_action(Editor::cancel_language_server_work);
369 workspace.register_action(Editor::toggle_focus);
370 },
371 )
372 .detach();
373
374 cx.on_action(move |_: &workspace::NewFile, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388 cx.on_action(move |_: &workspace::NewWindow, cx| {
389 let app_state = workspace::AppState::global(cx);
390 if let Some(app_state) = app_state.upgrade() {
391 workspace::open_new(
392 Default::default(),
393 app_state,
394 cx,
395 |workspace, window, cx| {
396 cx.activate(true);
397 Editor::new_file(workspace, &Default::default(), window, cx)
398 },
399 )
400 .detach();
401 }
402 });
403}
404
405pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
406 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
407}
408
409pub trait DiagnosticRenderer {
410 fn render_group(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 buffer_id: BufferId,
414 snapshot: EditorSnapshot,
415 editor: WeakEntity<Editor>,
416 cx: &mut App,
417 ) -> Vec<BlockProperties<Anchor>>;
418
419 fn render_hover(
420 &self,
421 diagnostic_group: Vec<DiagnosticEntry<Point>>,
422 range: Range<Point>,
423 buffer_id: BufferId,
424 cx: &mut App,
425 ) -> Option<Entity<markdown::Markdown>>;
426
427 fn open_link(
428 &self,
429 editor: &mut Editor,
430 link: SharedString,
431 window: &mut Window,
432 cx: &mut Context<Editor>,
433 );
434}
435
436pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
437
438impl GlobalDiagnosticRenderer {
439 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
440 cx.try_global::<Self>().map(|g| g.0.clone())
441 }
442}
443
444impl gpui::Global for GlobalDiagnosticRenderer {}
445pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
446 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
447}
448
449pub struct SearchWithinRange;
450
451trait InvalidationRegion {
452 fn ranges(&self) -> &[Range<Anchor>];
453}
454
455#[derive(Clone, Debug, PartialEq)]
456pub enum SelectPhase {
457 Begin {
458 position: DisplayPoint,
459 add: bool,
460 click_count: usize,
461 },
462 BeginColumnar {
463 position: DisplayPoint,
464 reset: bool,
465 mode: ColumnarMode,
466 goal_column: u32,
467 },
468 Extend {
469 position: DisplayPoint,
470 click_count: usize,
471 },
472 Update {
473 position: DisplayPoint,
474 goal_column: u32,
475 scroll_delta: gpui::Point<f32>,
476 },
477 End,
478}
479
480#[derive(Clone, Debug, PartialEq)]
481pub enum ColumnarMode {
482 FromMouse,
483 FromSelection,
484}
485
486#[derive(Clone, Debug)]
487pub enum SelectMode {
488 Character,
489 Word(Range<Anchor>),
490 Line(Range<Anchor>),
491 All,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// When set to `true`, the editor's height will be determined by its content.
507 sized_by_content: bool,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sized_by_content: false,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
596 let show_background = language_settings::language_settings(None, None, cx)
597 .inlay_hints
598 .show_background;
599
600 HighlightStyle {
601 color: Some(cx.theme().status().hint),
602 background_color: show_background.then(|| cx.theme().status().hint_background),
603 ..HighlightStyle::default()
604 }
605}
606
607pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
608 EditPredictionStyles {
609 insertion: HighlightStyle {
610 color: Some(cx.theme().status().predictive),
611 ..HighlightStyle::default()
612 },
613 whitespace: HighlightStyle {
614 background_color: Some(cx.theme().status().created_background),
615 ..HighlightStyle::default()
616 },
617 }
618}
619
620type CompletionId = usize;
621
622pub(crate) enum EditDisplayMode {
623 TabAccept,
624 DiffPopover,
625 Inline,
626}
627
628enum EditPrediction {
629 Edit {
630 edits: Vec<(Range<Anchor>, String)>,
631 edit_preview: Option<EditPreview>,
632 display_mode: EditDisplayMode,
633 snapshot: BufferSnapshot,
634 },
635 Move {
636 target: Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Range<Anchor>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
730type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
731
732#[derive(Default)]
733struct ScrollbarMarkerState {
734 scrollbar_size: Size<Pixels>,
735 dirty: bool,
736 markers: Arc<[PaintQuad]>,
737 pending_refresh: Option<Task<Result<()>>>,
738}
739
740impl ScrollbarMarkerState {
741 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
742 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
743 }
744}
745
746#[derive(Clone, Copy, PartialEq, Eq)]
747pub enum MinimapVisibility {
748 Disabled,
749 Enabled {
750 /// The configuration currently present in the users settings.
751 setting_configuration: bool,
752 /// Whether to override the currently set visibility from the users setting.
753 toggle_override: bool,
754 },
755}
756
757impl MinimapVisibility {
758 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
759 if mode.is_full() {
760 Self::Enabled {
761 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
762 toggle_override: false,
763 }
764 } else {
765 Self::Disabled
766 }
767 }
768
769 fn hidden(&self) -> Self {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 ..
774 } => Self::Enabled {
775 setting_configuration,
776 toggle_override: setting_configuration,
777 },
778 Self::Disabled => Self::Disabled,
779 }
780 }
781
782 fn disabled(&self) -> bool {
783 matches!(*self, Self::Disabled)
784 }
785
786 fn settings_visibility(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 ..
791 } => setting_configuration,
792 _ => false,
793 }
794 }
795
796 fn visible(&self) -> bool {
797 match *self {
798 Self::Enabled {
799 setting_configuration,
800 toggle_override,
801 } => setting_configuration ^ toggle_override,
802 _ => false,
803 }
804 }
805
806 fn toggle_visibility(&self) -> Self {
807 match *self {
808 Self::Enabled {
809 toggle_override,
810 setting_configuration,
811 } => Self::Enabled {
812 setting_configuration,
813 toggle_override: !toggle_override,
814 },
815 Self::Disabled => Self::Disabled,
816 }
817 }
818}
819
820#[derive(Clone, Debug)]
821struct RunnableTasks {
822 templates: Vec<(TaskSourceKind, TaskTemplate)>,
823 offset: multi_buffer::Anchor,
824 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
825 column: u32,
826 // Values of all named captures, including those starting with '_'
827 extra_variables: HashMap<String, String>,
828 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
829 context_range: Range<BufferOffset>,
830}
831
832impl RunnableTasks {
833 fn resolve<'a>(
834 &'a self,
835 cx: &'a task::TaskContext,
836 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
837 self.templates.iter().filter_map(|(kind, template)| {
838 template
839 .resolve_task(&kind.to_id_base(), cx)
840 .map(|task| (kind.clone(), task))
841 })
842 }
843}
844
845#[derive(Clone)]
846pub struct ResolvedTasks {
847 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
848 position: Anchor,
849}
850
851#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
852struct BufferOffset(usize);
853
854// Addons allow storing per-editor state in other crates (e.g. Vim)
855pub trait Addon: 'static {
856 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
857
858 fn render_buffer_header_controls(
859 &self,
860 _: &ExcerptInfo,
861 _: &Window,
862 _: &App,
863 ) -> Option<AnyElement> {
864 None
865 }
866
867 fn to_any(&self) -> &dyn std::any::Any;
868
869 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
870 None
871 }
872}
873
874struct ChangeLocation {
875 current: Option<Vec<Anchor>>,
876 original: Vec<Anchor>,
877}
878impl ChangeLocation {
879 fn locations(&self) -> &[Anchor] {
880 self.current.as_ref().unwrap_or(&self.original)
881 }
882}
883
884/// A set of caret positions, registered when the editor was edited.
885pub struct ChangeList {
886 changes: Vec<ChangeLocation>,
887 /// Currently "selected" change.
888 position: Option<usize>,
889}
890
891impl ChangeList {
892 pub fn new() -> Self {
893 Self {
894 changes: Vec::new(),
895 position: None,
896 }
897 }
898
899 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
900 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
901 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
902 if self.changes.is_empty() {
903 return None;
904 }
905
906 let prev = self.position.unwrap_or(self.changes.len());
907 let next = if direction == Direction::Prev {
908 prev.saturating_sub(count)
909 } else {
910 (prev + count).min(self.changes.len() - 1)
911 };
912 self.position = Some(next);
913 self.changes.get(next).map(|change| change.locations())
914 }
915
916 /// Adds a new change to the list, resetting the change list position.
917 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
918 self.position.take();
919 if let Some(last) = self.changes.last_mut()
920 && group
921 {
922 last.current = Some(new_positions)
923 } else {
924 self.changes.push(ChangeLocation {
925 original: new_positions,
926 current: None,
927 });
928 }
929 }
930
931 pub fn last(&self) -> Option<&[Anchor]> {
932 self.changes.last().map(|change| change.locations())
933 }
934
935 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
936 self.changes.last().map(|change| change.original.as_slice())
937 }
938
939 pub fn invert_last_group(&mut self) {
940 if let Some(last) = self.changes.last_mut()
941 && let Some(current) = last.current.as_mut()
942 {
943 mem::swap(&mut last.original, current);
944 }
945 }
946}
947
948#[derive(Clone)]
949struct InlineBlamePopoverState {
950 scroll_handle: ScrollHandle,
951 commit_message: Option<ParsedCommitMessage>,
952 markdown: Entity<Markdown>,
953}
954
955struct InlineBlamePopover {
956 position: gpui::Point<Pixels>,
957 hide_task: Option<Task<()>>,
958 popover_bounds: Option<Bounds<Pixels>>,
959 popover_state: InlineBlamePopoverState,
960 keyboard_grace: bool,
961}
962
963enum SelectionDragState {
964 /// State when no drag related activity is detected.
965 None,
966 /// State when the mouse is down on a selection that is about to be dragged.
967 ReadyToDrag {
968 selection: Selection<Anchor>,
969 click_position: gpui::Point<Pixels>,
970 mouse_down_time: Instant,
971 },
972 /// State when the mouse is dragging the selection in the editor.
973 Dragging {
974 selection: Selection<Anchor>,
975 drop_cursor: Selection<Anchor>,
976 hide_drop_cursor: bool,
977 },
978}
979
980enum ColumnarSelectionState {
981 FromMouse {
982 selection_tail: Anchor,
983 display_point: Option<DisplayPoint>,
984 },
985 FromSelection {
986 selection_tail: Anchor,
987 },
988}
989
990/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
991/// a breakpoint on them.
992#[derive(Clone, Copy, Debug, PartialEq, Eq)]
993struct PhantomBreakpointIndicator {
994 display_row: DisplayRow,
995 /// There's a small debounce between hovering over the line and showing the indicator.
996 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
997 is_active: bool,
998 collides_with_existing_breakpoint: bool,
999}
1000
1001/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1002///
1003/// See the [module level documentation](self) for more information.
1004pub struct Editor {
1005 focus_handle: FocusHandle,
1006 last_focused_descendant: Option<WeakFocusHandle>,
1007 /// The text buffer being edited
1008 buffer: Entity<MultiBuffer>,
1009 /// Map of how text in the buffer should be displayed.
1010 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1011 pub display_map: Entity<DisplayMap>,
1012 pub selections: SelectionsCollection,
1013 pub scroll_manager: ScrollManager,
1014 /// When inline assist editors are linked, they all render cursors because
1015 /// typing enters text into each of them, even the ones that aren't focused.
1016 pub(crate) show_cursor_when_unfocused: bool,
1017 columnar_selection_state: Option<ColumnarSelectionState>,
1018 add_selections_state: Option<AddSelectionsState>,
1019 select_next_state: Option<SelectNextState>,
1020 select_prev_state: Option<SelectNextState>,
1021 selection_history: SelectionHistory,
1022 defer_selection_effects: bool,
1023 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1024 autoclose_regions: Vec<AutocloseRegion>,
1025 snippet_stack: InvalidationStack<SnippetState>,
1026 select_syntax_node_history: SelectSyntaxNodeHistory,
1027 ime_transaction: Option<TransactionId>,
1028 pub diagnostics_max_severity: DiagnosticSeverity,
1029 active_diagnostics: ActiveDiagnostic,
1030 show_inline_diagnostics: bool,
1031 inline_diagnostics_update: Task<()>,
1032 inline_diagnostics_enabled: bool,
1033 diagnostics_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: TreeMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: TreeMap<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: true,
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: TreeMap::default(),
2116 gutter_highlights: TreeMap::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 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2169 gutter_hovered: false,
2170 pixel_position_of_newest_cursor: None,
2171 last_bounds: None,
2172 last_position_map: None,
2173 expect_bounds_change: None,
2174 gutter_dimensions: GutterDimensions::default(),
2175 style: None,
2176 show_cursor_names: false,
2177 hovered_cursors: HashMap::default(),
2178 next_editor_action_id: EditorActionId::default(),
2179 editor_actions: Rc::default(),
2180 edit_predictions_hidden_for_vim_mode: false,
2181 show_edit_predictions_override: None,
2182 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2183 edit_prediction_settings: EditPredictionSettings::Disabled,
2184 edit_prediction_indent_conflict: false,
2185 edit_prediction_requires_modifier_in_indent_conflict: true,
2186 custom_context_menu: None,
2187 show_git_blame_gutter: false,
2188 show_git_blame_inline: false,
2189 show_selection_menu: None,
2190 show_git_blame_inline_delay_task: None,
2191 git_blame_inline_enabled: full_mode
2192 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2193 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2194 serialize_dirty_buffers: !is_minimap
2195 && ProjectSettings::get_global(cx)
2196 .session
2197 .restore_unsaved_buffers,
2198 blame: None,
2199 blame_subscription: None,
2200 tasks: BTreeMap::default(),
2201
2202 breakpoint_store,
2203 gutter_breakpoint_indicator: (None, None),
2204 hovered_diff_hunk_row: None,
2205 _subscriptions: (!is_minimap)
2206 .then(|| {
2207 vec![
2208 cx.observe(&buffer, Self::on_buffer_changed),
2209 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2210 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2211 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2212 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2213 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2214 cx.observe_window_activation(window, |editor, window, cx| {
2215 let active = window.is_window_active();
2216 editor.blink_manager.update(cx, |blink_manager, cx| {
2217 if active {
2218 blink_manager.enable(cx);
2219 } else {
2220 blink_manager.disable(cx);
2221 }
2222 });
2223 if active {
2224 editor.show_mouse_cursor(cx);
2225 }
2226 }),
2227 ]
2228 })
2229 .unwrap_or_default(),
2230 tasks_update_task: None,
2231 pull_diagnostics_task: Task::ready(()),
2232 colors: None,
2233 next_color_inlay_id: 0,
2234 linked_edit_ranges: Default::default(),
2235 in_project_search: false,
2236 previous_search_ranges: None,
2237 breadcrumb_header: None,
2238 focused_block: None,
2239 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2240 addons: HashMap::default(),
2241 registered_buffers: HashMap::default(),
2242 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2243 selection_mark_mode: false,
2244 toggle_fold_multiple_buffers: Task::ready(()),
2245 serialize_selections: Task::ready(()),
2246 serialize_folds: Task::ready(()),
2247 text_style_refinement: None,
2248 load_diff_task: load_uncommitted_diff,
2249 temporary_diff_override: false,
2250 mouse_cursor_hidden: false,
2251 minimap: None,
2252 hide_mouse_mode: EditorSettings::get_global(cx)
2253 .hide_mouse
2254 .unwrap_or_default(),
2255 change_list: ChangeList::new(),
2256 mode,
2257 selection_drag_state: SelectionDragState::None,
2258 folding_newlines: Task::ready(()),
2259 };
2260
2261 if is_minimap {
2262 return editor;
2263 }
2264
2265 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2266 editor
2267 ._subscriptions
2268 .push(cx.observe(breakpoints, |_, _, cx| {
2269 cx.notify();
2270 }));
2271 }
2272 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2273 editor._subscriptions.extend(project_subscriptions);
2274
2275 editor._subscriptions.push(cx.subscribe_in(
2276 &cx.entity(),
2277 window,
2278 |editor, _, e: &EditorEvent, window, cx| match e {
2279 EditorEvent::ScrollPositionChanged { local, .. } => {
2280 if *local {
2281 let new_anchor = editor.scroll_manager.anchor();
2282 let snapshot = editor.snapshot(window, cx);
2283 editor.update_restoration_data(cx, move |data| {
2284 data.scroll_position = (
2285 new_anchor.top_row(&snapshot.buffer_snapshot),
2286 new_anchor.offset,
2287 );
2288 });
2289 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2290 editor.inline_blame_popover.take();
2291 }
2292 }
2293 EditorEvent::Edited { .. } => {
2294 if !vim_enabled(cx) {
2295 let (map, selections) = editor.selections.all_adjusted_display(cx);
2296 let pop_state = editor
2297 .change_list
2298 .last()
2299 .map(|previous| {
2300 previous.len() == selections.len()
2301 && previous.iter().enumerate().all(|(ix, p)| {
2302 p.to_display_point(&map).row()
2303 == selections[ix].head().row()
2304 })
2305 })
2306 .unwrap_or(false);
2307 let new_positions = selections
2308 .into_iter()
2309 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2310 .collect();
2311 editor
2312 .change_list
2313 .push_to_change_list(pop_state, new_positions);
2314 }
2315 }
2316 _ => (),
2317 },
2318 ));
2319
2320 if let Some(dap_store) = editor
2321 .project
2322 .as_ref()
2323 .map(|project| project.read(cx).dap_store())
2324 {
2325 let weak_editor = cx.weak_entity();
2326
2327 editor
2328 ._subscriptions
2329 .push(
2330 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2331 let session_entity = cx.entity();
2332 weak_editor
2333 .update(cx, |editor, cx| {
2334 editor._subscriptions.push(
2335 cx.subscribe(&session_entity, Self::on_debug_session_event),
2336 );
2337 })
2338 .ok();
2339 }),
2340 );
2341
2342 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2343 editor
2344 ._subscriptions
2345 .push(cx.subscribe(&session, Self::on_debug_session_event));
2346 }
2347 }
2348
2349 // skip adding the initial selection to selection history
2350 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2351 editor.end_selection(window, cx);
2352 editor.selection_history.mode = SelectionHistoryMode::Normal;
2353
2354 editor.scroll_manager.show_scrollbars(window, cx);
2355 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2356
2357 if full_mode {
2358 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2359 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2360
2361 if editor.git_blame_inline_enabled {
2362 editor.start_git_blame_inline(false, window, cx);
2363 }
2364
2365 editor.go_to_active_debug_line(window, cx);
2366
2367 if let Some(buffer) = buffer.read(cx).as_singleton()
2368 && let Some(project) = editor.project()
2369 {
2370 let handle = project.update(cx, |project, cx| {
2371 project.register_buffer_with_language_servers(&buffer, cx)
2372 });
2373 editor
2374 .registered_buffers
2375 .insert(buffer.read(cx).remote_id(), handle);
2376 }
2377
2378 editor.minimap =
2379 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2380 editor.colors = Some(LspColorData::new(cx));
2381 editor.update_lsp_data(false, None, window, cx);
2382 }
2383
2384 if editor.mode.is_full() {
2385 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2386 }
2387
2388 editor
2389 }
2390
2391 pub fn deploy_mouse_context_menu(
2392 &mut self,
2393 position: gpui::Point<Pixels>,
2394 context_menu: Entity<ContextMenu>,
2395 window: &mut Window,
2396 cx: &mut Context<Self>,
2397 ) {
2398 self.mouse_context_menu = Some(MouseContextMenu::new(
2399 self,
2400 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2401 context_menu,
2402 window,
2403 cx,
2404 ));
2405 }
2406
2407 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2408 self.mouse_context_menu
2409 .as_ref()
2410 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2411 }
2412
2413 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2414 if self
2415 .selections
2416 .pending
2417 .as_ref()
2418 .is_some_and(|pending_selection| {
2419 let snapshot = self.buffer().read(cx).snapshot(cx);
2420 pending_selection
2421 .selection
2422 .range()
2423 .includes(range, &snapshot)
2424 })
2425 {
2426 return true;
2427 }
2428
2429 self.selections
2430 .disjoint_in_range::<usize>(range.clone(), cx)
2431 .into_iter()
2432 .any(|selection| {
2433 // This is needed to cover a corner case, if we just check for an existing
2434 // selection in the fold range, having a cursor at the start of the fold
2435 // marks it as selected. Non-empty selections don't cause this.
2436 let length = selection.end - selection.start;
2437 length > 0
2438 })
2439 }
2440
2441 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2442 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2443 }
2444
2445 fn key_context_internal(
2446 &self,
2447 has_active_edit_prediction: bool,
2448 window: &Window,
2449 cx: &App,
2450 ) -> KeyContext {
2451 let mut key_context = KeyContext::new_with_defaults();
2452 key_context.add("Editor");
2453 let mode = match self.mode {
2454 EditorMode::SingleLine => "single_line",
2455 EditorMode::AutoHeight { .. } => "auto_height",
2456 EditorMode::Minimap { .. } => "minimap",
2457 EditorMode::Full { .. } => "full",
2458 };
2459
2460 if EditorSettings::jupyter_enabled(cx) {
2461 key_context.add("jupyter");
2462 }
2463
2464 key_context.set("mode", mode);
2465 if self.pending_rename.is_some() {
2466 key_context.add("renaming");
2467 }
2468
2469 match self.context_menu.borrow().as_ref() {
2470 Some(CodeContextMenu::Completions(menu)) => {
2471 if menu.visible() {
2472 key_context.add("menu");
2473 key_context.add("showing_completions");
2474 }
2475 }
2476 Some(CodeContextMenu::CodeActions(menu)) => {
2477 if menu.visible() {
2478 key_context.add("menu");
2479 key_context.add("showing_code_actions")
2480 }
2481 }
2482 None => {}
2483 }
2484
2485 if self.signature_help_state.has_multiple_signatures() {
2486 key_context.add("showing_signature_help");
2487 }
2488
2489 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2490 if !self.focus_handle(cx).contains_focused(window, cx)
2491 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2492 {
2493 for addon in self.addons.values() {
2494 addon.extend_key_context(&mut key_context, cx)
2495 }
2496 }
2497
2498 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2499 if let Some(extension) = singleton_buffer
2500 .read(cx)
2501 .file()
2502 .and_then(|file| file.path().extension()?.to_str())
2503 {
2504 key_context.set("extension", extension.to_string());
2505 }
2506 } else {
2507 key_context.add("multibuffer");
2508 }
2509
2510 if has_active_edit_prediction {
2511 if self.edit_prediction_in_conflict() {
2512 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2513 } else {
2514 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2515 key_context.add("copilot_suggestion");
2516 }
2517 }
2518
2519 if self.selection_mark_mode {
2520 key_context.add("selection_mode");
2521 }
2522
2523 key_context
2524 }
2525
2526 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2527 if self.mouse_cursor_hidden {
2528 self.mouse_cursor_hidden = false;
2529 cx.notify();
2530 }
2531 }
2532
2533 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2534 let hide_mouse_cursor = match origin {
2535 HideMouseCursorOrigin::TypingAction => {
2536 matches!(
2537 self.hide_mouse_mode,
2538 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2539 )
2540 }
2541 HideMouseCursorOrigin::MovementAction => {
2542 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2543 }
2544 };
2545 if self.mouse_cursor_hidden != hide_mouse_cursor {
2546 self.mouse_cursor_hidden = hide_mouse_cursor;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn edit_prediction_in_conflict(&self) -> bool {
2552 if !self.show_edit_predictions_in_menu() {
2553 return false;
2554 }
2555
2556 let showing_completions = self
2557 .context_menu
2558 .borrow()
2559 .as_ref()
2560 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2561
2562 showing_completions
2563 || self.edit_prediction_requires_modifier()
2564 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2565 // bindings to insert tab characters.
2566 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2567 }
2568
2569 pub fn accept_edit_prediction_keybind(
2570 &self,
2571 accept_partial: bool,
2572 window: &Window,
2573 cx: &App,
2574 ) -> AcceptEditPredictionBinding {
2575 let key_context = self.key_context_internal(true, window, cx);
2576 let in_conflict = self.edit_prediction_in_conflict();
2577
2578 let bindings = if accept_partial {
2579 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2580 } else {
2581 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2582 };
2583
2584 // TODO: if the binding contains multiple keystrokes, display all of them, not
2585 // just the first one.
2586 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2587 !in_conflict
2588 || binding
2589 .keystrokes()
2590 .first()
2591 .is_some_and(|keystroke| keystroke.modifiers().modified())
2592 }))
2593 }
2594
2595 pub fn new_file(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFile,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2602 "Failed to create buffer",
2603 window,
2604 cx,
2605 |e, _, _| match e.error_code() {
2606 ErrorCode::RemoteUpgradeRequired => Some(format!(
2607 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2608 e.error_tag("required").unwrap_or("the latest version")
2609 )),
2610 _ => None,
2611 },
2612 );
2613 }
2614
2615 pub fn new_in_workspace(
2616 workspace: &mut Workspace,
2617 window: &mut Window,
2618 cx: &mut Context<Workspace>,
2619 ) -> Task<Result<Entity<Editor>>> {
2620 let project = workspace.project().clone();
2621 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2622
2623 cx.spawn_in(window, async move |workspace, cx| {
2624 let buffer = create.await?;
2625 workspace.update_in(cx, |workspace, window, cx| {
2626 let editor =
2627 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2628 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2629 editor
2630 })
2631 })
2632 }
2633
2634 fn new_file_vertical(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitVertical,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2641 }
2642
2643 fn new_file_horizontal(
2644 workspace: &mut Workspace,
2645 _: &workspace::NewFileSplitHorizontal,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2650 }
2651
2652 fn new_file_in_direction(
2653 workspace: &mut Workspace,
2654 direction: SplitDirection,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 let project = workspace.project().clone();
2659 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2660
2661 cx.spawn_in(window, async move |workspace, cx| {
2662 let buffer = create.await?;
2663 workspace.update_in(cx, move |workspace, window, cx| {
2664 workspace.split_item(
2665 direction,
2666 Box::new(
2667 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2668 ),
2669 window,
2670 cx,
2671 )
2672 })?;
2673 anyhow::Ok(())
2674 })
2675 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2676 match e.error_code() {
2677 ErrorCode::RemoteUpgradeRequired => Some(format!(
2678 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2679 e.error_tag("required").unwrap_or("the latest version")
2680 )),
2681 _ => None,
2682 }
2683 });
2684 }
2685
2686 pub fn leader_id(&self) -> Option<CollaboratorId> {
2687 self.leader_id
2688 }
2689
2690 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2691 &self.buffer
2692 }
2693
2694 pub fn project(&self) -> Option<&Entity<Project>> {
2695 self.project.as_ref()
2696 }
2697
2698 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2699 self.workspace.as_ref()?.0.upgrade()
2700 }
2701
2702 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2703 self.buffer().read(cx).title(cx)
2704 }
2705
2706 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2707 let git_blame_gutter_max_author_length = self
2708 .render_git_blame_gutter(cx)
2709 .then(|| {
2710 if let Some(blame) = self.blame.as_ref() {
2711 let max_author_length =
2712 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2713 Some(max_author_length)
2714 } else {
2715 None
2716 }
2717 })
2718 .flatten();
2719
2720 EditorSnapshot {
2721 mode: self.mode.clone(),
2722 show_gutter: self.show_gutter,
2723 show_line_numbers: self.show_line_numbers,
2724 show_git_diff_gutter: self.show_git_diff_gutter,
2725 show_code_actions: self.show_code_actions,
2726 show_runnables: self.show_runnables,
2727 show_breakpoints: self.show_breakpoints,
2728 git_blame_gutter_max_author_length,
2729 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2730 scroll_anchor: self.scroll_manager.anchor(),
2731 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2732 placeholder_text: self.placeholder_text.clone(),
2733 is_focused: self.focus_handle.is_focused(window),
2734 current_line_highlight: self
2735 .current_line_highlight
2736 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2737 gutter_hovered: self.gutter_hovered,
2738 }
2739 }
2740
2741 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2742 self.buffer.read(cx).language_at(point, cx)
2743 }
2744
2745 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2746 self.buffer.read(cx).read(cx).file_at(point).cloned()
2747 }
2748
2749 pub fn active_excerpt(
2750 &self,
2751 cx: &App,
2752 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2753 self.buffer
2754 .read(cx)
2755 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2756 }
2757
2758 pub fn mode(&self) -> &EditorMode {
2759 &self.mode
2760 }
2761
2762 pub fn set_mode(&mut self, mode: EditorMode) {
2763 self.mode = mode;
2764 }
2765
2766 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2767 self.collaboration_hub.as_deref()
2768 }
2769
2770 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2771 self.collaboration_hub = Some(hub);
2772 }
2773
2774 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2775 self.in_project_search = in_project_search;
2776 }
2777
2778 pub fn set_custom_context_menu(
2779 &mut self,
2780 f: impl 'static
2781 + Fn(
2782 &mut Self,
2783 DisplayPoint,
2784 &mut Window,
2785 &mut Context<Self>,
2786 ) -> Option<Entity<ui::ContextMenu>>,
2787 ) {
2788 self.custom_context_menu = Some(Box::new(f))
2789 }
2790
2791 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2792 self.completion_provider = provider;
2793 }
2794
2795 #[cfg(any(test, feature = "test-support"))]
2796 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2797 self.completion_provider.clone()
2798 }
2799
2800 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2801 self.semantics_provider.clone()
2802 }
2803
2804 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2805 self.semantics_provider = provider;
2806 }
2807
2808 pub fn set_edit_prediction_provider<T>(
2809 &mut self,
2810 provider: Option<Entity<T>>,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) where
2814 T: EditPredictionProvider,
2815 {
2816 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2817 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2818 if this.focus_handle.is_focused(window) {
2819 this.update_visible_edit_prediction(window, cx);
2820 }
2821 }),
2822 provider: Arc::new(provider),
2823 });
2824 self.update_edit_prediction_settings(cx);
2825 self.refresh_edit_prediction(false, false, window, cx);
2826 }
2827
2828 pub fn placeholder_text(&self) -> Option<&str> {
2829 self.placeholder_text.as_deref()
2830 }
2831
2832 pub fn set_placeholder_text(
2833 &mut self,
2834 placeholder_text: impl Into<Arc<str>>,
2835 cx: &mut Context<Self>,
2836 ) {
2837 let placeholder_text = Some(placeholder_text.into());
2838 if self.placeholder_text != placeholder_text {
2839 self.placeholder_text = placeholder_text;
2840 cx.notify();
2841 }
2842 }
2843
2844 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2845 self.cursor_shape = cursor_shape;
2846
2847 // Disrupt blink for immediate user feedback that the cursor shape has changed
2848 self.blink_manager.update(cx, BlinkManager::show_cursor);
2849
2850 cx.notify();
2851 }
2852
2853 pub fn set_current_line_highlight(
2854 &mut self,
2855 current_line_highlight: Option<CurrentLineHighlight>,
2856 ) {
2857 self.current_line_highlight = current_line_highlight;
2858 }
2859
2860 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2861 self.collapse_matches = collapse_matches;
2862 }
2863
2864 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2865 let buffers = self.buffer.read(cx).all_buffers();
2866 let Some(project) = self.project.as_ref() else {
2867 return;
2868 };
2869 project.update(cx, |project, cx| {
2870 for buffer in buffers {
2871 self.registered_buffers
2872 .entry(buffer.read(cx).remote_id())
2873 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2874 }
2875 })
2876 }
2877
2878 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2879 if self.collapse_matches {
2880 return range.start..range.start;
2881 }
2882 range.clone()
2883 }
2884
2885 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2886 if self.display_map.read(cx).clip_at_line_ends != clip {
2887 self.display_map
2888 .update(cx, |map, _| map.clip_at_line_ends = clip);
2889 }
2890 }
2891
2892 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2893 self.input_enabled = input_enabled;
2894 }
2895
2896 pub fn set_edit_predictions_hidden_for_vim_mode(
2897 &mut self,
2898 hidden: bool,
2899 window: &mut Window,
2900 cx: &mut Context<Self>,
2901 ) {
2902 if hidden != self.edit_predictions_hidden_for_vim_mode {
2903 self.edit_predictions_hidden_for_vim_mode = hidden;
2904 if hidden {
2905 self.update_visible_edit_prediction(window, cx);
2906 } else {
2907 self.refresh_edit_prediction(true, false, window, cx);
2908 }
2909 }
2910 }
2911
2912 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2913 self.menu_edit_predictions_policy = value;
2914 }
2915
2916 pub fn set_autoindent(&mut self, autoindent: bool) {
2917 if autoindent {
2918 self.autoindent_mode = Some(AutoindentMode::EachLine);
2919 } else {
2920 self.autoindent_mode = None;
2921 }
2922 }
2923
2924 pub fn read_only(&self, cx: &App) -> bool {
2925 self.read_only || self.buffer.read(cx).read_only()
2926 }
2927
2928 pub fn set_read_only(&mut self, read_only: bool) {
2929 self.read_only = read_only;
2930 }
2931
2932 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2933 self.use_autoclose = autoclose;
2934 }
2935
2936 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2937 self.use_auto_surround = auto_surround;
2938 }
2939
2940 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2941 self.auto_replace_emoji_shortcode = auto_replace;
2942 }
2943
2944 pub fn toggle_edit_predictions(
2945 &mut self,
2946 _: &ToggleEditPrediction,
2947 window: &mut Window,
2948 cx: &mut Context<Self>,
2949 ) {
2950 if self.show_edit_predictions_override.is_some() {
2951 self.set_show_edit_predictions(None, window, cx);
2952 } else {
2953 let show_edit_predictions = !self.edit_predictions_enabled();
2954 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2955 }
2956 }
2957
2958 pub fn set_show_edit_predictions(
2959 &mut self,
2960 show_edit_predictions: Option<bool>,
2961 window: &mut Window,
2962 cx: &mut Context<Self>,
2963 ) {
2964 self.show_edit_predictions_override = show_edit_predictions;
2965 self.update_edit_prediction_settings(cx);
2966
2967 if let Some(false) = show_edit_predictions {
2968 self.discard_edit_prediction(false, cx);
2969 } else {
2970 self.refresh_edit_prediction(false, true, window, cx);
2971 }
2972 }
2973
2974 fn edit_predictions_disabled_in_scope(
2975 &self,
2976 buffer: &Entity<Buffer>,
2977 buffer_position: language::Anchor,
2978 cx: &App,
2979 ) -> bool {
2980 let snapshot = buffer.read(cx).snapshot();
2981 let settings = snapshot.settings_at(buffer_position, cx);
2982
2983 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2984 return false;
2985 };
2986
2987 scope.override_name().is_some_and(|scope_name| {
2988 settings
2989 .edit_predictions_disabled_in
2990 .iter()
2991 .any(|s| s == scope_name)
2992 })
2993 }
2994
2995 pub fn set_use_modal_editing(&mut self, to: bool) {
2996 self.use_modal_editing = to;
2997 }
2998
2999 pub fn use_modal_editing(&self) -> bool {
3000 self.use_modal_editing
3001 }
3002
3003 fn selections_did_change(
3004 &mut self,
3005 local: bool,
3006 old_cursor_position: &Anchor,
3007 effects: SelectionEffects,
3008 window: &mut Window,
3009 cx: &mut Context<Self>,
3010 ) {
3011 window.invalidate_character_coordinates();
3012
3013 // Copy selections to primary selection buffer
3014 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3015 if local {
3016 let selections = self.selections.all::<usize>(cx);
3017 let buffer_handle = self.buffer.read(cx).read(cx);
3018
3019 let mut text = String::new();
3020 for (index, selection) in selections.iter().enumerate() {
3021 let text_for_selection = buffer_handle
3022 .text_for_range(selection.start..selection.end)
3023 .collect::<String>();
3024
3025 text.push_str(&text_for_selection);
3026 if index != selections.len() - 1 {
3027 text.push('\n');
3028 }
3029 }
3030
3031 if !text.is_empty() {
3032 cx.write_to_primary(ClipboardItem::new_string(text));
3033 }
3034 }
3035
3036 let selection_anchors = self.selections.disjoint_anchors();
3037
3038 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3039 self.buffer.update(cx, |buffer, cx| {
3040 buffer.set_active_selections(
3041 &selection_anchors,
3042 self.selections.line_mode,
3043 self.cursor_shape,
3044 cx,
3045 )
3046 });
3047 }
3048 let display_map = self
3049 .display_map
3050 .update(cx, |display_map, cx| display_map.snapshot(cx));
3051 let buffer = &display_map.buffer_snapshot;
3052 if self.selections.count() == 1 {
3053 self.add_selections_state = None;
3054 }
3055 self.select_next_state = None;
3056 self.select_prev_state = None;
3057 self.select_syntax_node_history.try_clear();
3058 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3059 self.snippet_stack.invalidate(&selection_anchors, buffer);
3060 self.take_rename(false, window, cx);
3061
3062 let newest_selection = self.selections.newest_anchor();
3063 let new_cursor_position = newest_selection.head();
3064 let selection_start = newest_selection.start;
3065
3066 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3067 self.push_to_nav_history(
3068 *old_cursor_position,
3069 Some(new_cursor_position.to_point(buffer)),
3070 false,
3071 effects.nav_history == Some(true),
3072 cx,
3073 );
3074 }
3075
3076 if local {
3077 if let Some(buffer_id) = new_cursor_position.buffer_id
3078 && !self.registered_buffers.contains_key(&buffer_id)
3079 && let Some(project) = self.project.as_ref()
3080 {
3081 project.update(cx, |project, cx| {
3082 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3083 return;
3084 };
3085 self.registered_buffers.insert(
3086 buffer_id,
3087 project.register_buffer_with_language_servers(&buffer, cx),
3088 );
3089 })
3090 }
3091
3092 let mut context_menu = self.context_menu.borrow_mut();
3093 let completion_menu = match context_menu.as_ref() {
3094 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3095 Some(CodeContextMenu::CodeActions(_)) => {
3096 *context_menu = None;
3097 None
3098 }
3099 None => None,
3100 };
3101 let completion_position = completion_menu.map(|menu| menu.initial_position);
3102 drop(context_menu);
3103
3104 if effects.completions
3105 && let Some(completion_position) = completion_position
3106 {
3107 let start_offset = selection_start.to_offset(buffer);
3108 let position_matches = start_offset == completion_position.to_offset(buffer);
3109 let continue_showing = if position_matches {
3110 if self.snippet_stack.is_empty() {
3111 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3112 } else {
3113 // Snippet choices can be shown even when the cursor is in whitespace.
3114 // Dismissing the menu with actions like backspace is handled by
3115 // invalidation regions.
3116 true
3117 }
3118 } else {
3119 false
3120 };
3121
3122 if continue_showing {
3123 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3124 } else {
3125 self.hide_context_menu(window, cx);
3126 }
3127 }
3128
3129 hide_hover(self, cx);
3130
3131 if old_cursor_position.to_display_point(&display_map).row()
3132 != new_cursor_position.to_display_point(&display_map).row()
3133 {
3134 self.available_code_actions.take();
3135 }
3136 self.refresh_code_actions(window, cx);
3137 self.refresh_document_highlights(cx);
3138 self.refresh_selected_text_highlights(false, window, cx);
3139 refresh_matching_bracket_highlights(self, window, cx);
3140 self.update_visible_edit_prediction(window, cx);
3141 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3142 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3143 self.inline_blame_popover.take();
3144 if self.git_blame_inline_enabled {
3145 self.start_inline_blame_timer(window, cx);
3146 }
3147 }
3148
3149 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3150 cx.emit(EditorEvent::SelectionsChanged { local });
3151
3152 let selections = &self.selections.disjoint;
3153 if selections.len() == 1 {
3154 cx.emit(SearchEvent::ActiveMatchChanged)
3155 }
3156 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3157 let inmemory_selections = selections
3158 .iter()
3159 .map(|s| {
3160 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3161 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3162 })
3163 .collect();
3164 self.update_restoration_data(cx, |data| {
3165 data.selections = inmemory_selections;
3166 });
3167
3168 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3169 && let Some(workspace_id) =
3170 self.workspace.as_ref().and_then(|workspace| workspace.1)
3171 {
3172 let snapshot = self.buffer().read(cx).snapshot(cx);
3173 let selections = selections.clone();
3174 let background_executor = cx.background_executor().clone();
3175 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3176 self.serialize_selections = cx.background_spawn(async move {
3177 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3178 let db_selections = selections
3179 .iter()
3180 .map(|selection| {
3181 (
3182 selection.start.to_offset(&snapshot),
3183 selection.end.to_offset(&snapshot),
3184 )
3185 })
3186 .collect();
3187
3188 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3189 .await
3190 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3191 .log_err();
3192 });
3193 }
3194 }
3195
3196 cx.notify();
3197 }
3198
3199 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3200 use text::ToOffset as _;
3201 use text::ToPoint as _;
3202
3203 if self.mode.is_minimap()
3204 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3205 {
3206 return;
3207 }
3208
3209 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3210 return;
3211 };
3212
3213 let snapshot = singleton.read(cx).snapshot();
3214 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3215 let display_snapshot = display_map.snapshot(cx);
3216
3217 display_snapshot
3218 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3219 .map(|fold| {
3220 fold.range.start.text_anchor.to_point(&snapshot)
3221 ..fold.range.end.text_anchor.to_point(&snapshot)
3222 })
3223 .collect()
3224 });
3225 self.update_restoration_data(cx, |data| {
3226 data.folds = inmemory_folds;
3227 });
3228
3229 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3230 return;
3231 };
3232 let background_executor = cx.background_executor().clone();
3233 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3234 let db_folds = self.display_map.update(cx, |display_map, cx| {
3235 display_map
3236 .snapshot(cx)
3237 .folds_in_range(0..snapshot.len())
3238 .map(|fold| {
3239 (
3240 fold.range.start.text_anchor.to_offset(&snapshot),
3241 fold.range.end.text_anchor.to_offset(&snapshot),
3242 )
3243 })
3244 .collect()
3245 });
3246 self.serialize_folds = cx.background_spawn(async move {
3247 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3248 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3249 .await
3250 .with_context(|| {
3251 format!(
3252 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3253 )
3254 })
3255 .log_err();
3256 });
3257 }
3258
3259 pub fn sync_selections(
3260 &mut self,
3261 other: Entity<Editor>,
3262 cx: &mut Context<Self>,
3263 ) -> gpui::Subscription {
3264 let other_selections = other.read(cx).selections.disjoint.to_vec();
3265 self.selections.change_with(cx, |selections| {
3266 selections.select_anchors(other_selections);
3267 });
3268
3269 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3270 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3271 let other_selections = other.read(cx).selections.disjoint.to_vec();
3272 if other_selections.is_empty() {
3273 return;
3274 }
3275 this.selections.change_with(cx, |selections| {
3276 selections.select_anchors(other_selections);
3277 });
3278 }
3279 });
3280
3281 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3282 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3283 let these_selections = this.selections.disjoint.to_vec();
3284 if these_selections.is_empty() {
3285 return;
3286 }
3287 other.update(cx, |other_editor, cx| {
3288 other_editor.selections.change_with(cx, |selections| {
3289 selections.select_anchors(these_selections);
3290 })
3291 });
3292 }
3293 });
3294
3295 Subscription::join(other_subscription, this_subscription)
3296 }
3297
3298 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3299 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3300 /// effects of selection change occur at the end of the transaction.
3301 pub fn change_selections<R>(
3302 &mut self,
3303 effects: SelectionEffects,
3304 window: &mut Window,
3305 cx: &mut Context<Self>,
3306 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3307 ) -> R {
3308 if let Some(state) = &mut self.deferred_selection_effects_state {
3309 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3310 state.effects.completions = effects.completions;
3311 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3312 let (changed, result) = self.selections.change_with(cx, change);
3313 state.changed |= changed;
3314 return result;
3315 }
3316 let mut state = DeferredSelectionEffectsState {
3317 changed: false,
3318 effects,
3319 old_cursor_position: self.selections.newest_anchor().head(),
3320 history_entry: SelectionHistoryEntry {
3321 selections: self.selections.disjoint_anchors(),
3322 select_next_state: self.select_next_state.clone(),
3323 select_prev_state: self.select_prev_state.clone(),
3324 add_selections_state: self.add_selections_state.clone(),
3325 },
3326 };
3327 let (changed, result) = self.selections.change_with(cx, change);
3328 state.changed = state.changed || changed;
3329 if self.defer_selection_effects {
3330 self.deferred_selection_effects_state = Some(state);
3331 } else {
3332 self.apply_selection_effects(state, window, cx);
3333 }
3334 result
3335 }
3336
3337 /// Defers the effects of selection change, so that the effects of multiple calls to
3338 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3339 /// to selection history and the state of popovers based on selection position aren't
3340 /// erroneously updated.
3341 pub fn with_selection_effects_deferred<R>(
3342 &mut self,
3343 window: &mut Window,
3344 cx: &mut Context<Self>,
3345 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3346 ) -> R {
3347 let already_deferred = self.defer_selection_effects;
3348 self.defer_selection_effects = true;
3349 let result = update(self, window, cx);
3350 if !already_deferred {
3351 self.defer_selection_effects = false;
3352 if let Some(state) = self.deferred_selection_effects_state.take() {
3353 self.apply_selection_effects(state, window, cx);
3354 }
3355 }
3356 result
3357 }
3358
3359 fn apply_selection_effects(
3360 &mut self,
3361 state: DeferredSelectionEffectsState,
3362 window: &mut Window,
3363 cx: &mut Context<Self>,
3364 ) {
3365 if state.changed {
3366 self.selection_history.push(state.history_entry);
3367
3368 if let Some(autoscroll) = state.effects.scroll {
3369 self.request_autoscroll(autoscroll, cx);
3370 }
3371
3372 let old_cursor_position = &state.old_cursor_position;
3373
3374 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3375
3376 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3377 self.show_signature_help(&ShowSignatureHelp, window, cx);
3378 }
3379 }
3380 }
3381
3382 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3383 where
3384 I: IntoIterator<Item = (Range<S>, T)>,
3385 S: ToOffset,
3386 T: Into<Arc<str>>,
3387 {
3388 if self.read_only(cx) {
3389 return;
3390 }
3391
3392 self.buffer
3393 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3394 }
3395
3396 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3397 where
3398 I: IntoIterator<Item = (Range<S>, T)>,
3399 S: ToOffset,
3400 T: Into<Arc<str>>,
3401 {
3402 if self.read_only(cx) {
3403 return;
3404 }
3405
3406 self.buffer.update(cx, |buffer, cx| {
3407 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3408 });
3409 }
3410
3411 pub fn edit_with_block_indent<I, S, T>(
3412 &mut self,
3413 edits: I,
3414 original_indent_columns: Vec<Option<u32>>,
3415 cx: &mut Context<Self>,
3416 ) where
3417 I: IntoIterator<Item = (Range<S>, T)>,
3418 S: ToOffset,
3419 T: Into<Arc<str>>,
3420 {
3421 if self.read_only(cx) {
3422 return;
3423 }
3424
3425 self.buffer.update(cx, |buffer, cx| {
3426 buffer.edit(
3427 edits,
3428 Some(AutoindentMode::Block {
3429 original_indent_columns,
3430 }),
3431 cx,
3432 )
3433 });
3434 }
3435
3436 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3437 self.hide_context_menu(window, cx);
3438
3439 match phase {
3440 SelectPhase::Begin {
3441 position,
3442 add,
3443 click_count,
3444 } => self.begin_selection(position, add, click_count, window, cx),
3445 SelectPhase::BeginColumnar {
3446 position,
3447 goal_column,
3448 reset,
3449 mode,
3450 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3451 SelectPhase::Extend {
3452 position,
3453 click_count,
3454 } => self.extend_selection(position, click_count, window, cx),
3455 SelectPhase::Update {
3456 position,
3457 goal_column,
3458 scroll_delta,
3459 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3460 SelectPhase::End => self.end_selection(window, cx),
3461 }
3462 }
3463
3464 fn extend_selection(
3465 &mut self,
3466 position: DisplayPoint,
3467 click_count: usize,
3468 window: &mut Window,
3469 cx: &mut Context<Self>,
3470 ) {
3471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3472 let tail = self.selections.newest::<usize>(cx).tail();
3473 self.begin_selection(position, false, click_count, window, cx);
3474
3475 let position = position.to_offset(&display_map, Bias::Left);
3476 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3477
3478 let mut pending_selection = self
3479 .selections
3480 .pending_anchor()
3481 .expect("extend_selection not called with pending selection");
3482 if position >= tail {
3483 pending_selection.start = tail_anchor;
3484 } else {
3485 pending_selection.end = tail_anchor;
3486 pending_selection.reversed = true;
3487 }
3488
3489 let mut pending_mode = self.selections.pending_mode().unwrap();
3490 match &mut pending_mode {
3491 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3492 _ => {}
3493 }
3494
3495 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3496 SelectionEffects::scroll(Autoscroll::fit())
3497 } else {
3498 SelectionEffects::no_scroll()
3499 };
3500
3501 self.change_selections(effects, window, cx, |s| {
3502 s.set_pending(pending_selection, pending_mode)
3503 });
3504 }
3505
3506 fn begin_selection(
3507 &mut self,
3508 position: DisplayPoint,
3509 add: bool,
3510 click_count: usize,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 ) {
3514 if !self.focus_handle.is_focused(window) {
3515 self.last_focused_descendant = None;
3516 window.focus(&self.focus_handle);
3517 }
3518
3519 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3520 let buffer = &display_map.buffer_snapshot;
3521 let position = display_map.clip_point(position, Bias::Left);
3522
3523 let start;
3524 let end;
3525 let mode;
3526 let mut auto_scroll;
3527 match click_count {
3528 1 => {
3529 start = buffer.anchor_before(position.to_point(&display_map));
3530 end = start;
3531 mode = SelectMode::Character;
3532 auto_scroll = true;
3533 }
3534 2 => {
3535 let position = display_map
3536 .clip_point(position, Bias::Left)
3537 .to_offset(&display_map, Bias::Left);
3538 let (range, _) = buffer.surrounding_word(position, false);
3539 start = buffer.anchor_before(range.start);
3540 end = buffer.anchor_before(range.end);
3541 mode = SelectMode::Word(start..end);
3542 auto_scroll = true;
3543 }
3544 3 => {
3545 let position = display_map
3546 .clip_point(position, Bias::Left)
3547 .to_point(&display_map);
3548 let line_start = display_map.prev_line_boundary(position).0;
3549 let next_line_start = buffer.clip_point(
3550 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3551 Bias::Left,
3552 );
3553 start = buffer.anchor_before(line_start);
3554 end = buffer.anchor_before(next_line_start);
3555 mode = SelectMode::Line(start..end);
3556 auto_scroll = true;
3557 }
3558 _ => {
3559 start = buffer.anchor_before(0);
3560 end = buffer.anchor_before(buffer.len());
3561 mode = SelectMode::All;
3562 auto_scroll = false;
3563 }
3564 }
3565 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3566
3567 let point_to_delete: Option<usize> = {
3568 let selected_points: Vec<Selection<Point>> =
3569 self.selections.disjoint_in_range(start..end, cx);
3570
3571 if !add || click_count > 1 {
3572 None
3573 } else if !selected_points.is_empty() {
3574 Some(selected_points[0].id)
3575 } else {
3576 let clicked_point_already_selected =
3577 self.selections.disjoint.iter().find(|selection| {
3578 selection.start.to_point(buffer) == start.to_point(buffer)
3579 || selection.end.to_point(buffer) == end.to_point(buffer)
3580 });
3581
3582 clicked_point_already_selected.map(|selection| selection.id)
3583 }
3584 };
3585
3586 let selections_count = self.selections.count();
3587 let effects = if auto_scroll {
3588 SelectionEffects::default()
3589 } else {
3590 SelectionEffects::no_scroll()
3591 };
3592
3593 self.change_selections(effects, window, cx, |s| {
3594 if let Some(point_to_delete) = point_to_delete {
3595 s.delete(point_to_delete);
3596
3597 if selections_count == 1 {
3598 s.set_pending_anchor_range(start..end, mode);
3599 }
3600 } else {
3601 if !add {
3602 s.clear_disjoint();
3603 }
3604
3605 s.set_pending_anchor_range(start..end, mode);
3606 }
3607 });
3608 }
3609
3610 fn begin_columnar_selection(
3611 &mut self,
3612 position: DisplayPoint,
3613 goal_column: u32,
3614 reset: bool,
3615 mode: ColumnarMode,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 if !self.focus_handle.is_focused(window) {
3620 self.last_focused_descendant = None;
3621 window.focus(&self.focus_handle);
3622 }
3623
3624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3625
3626 if reset {
3627 let pointer_position = display_map
3628 .buffer_snapshot
3629 .anchor_before(position.to_point(&display_map));
3630
3631 self.change_selections(
3632 SelectionEffects::scroll(Autoscroll::newest()),
3633 window,
3634 cx,
3635 |s| {
3636 s.clear_disjoint();
3637 s.set_pending_anchor_range(
3638 pointer_position..pointer_position,
3639 SelectMode::Character,
3640 );
3641 },
3642 );
3643 };
3644
3645 let tail = self.selections.newest::<Point>(cx).tail();
3646 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3647 self.columnar_selection_state = match mode {
3648 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3649 selection_tail: selection_anchor,
3650 display_point: if reset {
3651 if position.column() != goal_column {
3652 Some(DisplayPoint::new(position.row(), goal_column))
3653 } else {
3654 None
3655 }
3656 } else {
3657 None
3658 },
3659 }),
3660 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3661 selection_tail: selection_anchor,
3662 }),
3663 };
3664
3665 if !reset {
3666 self.select_columns(position, goal_column, &display_map, window, cx);
3667 }
3668 }
3669
3670 fn update_selection(
3671 &mut self,
3672 position: DisplayPoint,
3673 goal_column: u32,
3674 scroll_delta: gpui::Point<f32>,
3675 window: &mut Window,
3676 cx: &mut Context<Self>,
3677 ) {
3678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3679
3680 if self.columnar_selection_state.is_some() {
3681 self.select_columns(position, goal_column, &display_map, window, cx);
3682 } else if let Some(mut pending) = self.selections.pending_anchor() {
3683 let buffer = &display_map.buffer_snapshot;
3684 let head;
3685 let tail;
3686 let mode = self.selections.pending_mode().unwrap();
3687 match &mode {
3688 SelectMode::Character => {
3689 head = position.to_point(&display_map);
3690 tail = pending.tail().to_point(buffer);
3691 }
3692 SelectMode::Word(original_range) => {
3693 let offset = display_map
3694 .clip_point(position, Bias::Left)
3695 .to_offset(&display_map, Bias::Left);
3696 let original_range = original_range.to_offset(buffer);
3697
3698 let head_offset = if buffer.is_inside_word(offset, false)
3699 || original_range.contains(&offset)
3700 {
3701 let (word_range, _) = buffer.surrounding_word(offset, false);
3702 if word_range.start < original_range.start {
3703 word_range.start
3704 } else {
3705 word_range.end
3706 }
3707 } else {
3708 offset
3709 };
3710
3711 head = head_offset.to_point(buffer);
3712 if head_offset <= original_range.start {
3713 tail = original_range.end.to_point(buffer);
3714 } else {
3715 tail = original_range.start.to_point(buffer);
3716 }
3717 }
3718 SelectMode::Line(original_range) => {
3719 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3720
3721 let position = display_map
3722 .clip_point(position, Bias::Left)
3723 .to_point(&display_map);
3724 let line_start = display_map.prev_line_boundary(position).0;
3725 let next_line_start = buffer.clip_point(
3726 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3727 Bias::Left,
3728 );
3729
3730 if line_start < original_range.start {
3731 head = line_start
3732 } else {
3733 head = next_line_start
3734 }
3735
3736 if head <= original_range.start {
3737 tail = original_range.end;
3738 } else {
3739 tail = original_range.start;
3740 }
3741 }
3742 SelectMode::All => {
3743 return;
3744 }
3745 };
3746
3747 if head < tail {
3748 pending.start = buffer.anchor_before(head);
3749 pending.end = buffer.anchor_before(tail);
3750 pending.reversed = true;
3751 } else {
3752 pending.start = buffer.anchor_before(tail);
3753 pending.end = buffer.anchor_before(head);
3754 pending.reversed = false;
3755 }
3756
3757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3758 s.set_pending(pending, mode);
3759 });
3760 } else {
3761 log::error!("update_selection dispatched with no pending selection");
3762 return;
3763 }
3764
3765 self.apply_scroll_delta(scroll_delta, window, cx);
3766 cx.notify();
3767 }
3768
3769 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3770 self.columnar_selection_state.take();
3771 if self.selections.pending_anchor().is_some() {
3772 let selections = self.selections.all::<usize>(cx);
3773 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3774 s.select(selections);
3775 s.clear_pending();
3776 });
3777 }
3778 }
3779
3780 fn select_columns(
3781 &mut self,
3782 head: DisplayPoint,
3783 goal_column: u32,
3784 display_map: &DisplaySnapshot,
3785 window: &mut Window,
3786 cx: &mut Context<Self>,
3787 ) {
3788 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3789 return;
3790 };
3791
3792 let tail = match columnar_state {
3793 ColumnarSelectionState::FromMouse {
3794 selection_tail,
3795 display_point,
3796 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3797 ColumnarSelectionState::FromSelection { selection_tail } => {
3798 selection_tail.to_display_point(display_map)
3799 }
3800 };
3801
3802 let start_row = cmp::min(tail.row(), head.row());
3803 let end_row = cmp::max(tail.row(), head.row());
3804 let start_column = cmp::min(tail.column(), goal_column);
3805 let end_column = cmp::max(tail.column(), goal_column);
3806 let reversed = start_column < tail.column();
3807
3808 let selection_ranges = (start_row.0..=end_row.0)
3809 .map(DisplayRow)
3810 .filter_map(|row| {
3811 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3812 || start_column <= display_map.line_len(row))
3813 && !display_map.is_block_line(row)
3814 {
3815 let start = display_map
3816 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3817 .to_point(display_map);
3818 let end = display_map
3819 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3820 .to_point(display_map);
3821 if reversed {
3822 Some(end..start)
3823 } else {
3824 Some(start..end)
3825 }
3826 } else {
3827 None
3828 }
3829 })
3830 .collect::<Vec<_>>();
3831
3832 let ranges = match columnar_state {
3833 ColumnarSelectionState::FromMouse { .. } => {
3834 let mut non_empty_ranges = selection_ranges
3835 .iter()
3836 .filter(|selection_range| selection_range.start != selection_range.end)
3837 .peekable();
3838 if non_empty_ranges.peek().is_some() {
3839 non_empty_ranges.cloned().collect()
3840 } else {
3841 selection_ranges
3842 }
3843 }
3844 _ => selection_ranges,
3845 };
3846
3847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3848 s.select_ranges(ranges);
3849 });
3850 cx.notify();
3851 }
3852
3853 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3854 self.selections
3855 .all_adjusted(cx)
3856 .iter()
3857 .any(|selection| !selection.is_empty())
3858 }
3859
3860 pub fn has_pending_nonempty_selection(&self) -> bool {
3861 let pending_nonempty_selection = match self.selections.pending_anchor() {
3862 Some(Selection { start, end, .. }) => start != end,
3863 None => false,
3864 };
3865
3866 pending_nonempty_selection
3867 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3868 }
3869
3870 pub fn has_pending_selection(&self) -> bool {
3871 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3872 }
3873
3874 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3875 self.selection_mark_mode = false;
3876 self.selection_drag_state = SelectionDragState::None;
3877
3878 if self.clear_expanded_diff_hunks(cx) {
3879 cx.notify();
3880 return;
3881 }
3882 if self.dismiss_menus_and_popups(true, window, cx) {
3883 return;
3884 }
3885
3886 if self.mode.is_full()
3887 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3888 {
3889 return;
3890 }
3891
3892 cx.propagate();
3893 }
3894
3895 pub fn dismiss_menus_and_popups(
3896 &mut self,
3897 is_user_requested: bool,
3898 window: &mut Window,
3899 cx: &mut Context<Self>,
3900 ) -> bool {
3901 if self.take_rename(false, window, cx).is_some() {
3902 return true;
3903 }
3904
3905 if hide_hover(self, cx) {
3906 return true;
3907 }
3908
3909 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3910 return true;
3911 }
3912
3913 if self.hide_context_menu(window, cx).is_some() {
3914 return true;
3915 }
3916
3917 if self.mouse_context_menu.take().is_some() {
3918 return true;
3919 }
3920
3921 if is_user_requested && self.discard_edit_prediction(true, cx) {
3922 return true;
3923 }
3924
3925 if self.snippet_stack.pop().is_some() {
3926 return true;
3927 }
3928
3929 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3930 self.dismiss_diagnostics(cx);
3931 return true;
3932 }
3933
3934 false
3935 }
3936
3937 fn linked_editing_ranges_for(
3938 &self,
3939 selection: Range<text::Anchor>,
3940 cx: &App,
3941 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3942 if self.linked_edit_ranges.is_empty() {
3943 return None;
3944 }
3945 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3946 selection.end.buffer_id.and_then(|end_buffer_id| {
3947 if selection.start.buffer_id != Some(end_buffer_id) {
3948 return None;
3949 }
3950 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3951 let snapshot = buffer.read(cx).snapshot();
3952 self.linked_edit_ranges
3953 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3954 .map(|ranges| (ranges, snapshot, buffer))
3955 })?;
3956 use text::ToOffset as TO;
3957 // find offset from the start of current range to current cursor position
3958 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3959
3960 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3961 let start_difference = start_offset - start_byte_offset;
3962 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3963 let end_difference = end_offset - start_byte_offset;
3964 // Current range has associated linked ranges.
3965 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3966 for range in linked_ranges.iter() {
3967 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3968 let end_offset = start_offset + end_difference;
3969 let start_offset = start_offset + start_difference;
3970 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3971 continue;
3972 }
3973 if self.selections.disjoint_anchor_ranges().any(|s| {
3974 if s.start.buffer_id != selection.start.buffer_id
3975 || s.end.buffer_id != selection.end.buffer_id
3976 {
3977 return false;
3978 }
3979 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3980 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3981 }) {
3982 continue;
3983 }
3984 let start = buffer_snapshot.anchor_after(start_offset);
3985 let end = buffer_snapshot.anchor_after(end_offset);
3986 linked_edits
3987 .entry(buffer.clone())
3988 .or_default()
3989 .push(start..end);
3990 }
3991 Some(linked_edits)
3992 }
3993
3994 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3995 let text: Arc<str> = text.into();
3996
3997 if self.read_only(cx) {
3998 return;
3999 }
4000
4001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4002
4003 let selections = self.selections.all_adjusted(cx);
4004 let mut bracket_inserted = false;
4005 let mut edits = Vec::new();
4006 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4007 let mut new_selections = Vec::with_capacity(selections.len());
4008 let mut new_autoclose_regions = Vec::new();
4009 let snapshot = self.buffer.read(cx).read(cx);
4010 let mut clear_linked_edit_ranges = false;
4011
4012 for (selection, autoclose_region) in
4013 self.selections_with_autoclose_regions(selections, &snapshot)
4014 {
4015 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4016 // Determine if the inserted text matches the opening or closing
4017 // bracket of any of this language's bracket pairs.
4018 let mut bracket_pair = None;
4019 let mut is_bracket_pair_start = false;
4020 let mut is_bracket_pair_end = false;
4021 if !text.is_empty() {
4022 let mut bracket_pair_matching_end = None;
4023 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4024 // and they are removing the character that triggered IME popup.
4025 for (pair, enabled) in scope.brackets() {
4026 if !pair.close && !pair.surround {
4027 continue;
4028 }
4029
4030 if enabled && pair.start.ends_with(text.as_ref()) {
4031 let prefix_len = pair.start.len() - text.len();
4032 let preceding_text_matches_prefix = prefix_len == 0
4033 || (selection.start.column >= (prefix_len as u32)
4034 && snapshot.contains_str_at(
4035 Point::new(
4036 selection.start.row,
4037 selection.start.column - (prefix_len as u32),
4038 ),
4039 &pair.start[..prefix_len],
4040 ));
4041 if preceding_text_matches_prefix {
4042 bracket_pair = Some(pair.clone());
4043 is_bracket_pair_start = true;
4044 break;
4045 }
4046 }
4047 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4048 {
4049 // take first bracket pair matching end, but don't break in case a later bracket
4050 // pair matches start
4051 bracket_pair_matching_end = Some(pair.clone());
4052 }
4053 }
4054 if let Some(end) = bracket_pair_matching_end
4055 && bracket_pair.is_none()
4056 {
4057 bracket_pair = Some(end);
4058 is_bracket_pair_end = true;
4059 }
4060 }
4061
4062 if let Some(bracket_pair) = bracket_pair {
4063 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4064 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4065 let auto_surround =
4066 self.use_auto_surround && snapshot_settings.use_auto_surround;
4067 if selection.is_empty() {
4068 if is_bracket_pair_start {
4069 // If the inserted text is a suffix of an opening bracket and the
4070 // selection is preceded by the rest of the opening bracket, then
4071 // insert the closing bracket.
4072 let following_text_allows_autoclose = snapshot
4073 .chars_at(selection.start)
4074 .next()
4075 .is_none_or(|c| scope.should_autoclose_before(c));
4076
4077 let preceding_text_allows_autoclose = selection.start.column == 0
4078 || snapshot
4079 .reversed_chars_at(selection.start)
4080 .next()
4081 .is_none_or(|c| {
4082 bracket_pair.start != bracket_pair.end
4083 || !snapshot
4084 .char_classifier_at(selection.start)
4085 .is_word(c)
4086 });
4087
4088 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4089 && bracket_pair.start.len() == 1
4090 {
4091 let target = bracket_pair.start.chars().next().unwrap();
4092 let current_line_count = snapshot
4093 .reversed_chars_at(selection.start)
4094 .take_while(|&c| c != '\n')
4095 .filter(|&c| c == target)
4096 .count();
4097 current_line_count % 2 == 1
4098 } else {
4099 false
4100 };
4101
4102 if autoclose
4103 && bracket_pair.close
4104 && following_text_allows_autoclose
4105 && preceding_text_allows_autoclose
4106 && !is_closing_quote
4107 {
4108 let anchor = snapshot.anchor_before(selection.end);
4109 new_selections.push((selection.map(|_| anchor), text.len()));
4110 new_autoclose_regions.push((
4111 anchor,
4112 text.len(),
4113 selection.id,
4114 bracket_pair.clone(),
4115 ));
4116 edits.push((
4117 selection.range(),
4118 format!("{}{}", text, bracket_pair.end).into(),
4119 ));
4120 bracket_inserted = true;
4121 continue;
4122 }
4123 }
4124
4125 if let Some(region) = autoclose_region {
4126 // If the selection is followed by an auto-inserted closing bracket,
4127 // then don't insert that closing bracket again; just move the selection
4128 // past the closing bracket.
4129 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4130 && text.as_ref() == region.pair.end.as_str()
4131 && snapshot.contains_str_at(region.range.end, text.as_ref());
4132 if should_skip {
4133 let anchor = snapshot.anchor_after(selection.end);
4134 new_selections
4135 .push((selection.map(|_| anchor), region.pair.end.len()));
4136 continue;
4137 }
4138 }
4139
4140 let always_treat_brackets_as_autoclosed = snapshot
4141 .language_settings_at(selection.start, cx)
4142 .always_treat_brackets_as_autoclosed;
4143 if always_treat_brackets_as_autoclosed
4144 && is_bracket_pair_end
4145 && snapshot.contains_str_at(selection.end, text.as_ref())
4146 {
4147 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4148 // and the inserted text is a closing bracket and the selection is followed
4149 // by the closing bracket then move the selection past the closing bracket.
4150 let anchor = snapshot.anchor_after(selection.end);
4151 new_selections.push((selection.map(|_| anchor), text.len()));
4152 continue;
4153 }
4154 }
4155 // If an opening bracket is 1 character long and is typed while
4156 // text is selected, then surround that text with the bracket pair.
4157 else if auto_surround
4158 && bracket_pair.surround
4159 && is_bracket_pair_start
4160 && bracket_pair.start.chars().count() == 1
4161 {
4162 edits.push((selection.start..selection.start, text.clone()));
4163 edits.push((
4164 selection.end..selection.end,
4165 bracket_pair.end.as_str().into(),
4166 ));
4167 bracket_inserted = true;
4168 new_selections.push((
4169 Selection {
4170 id: selection.id,
4171 start: snapshot.anchor_after(selection.start),
4172 end: snapshot.anchor_before(selection.end),
4173 reversed: selection.reversed,
4174 goal: selection.goal,
4175 },
4176 0,
4177 ));
4178 continue;
4179 }
4180 }
4181 }
4182
4183 if self.auto_replace_emoji_shortcode
4184 && selection.is_empty()
4185 && text.as_ref().ends_with(':')
4186 && let Some(possible_emoji_short_code) =
4187 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4188 && !possible_emoji_short_code.is_empty()
4189 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4190 {
4191 let emoji_shortcode_start = Point::new(
4192 selection.start.row,
4193 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4194 );
4195
4196 // Remove shortcode from buffer
4197 edits.push((
4198 emoji_shortcode_start..selection.start,
4199 "".to_string().into(),
4200 ));
4201 new_selections.push((
4202 Selection {
4203 id: selection.id,
4204 start: snapshot.anchor_after(emoji_shortcode_start),
4205 end: snapshot.anchor_before(selection.start),
4206 reversed: selection.reversed,
4207 goal: selection.goal,
4208 },
4209 0,
4210 ));
4211
4212 // Insert emoji
4213 let selection_start_anchor = snapshot.anchor_after(selection.start);
4214 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4215 edits.push((selection.start..selection.end, emoji.to_string().into()));
4216
4217 continue;
4218 }
4219
4220 // If not handling any auto-close operation, then just replace the selected
4221 // text with the given input and move the selection to the end of the
4222 // newly inserted text.
4223 let anchor = snapshot.anchor_after(selection.end);
4224 if !self.linked_edit_ranges.is_empty() {
4225 let start_anchor = snapshot.anchor_before(selection.start);
4226
4227 let is_word_char = text.chars().next().is_none_or(|char| {
4228 let classifier = snapshot
4229 .char_classifier_at(start_anchor.to_offset(&snapshot))
4230 .ignore_punctuation(true);
4231 classifier.is_word(char)
4232 });
4233
4234 if is_word_char {
4235 if let Some(ranges) = self
4236 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4237 {
4238 for (buffer, edits) in ranges {
4239 linked_edits
4240 .entry(buffer.clone())
4241 .or_default()
4242 .extend(edits.into_iter().map(|range| (range, text.clone())));
4243 }
4244 }
4245 } else {
4246 clear_linked_edit_ranges = true;
4247 }
4248 }
4249
4250 new_selections.push((selection.map(|_| anchor), 0));
4251 edits.push((selection.start..selection.end, text.clone()));
4252 }
4253
4254 drop(snapshot);
4255
4256 self.transact(window, cx, |this, window, cx| {
4257 if clear_linked_edit_ranges {
4258 this.linked_edit_ranges.clear();
4259 }
4260 let initial_buffer_versions =
4261 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4262
4263 this.buffer.update(cx, |buffer, cx| {
4264 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4265 });
4266 for (buffer, edits) in linked_edits {
4267 buffer.update(cx, |buffer, cx| {
4268 let snapshot = buffer.snapshot();
4269 let edits = edits
4270 .into_iter()
4271 .map(|(range, text)| {
4272 use text::ToPoint as TP;
4273 let end_point = TP::to_point(&range.end, &snapshot);
4274 let start_point = TP::to_point(&range.start, &snapshot);
4275 (start_point..end_point, text)
4276 })
4277 .sorted_by_key(|(range, _)| range.start);
4278 buffer.edit(edits, None, cx);
4279 })
4280 }
4281 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4282 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4283 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4284 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4285 .zip(new_selection_deltas)
4286 .map(|(selection, delta)| Selection {
4287 id: selection.id,
4288 start: selection.start + delta,
4289 end: selection.end + delta,
4290 reversed: selection.reversed,
4291 goal: SelectionGoal::None,
4292 })
4293 .collect::<Vec<_>>();
4294
4295 let mut i = 0;
4296 for (position, delta, selection_id, pair) in new_autoclose_regions {
4297 let position = position.to_offset(&map.buffer_snapshot) + delta;
4298 let start = map.buffer_snapshot.anchor_before(position);
4299 let end = map.buffer_snapshot.anchor_after(position);
4300 while let Some(existing_state) = this.autoclose_regions.get(i) {
4301 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4302 Ordering::Less => i += 1,
4303 Ordering::Greater => break,
4304 Ordering::Equal => {
4305 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4306 Ordering::Less => i += 1,
4307 Ordering::Equal => break,
4308 Ordering::Greater => break,
4309 }
4310 }
4311 }
4312 }
4313 this.autoclose_regions.insert(
4314 i,
4315 AutocloseRegion {
4316 selection_id,
4317 range: start..end,
4318 pair,
4319 },
4320 );
4321 }
4322
4323 let had_active_edit_prediction = this.has_active_edit_prediction();
4324 this.change_selections(
4325 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4326 window,
4327 cx,
4328 |s| s.select(new_selections),
4329 );
4330
4331 if !bracket_inserted
4332 && let Some(on_type_format_task) =
4333 this.trigger_on_type_formatting(text.to_string(), window, cx)
4334 {
4335 on_type_format_task.detach_and_log_err(cx);
4336 }
4337
4338 let editor_settings = EditorSettings::get_global(cx);
4339 if bracket_inserted
4340 && (editor_settings.auto_signature_help
4341 || editor_settings.show_signature_help_after_edits)
4342 {
4343 this.show_signature_help(&ShowSignatureHelp, window, cx);
4344 }
4345
4346 let trigger_in_words =
4347 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4348 if this.hard_wrap.is_some() {
4349 let latest: Range<Point> = this.selections.newest(cx).range();
4350 if latest.is_empty()
4351 && this
4352 .buffer()
4353 .read(cx)
4354 .snapshot(cx)
4355 .line_len(MultiBufferRow(latest.start.row))
4356 == latest.start.column
4357 {
4358 this.rewrap_impl(
4359 RewrapOptions {
4360 override_language_settings: true,
4361 preserve_existing_whitespace: true,
4362 },
4363 cx,
4364 )
4365 }
4366 }
4367 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4368 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4369 this.refresh_edit_prediction(true, false, window, cx);
4370 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4371 });
4372 }
4373
4374 fn find_possible_emoji_shortcode_at_position(
4375 snapshot: &MultiBufferSnapshot,
4376 position: Point,
4377 ) -> Option<String> {
4378 let mut chars = Vec::new();
4379 let mut found_colon = false;
4380 for char in snapshot.reversed_chars_at(position).take(100) {
4381 // Found a possible emoji shortcode in the middle of the buffer
4382 if found_colon {
4383 if char.is_whitespace() {
4384 chars.reverse();
4385 return Some(chars.iter().collect());
4386 }
4387 // If the previous character is not a whitespace, we are in the middle of a word
4388 // and we only want to complete the shortcode if the word is made up of other emojis
4389 let mut containing_word = String::new();
4390 for ch in snapshot
4391 .reversed_chars_at(position)
4392 .skip(chars.len() + 1)
4393 .take(100)
4394 {
4395 if ch.is_whitespace() {
4396 break;
4397 }
4398 containing_word.push(ch);
4399 }
4400 let containing_word = containing_word.chars().rev().collect::<String>();
4401 if util::word_consists_of_emojis(containing_word.as_str()) {
4402 chars.reverse();
4403 return Some(chars.iter().collect());
4404 }
4405 }
4406
4407 if char.is_whitespace() || !char.is_ascii() {
4408 return None;
4409 }
4410 if char == ':' {
4411 found_colon = true;
4412 } else {
4413 chars.push(char);
4414 }
4415 }
4416 // Found a possible emoji shortcode at the beginning of the buffer
4417 chars.reverse();
4418 Some(chars.iter().collect())
4419 }
4420
4421 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4423 self.transact(window, cx, |this, window, cx| {
4424 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4425 let selections = this.selections.all::<usize>(cx);
4426 let multi_buffer = this.buffer.read(cx);
4427 let buffer = multi_buffer.snapshot(cx);
4428 selections
4429 .iter()
4430 .map(|selection| {
4431 let start_point = selection.start.to_point(&buffer);
4432 let mut existing_indent =
4433 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4434 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4435 let start = selection.start;
4436 let end = selection.end;
4437 let selection_is_empty = start == end;
4438 let language_scope = buffer.language_scope_at(start);
4439 let (
4440 comment_delimiter,
4441 doc_delimiter,
4442 insert_extra_newline,
4443 indent_on_newline,
4444 indent_on_extra_newline,
4445 ) = if let Some(language) = &language_scope {
4446 let mut insert_extra_newline =
4447 insert_extra_newline_brackets(&buffer, start..end, language)
4448 || insert_extra_newline_tree_sitter(&buffer, start..end);
4449
4450 // Comment extension on newline is allowed only for cursor selections
4451 let comment_delimiter = maybe!({
4452 if !selection_is_empty {
4453 return None;
4454 }
4455
4456 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4457 return None;
4458 }
4459
4460 let delimiters = language.line_comment_prefixes();
4461 let max_len_of_delimiter =
4462 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4463 let (snapshot, range) =
4464 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4465
4466 let num_of_whitespaces = snapshot
4467 .chars_for_range(range.clone())
4468 .take_while(|c| c.is_whitespace())
4469 .count();
4470 let comment_candidate = snapshot
4471 .chars_for_range(range.clone())
4472 .skip(num_of_whitespaces)
4473 .take(max_len_of_delimiter)
4474 .collect::<String>();
4475 let (delimiter, trimmed_len) = delimiters
4476 .iter()
4477 .filter_map(|delimiter| {
4478 let prefix = delimiter.trim_end();
4479 if comment_candidate.starts_with(prefix) {
4480 Some((delimiter, prefix.len()))
4481 } else {
4482 None
4483 }
4484 })
4485 .max_by_key(|(_, len)| *len)?;
4486
4487 if let Some(BlockCommentConfig {
4488 start: block_start, ..
4489 }) = language.block_comment()
4490 {
4491 let block_start_trimmed = block_start.trim_end();
4492 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4493 let line_content = snapshot
4494 .chars_for_range(range)
4495 .skip(num_of_whitespaces)
4496 .take(block_start_trimmed.len())
4497 .collect::<String>();
4498
4499 if line_content.starts_with(block_start_trimmed) {
4500 return None;
4501 }
4502 }
4503 }
4504
4505 let cursor_is_placed_after_comment_marker =
4506 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4507 if cursor_is_placed_after_comment_marker {
4508 Some(delimiter.clone())
4509 } else {
4510 None
4511 }
4512 });
4513
4514 let mut indent_on_newline = IndentSize::spaces(0);
4515 let mut indent_on_extra_newline = IndentSize::spaces(0);
4516
4517 let doc_delimiter = maybe!({
4518 if !selection_is_empty {
4519 return None;
4520 }
4521
4522 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4523 return None;
4524 }
4525
4526 let BlockCommentConfig {
4527 start: start_tag,
4528 end: end_tag,
4529 prefix: delimiter,
4530 tab_size: len,
4531 } = language.documentation_comment()?;
4532 let is_within_block_comment = buffer
4533 .language_scope_at(start_point)
4534 .is_some_and(|scope| scope.override_name() == Some("comment"));
4535 if !is_within_block_comment {
4536 return None;
4537 }
4538
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546
4547 // 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.
4548 let column = start_point.column;
4549 let cursor_is_after_start_tag = {
4550 let start_tag_len = start_tag.len();
4551 let start_tag_line = snapshot
4552 .chars_for_range(range.clone())
4553 .skip(num_of_whitespaces)
4554 .take(start_tag_len)
4555 .collect::<String>();
4556 if start_tag_line.starts_with(start_tag.as_ref()) {
4557 num_of_whitespaces + start_tag_len <= column as usize
4558 } else {
4559 false
4560 }
4561 };
4562
4563 let cursor_is_after_delimiter = {
4564 let delimiter_trim = delimiter.trim_end();
4565 let delimiter_line = snapshot
4566 .chars_for_range(range.clone())
4567 .skip(num_of_whitespaces)
4568 .take(delimiter_trim.len())
4569 .collect::<String>();
4570 if delimiter_line.starts_with(delimiter_trim) {
4571 num_of_whitespaces + delimiter_trim.len() <= column as usize
4572 } else {
4573 false
4574 }
4575 };
4576
4577 let cursor_is_before_end_tag_if_exists = {
4578 let mut char_position = 0u32;
4579 let mut end_tag_offset = None;
4580
4581 'outer: for chunk in snapshot.text_for_range(range) {
4582 if let Some(byte_pos) = chunk.find(&**end_tag) {
4583 let chars_before_match =
4584 chunk[..byte_pos].chars().count() as u32;
4585 end_tag_offset =
4586 Some(char_position + chars_before_match);
4587 break 'outer;
4588 }
4589 char_position += chunk.chars().count() as u32;
4590 }
4591
4592 if let Some(end_tag_offset) = end_tag_offset {
4593 let cursor_is_before_end_tag = column <= end_tag_offset;
4594 if cursor_is_after_start_tag {
4595 if cursor_is_before_end_tag {
4596 insert_extra_newline = true;
4597 }
4598 let cursor_is_at_start_of_end_tag =
4599 column == end_tag_offset;
4600 if cursor_is_at_start_of_end_tag {
4601 indent_on_extra_newline.len = *len;
4602 }
4603 }
4604 cursor_is_before_end_tag
4605 } else {
4606 true
4607 }
4608 };
4609
4610 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4611 && cursor_is_before_end_tag_if_exists
4612 {
4613 if cursor_is_after_start_tag {
4614 indent_on_newline.len = *len;
4615 }
4616 Some(delimiter.clone())
4617 } else {
4618 None
4619 }
4620 });
4621
4622 (
4623 comment_delimiter,
4624 doc_delimiter,
4625 insert_extra_newline,
4626 indent_on_newline,
4627 indent_on_extra_newline,
4628 )
4629 } else {
4630 (
4631 None,
4632 None,
4633 false,
4634 IndentSize::default(),
4635 IndentSize::default(),
4636 )
4637 };
4638
4639 let prevent_auto_indent = doc_delimiter.is_some();
4640 let delimiter = comment_delimiter.or(doc_delimiter);
4641
4642 let capacity_for_delimiter =
4643 delimiter.as_deref().map(str::len).unwrap_or_default();
4644 let mut new_text = String::with_capacity(
4645 1 + capacity_for_delimiter
4646 + existing_indent.len as usize
4647 + indent_on_newline.len as usize
4648 + indent_on_extra_newline.len as usize,
4649 );
4650 new_text.push('\n');
4651 new_text.extend(existing_indent.chars());
4652 new_text.extend(indent_on_newline.chars());
4653
4654 if let Some(delimiter) = &delimiter {
4655 new_text.push_str(delimiter);
4656 }
4657
4658 if insert_extra_newline {
4659 new_text.push('\n');
4660 new_text.extend(existing_indent.chars());
4661 new_text.extend(indent_on_extra_newline.chars());
4662 }
4663
4664 let anchor = buffer.anchor_after(end);
4665 let new_selection = selection.map(|_| anchor);
4666 (
4667 ((start..end, new_text), prevent_auto_indent),
4668 (insert_extra_newline, new_selection),
4669 )
4670 })
4671 .unzip()
4672 };
4673
4674 let mut auto_indent_edits = Vec::new();
4675 let mut edits = Vec::new();
4676 for (edit, prevent_auto_indent) in edits_with_flags {
4677 if prevent_auto_indent {
4678 edits.push(edit);
4679 } else {
4680 auto_indent_edits.push(edit);
4681 }
4682 }
4683 if !edits.is_empty() {
4684 this.edit(edits, cx);
4685 }
4686 if !auto_indent_edits.is_empty() {
4687 this.edit_with_autoindent(auto_indent_edits, cx);
4688 }
4689
4690 let buffer = this.buffer.read(cx).snapshot(cx);
4691 let new_selections = selection_info
4692 .into_iter()
4693 .map(|(extra_newline_inserted, new_selection)| {
4694 let mut cursor = new_selection.end.to_point(&buffer);
4695 if extra_newline_inserted {
4696 cursor.row -= 1;
4697 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4698 }
4699 new_selection.map(|_| cursor)
4700 })
4701 .collect();
4702
4703 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4704 this.refresh_edit_prediction(true, false, window, cx);
4705 });
4706 }
4707
4708 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4710
4711 let buffer = self.buffer.read(cx);
4712 let snapshot = buffer.snapshot(cx);
4713
4714 let mut edits = Vec::new();
4715 let mut rows = Vec::new();
4716
4717 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4718 let cursor = selection.head();
4719 let row = cursor.row;
4720
4721 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4722
4723 let newline = "\n".to_string();
4724 edits.push((start_of_line..start_of_line, newline));
4725
4726 rows.push(row + rows_inserted as u32);
4727 }
4728
4729 self.transact(window, cx, |editor, window, cx| {
4730 editor.edit(edits, cx);
4731
4732 editor.change_selections(Default::default(), window, cx, |s| {
4733 let mut index = 0;
4734 s.move_cursors_with(|map, _, _| {
4735 let row = rows[index];
4736 index += 1;
4737
4738 let point = Point::new(row, 0);
4739 let boundary = map.next_line_boundary(point).1;
4740 let clipped = map.clip_point(boundary, Bias::Left);
4741
4742 (clipped, SelectionGoal::None)
4743 });
4744 });
4745
4746 let mut indent_edits = Vec::new();
4747 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4748 for row in rows {
4749 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4750 for (row, indent) in indents {
4751 if indent.len == 0 {
4752 continue;
4753 }
4754
4755 let text = match indent.kind {
4756 IndentKind::Space => " ".repeat(indent.len as usize),
4757 IndentKind::Tab => "\t".repeat(indent.len as usize),
4758 };
4759 let point = Point::new(row.0, 0);
4760 indent_edits.push((point..point, text));
4761 }
4762 }
4763 editor.edit(indent_edits, cx);
4764 });
4765 }
4766
4767 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4769
4770 let buffer = self.buffer.read(cx);
4771 let snapshot = buffer.snapshot(cx);
4772
4773 let mut edits = Vec::new();
4774 let mut rows = Vec::new();
4775 let mut rows_inserted = 0;
4776
4777 for selection in self.selections.all_adjusted(cx) {
4778 let cursor = selection.head();
4779 let row = cursor.row;
4780
4781 let point = Point::new(row + 1, 0);
4782 let start_of_line = snapshot.clip_point(point, Bias::Left);
4783
4784 let newline = "\n".to_string();
4785 edits.push((start_of_line..start_of_line, newline));
4786
4787 rows_inserted += 1;
4788 rows.push(row + rows_inserted);
4789 }
4790
4791 self.transact(window, cx, |editor, window, cx| {
4792 editor.edit(edits, cx);
4793
4794 editor.change_selections(Default::default(), window, cx, |s| {
4795 let mut index = 0;
4796 s.move_cursors_with(|map, _, _| {
4797 let row = rows[index];
4798 index += 1;
4799
4800 let point = Point::new(row, 0);
4801 let boundary = map.next_line_boundary(point).1;
4802 let clipped = map.clip_point(boundary, Bias::Left);
4803
4804 (clipped, SelectionGoal::None)
4805 });
4806 });
4807
4808 let mut indent_edits = Vec::new();
4809 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4810 for row in rows {
4811 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4812 for (row, indent) in indents {
4813 if indent.len == 0 {
4814 continue;
4815 }
4816
4817 let text = match indent.kind {
4818 IndentKind::Space => " ".repeat(indent.len as usize),
4819 IndentKind::Tab => "\t".repeat(indent.len as usize),
4820 };
4821 let point = Point::new(row.0, 0);
4822 indent_edits.push((point..point, text));
4823 }
4824 }
4825 editor.edit(indent_edits, cx);
4826 });
4827 }
4828
4829 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4830 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4831 original_indent_columns: Vec::new(),
4832 });
4833 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4834 }
4835
4836 fn insert_with_autoindent_mode(
4837 &mut self,
4838 text: &str,
4839 autoindent_mode: Option<AutoindentMode>,
4840 window: &mut Window,
4841 cx: &mut Context<Self>,
4842 ) {
4843 if self.read_only(cx) {
4844 return;
4845 }
4846
4847 let text: Arc<str> = text.into();
4848 self.transact(window, cx, |this, window, cx| {
4849 let old_selections = this.selections.all_adjusted(cx);
4850 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4851 let anchors = {
4852 let snapshot = buffer.read(cx);
4853 old_selections
4854 .iter()
4855 .map(|s| {
4856 let anchor = snapshot.anchor_after(s.head());
4857 s.map(|_| anchor)
4858 })
4859 .collect::<Vec<_>>()
4860 };
4861 buffer.edit(
4862 old_selections
4863 .iter()
4864 .map(|s| (s.start..s.end, text.clone())),
4865 autoindent_mode,
4866 cx,
4867 );
4868 anchors
4869 });
4870
4871 this.change_selections(Default::default(), window, cx, |s| {
4872 s.select_anchors(selection_anchors);
4873 });
4874
4875 cx.notify();
4876 });
4877 }
4878
4879 fn trigger_completion_on_input(
4880 &mut self,
4881 text: &str,
4882 trigger_in_words: bool,
4883 window: &mut Window,
4884 cx: &mut Context<Self>,
4885 ) {
4886 let completions_source = self
4887 .context_menu
4888 .borrow()
4889 .as_ref()
4890 .and_then(|menu| match menu {
4891 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4892 CodeContextMenu::CodeActions(_) => None,
4893 });
4894
4895 match completions_source {
4896 Some(CompletionsMenuSource::Words) => {
4897 self.show_word_completions(&ShowWordCompletions, window, cx)
4898 }
4899 Some(CompletionsMenuSource::Normal)
4900 | Some(CompletionsMenuSource::SnippetChoices)
4901 | None
4902 if self.is_completion_trigger(
4903 text,
4904 trigger_in_words,
4905 completions_source.is_some(),
4906 cx,
4907 ) =>
4908 {
4909 self.show_completions(
4910 &ShowCompletions {
4911 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4912 },
4913 window,
4914 cx,
4915 )
4916 }
4917 _ => {
4918 self.hide_context_menu(window, cx);
4919 }
4920 }
4921 }
4922
4923 fn is_completion_trigger(
4924 &self,
4925 text: &str,
4926 trigger_in_words: bool,
4927 menu_is_open: bool,
4928 cx: &mut Context<Self>,
4929 ) -> bool {
4930 let position = self.selections.newest_anchor().head();
4931 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4932 return false;
4933 };
4934
4935 if let Some(completion_provider) = &self.completion_provider {
4936 completion_provider.is_completion_trigger(
4937 &buffer,
4938 position.text_anchor,
4939 text,
4940 trigger_in_words,
4941 menu_is_open,
4942 cx,
4943 )
4944 } else {
4945 false
4946 }
4947 }
4948
4949 /// If any empty selections is touching the start of its innermost containing autoclose
4950 /// region, expand it to select the brackets.
4951 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4952 let selections = self.selections.all::<usize>(cx);
4953 let buffer = self.buffer.read(cx).read(cx);
4954 let new_selections = self
4955 .selections_with_autoclose_regions(selections, &buffer)
4956 .map(|(mut selection, region)| {
4957 if !selection.is_empty() {
4958 return selection;
4959 }
4960
4961 if let Some(region) = region {
4962 let mut range = region.range.to_offset(&buffer);
4963 if selection.start == range.start && range.start >= region.pair.start.len() {
4964 range.start -= region.pair.start.len();
4965 if buffer.contains_str_at(range.start, ®ion.pair.start)
4966 && buffer.contains_str_at(range.end, ®ion.pair.end)
4967 {
4968 range.end += region.pair.end.len();
4969 selection.start = range.start;
4970 selection.end = range.end;
4971
4972 return selection;
4973 }
4974 }
4975 }
4976
4977 let always_treat_brackets_as_autoclosed = buffer
4978 .language_settings_at(selection.start, cx)
4979 .always_treat_brackets_as_autoclosed;
4980
4981 if !always_treat_brackets_as_autoclosed {
4982 return selection;
4983 }
4984
4985 if let Some(scope) = buffer.language_scope_at(selection.start) {
4986 for (pair, enabled) in scope.brackets() {
4987 if !enabled || !pair.close {
4988 continue;
4989 }
4990
4991 if buffer.contains_str_at(selection.start, &pair.end) {
4992 let pair_start_len = pair.start.len();
4993 if buffer.contains_str_at(
4994 selection.start.saturating_sub(pair_start_len),
4995 &pair.start,
4996 ) {
4997 selection.start -= pair_start_len;
4998 selection.end += pair.end.len();
4999
5000 return selection;
5001 }
5002 }
5003 }
5004 }
5005
5006 selection
5007 })
5008 .collect();
5009
5010 drop(buffer);
5011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5012 selections.select(new_selections)
5013 });
5014 }
5015
5016 /// Iterate the given selections, and for each one, find the smallest surrounding
5017 /// autoclose region. This uses the ordering of the selections and the autoclose
5018 /// regions to avoid repeated comparisons.
5019 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5020 &'a self,
5021 selections: impl IntoIterator<Item = Selection<D>>,
5022 buffer: &'a MultiBufferSnapshot,
5023 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5024 let mut i = 0;
5025 let mut regions = self.autoclose_regions.as_slice();
5026 selections.into_iter().map(move |selection| {
5027 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5028
5029 let mut enclosing = None;
5030 while let Some(pair_state) = regions.get(i) {
5031 if pair_state.range.end.to_offset(buffer) < range.start {
5032 regions = ®ions[i + 1..];
5033 i = 0;
5034 } else if pair_state.range.start.to_offset(buffer) > range.end {
5035 break;
5036 } else {
5037 if pair_state.selection_id == selection.id {
5038 enclosing = Some(pair_state);
5039 }
5040 i += 1;
5041 }
5042 }
5043
5044 (selection, enclosing)
5045 })
5046 }
5047
5048 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5049 fn invalidate_autoclose_regions(
5050 &mut self,
5051 mut selections: &[Selection<Anchor>],
5052 buffer: &MultiBufferSnapshot,
5053 ) {
5054 self.autoclose_regions.retain(|state| {
5055 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5056 return false;
5057 }
5058
5059 let mut i = 0;
5060 while let Some(selection) = selections.get(i) {
5061 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5062 selections = &selections[1..];
5063 continue;
5064 }
5065 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5066 break;
5067 }
5068 if selection.id == state.selection_id {
5069 return true;
5070 } else {
5071 i += 1;
5072 }
5073 }
5074 false
5075 });
5076 }
5077
5078 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5079 let offset = position.to_offset(buffer);
5080 let (word_range, kind) = buffer.surrounding_word(offset, true);
5081 if offset > word_range.start && kind == Some(CharKind::Word) {
5082 Some(
5083 buffer
5084 .text_for_range(word_range.start..offset)
5085 .collect::<String>(),
5086 )
5087 } else {
5088 None
5089 }
5090 }
5091
5092 pub fn toggle_inline_values(
5093 &mut self,
5094 _: &ToggleInlineValues,
5095 _: &mut Window,
5096 cx: &mut Context<Self>,
5097 ) {
5098 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5099
5100 self.refresh_inline_values(cx);
5101 }
5102
5103 pub fn toggle_inlay_hints(
5104 &mut self,
5105 _: &ToggleInlayHints,
5106 _: &mut Window,
5107 cx: &mut Context<Self>,
5108 ) {
5109 self.refresh_inlay_hints(
5110 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5111 cx,
5112 );
5113 }
5114
5115 pub fn inlay_hints_enabled(&self) -> bool {
5116 self.inlay_hint_cache.enabled
5117 }
5118
5119 pub fn inline_values_enabled(&self) -> bool {
5120 self.inline_value_cache.enabled
5121 }
5122
5123 #[cfg(any(test, feature = "test-support"))]
5124 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5125 self.display_map
5126 .read(cx)
5127 .current_inlays()
5128 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5129 .cloned()
5130 .collect()
5131 }
5132
5133 #[cfg(any(test, feature = "test-support"))]
5134 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5135 self.display_map
5136 .read(cx)
5137 .current_inlays()
5138 .cloned()
5139 .collect()
5140 }
5141
5142 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5143 if self.semantics_provider.is_none() || !self.mode.is_full() {
5144 return;
5145 }
5146
5147 let reason_description = reason.description();
5148 let ignore_debounce = matches!(
5149 reason,
5150 InlayHintRefreshReason::SettingsChange(_)
5151 | InlayHintRefreshReason::Toggle(_)
5152 | InlayHintRefreshReason::ExcerptsRemoved(_)
5153 | InlayHintRefreshReason::ModifiersChanged(_)
5154 );
5155 let (invalidate_cache, required_languages) = match reason {
5156 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5157 match self.inlay_hint_cache.modifiers_override(enabled) {
5158 Some(enabled) => {
5159 if enabled {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 } else {
5162 self.splice_inlays(
5163 &self
5164 .visible_inlay_hints(cx)
5165 .iter()
5166 .map(|inlay| inlay.id)
5167 .collect::<Vec<InlayId>>(),
5168 Vec::new(),
5169 cx,
5170 );
5171 return;
5172 }
5173 }
5174 None => return,
5175 }
5176 }
5177 InlayHintRefreshReason::Toggle(enabled) => {
5178 if self.inlay_hint_cache.toggle(enabled) {
5179 if enabled {
5180 (InvalidationStrategy::RefreshRequested, None)
5181 } else {
5182 self.splice_inlays(
5183 &self
5184 .visible_inlay_hints(cx)
5185 .iter()
5186 .map(|inlay| inlay.id)
5187 .collect::<Vec<InlayId>>(),
5188 Vec::new(),
5189 cx,
5190 );
5191 return;
5192 }
5193 } else {
5194 return;
5195 }
5196 }
5197 InlayHintRefreshReason::SettingsChange(new_settings) => {
5198 match self.inlay_hint_cache.update_settings(
5199 &self.buffer,
5200 new_settings,
5201 self.visible_inlay_hints(cx),
5202 cx,
5203 ) {
5204 ControlFlow::Break(Some(InlaySplice {
5205 to_remove,
5206 to_insert,
5207 })) => {
5208 self.splice_inlays(&to_remove, to_insert, cx);
5209 return;
5210 }
5211 ControlFlow::Break(None) => return,
5212 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5213 }
5214 }
5215 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5216 if let Some(InlaySplice {
5217 to_remove,
5218 to_insert,
5219 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5220 {
5221 self.splice_inlays(&to_remove, to_insert, cx);
5222 }
5223 self.display_map.update(cx, |display_map, _| {
5224 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5225 });
5226 return;
5227 }
5228 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5229 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5230 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5231 }
5232 InlayHintRefreshReason::RefreshRequested => {
5233 (InvalidationStrategy::RefreshRequested, None)
5234 }
5235 };
5236
5237 if let Some(InlaySplice {
5238 to_remove,
5239 to_insert,
5240 }) = self.inlay_hint_cache.spawn_hint_refresh(
5241 reason_description,
5242 self.visible_excerpts(required_languages.as_ref(), cx),
5243 invalidate_cache,
5244 ignore_debounce,
5245 cx,
5246 ) {
5247 self.splice_inlays(&to_remove, to_insert, cx);
5248 }
5249 }
5250
5251 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5252 self.display_map
5253 .read(cx)
5254 .current_inlays()
5255 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5256 .cloned()
5257 .collect()
5258 }
5259
5260 pub fn visible_excerpts(
5261 &self,
5262 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5263 cx: &mut Context<Editor>,
5264 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5265 let Some(project) = self.project() else {
5266 return HashMap::default();
5267 };
5268 let project = project.read(cx);
5269 let multi_buffer = self.buffer().read(cx);
5270 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5271 let multi_buffer_visible_start = self
5272 .scroll_manager
5273 .anchor()
5274 .anchor
5275 .to_point(&multi_buffer_snapshot);
5276 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5277 multi_buffer_visible_start
5278 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5279 Bias::Left,
5280 );
5281 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5282 multi_buffer_snapshot
5283 .range_to_buffer_ranges(multi_buffer_visible_range)
5284 .into_iter()
5285 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5286 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5287 let buffer_file = project::File::from_dyn(buffer.file())?;
5288 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5289 let worktree_entry = buffer_worktree
5290 .read(cx)
5291 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5292 if worktree_entry.is_ignored {
5293 return None;
5294 }
5295
5296 let language = buffer.language()?;
5297 if let Some(restrict_to_languages) = restrict_to_languages
5298 && !restrict_to_languages.contains(language)
5299 {
5300 return None;
5301 }
5302 Some((
5303 excerpt_id,
5304 (
5305 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5306 buffer.version().clone(),
5307 excerpt_visible_range,
5308 ),
5309 ))
5310 })
5311 .collect()
5312 }
5313
5314 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5315 TextLayoutDetails {
5316 text_system: window.text_system().clone(),
5317 editor_style: self.style.clone().unwrap(),
5318 rem_size: window.rem_size(),
5319 scroll_anchor: self.scroll_manager.anchor(),
5320 visible_rows: self.visible_line_count(),
5321 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5322 }
5323 }
5324
5325 pub fn splice_inlays(
5326 &self,
5327 to_remove: &[InlayId],
5328 to_insert: Vec<Inlay>,
5329 cx: &mut Context<Self>,
5330 ) {
5331 self.display_map.update(cx, |display_map, cx| {
5332 display_map.splice_inlays(to_remove, to_insert, cx)
5333 });
5334 cx.notify();
5335 }
5336
5337 fn trigger_on_type_formatting(
5338 &self,
5339 input: String,
5340 window: &mut Window,
5341 cx: &mut Context<Self>,
5342 ) -> Option<Task<Result<()>>> {
5343 if input.len() != 1 {
5344 return None;
5345 }
5346
5347 let project = self.project()?;
5348 let position = self.selections.newest_anchor().head();
5349 let (buffer, buffer_position) = self
5350 .buffer
5351 .read(cx)
5352 .text_anchor_for_position(position, cx)?;
5353
5354 let settings = language_settings::language_settings(
5355 buffer
5356 .read(cx)
5357 .language_at(buffer_position)
5358 .map(|l| l.name()),
5359 buffer.read(cx).file(),
5360 cx,
5361 );
5362 if !settings.use_on_type_format {
5363 return None;
5364 }
5365
5366 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5367 // hence we do LSP request & edit on host side only — add formats to host's history.
5368 let push_to_lsp_host_history = true;
5369 // If this is not the host, append its history with new edits.
5370 let push_to_client_history = project.read(cx).is_via_collab();
5371
5372 let on_type_formatting = project.update(cx, |project, cx| {
5373 project.on_type_format(
5374 buffer.clone(),
5375 buffer_position,
5376 input,
5377 push_to_lsp_host_history,
5378 cx,
5379 )
5380 });
5381 Some(cx.spawn_in(window, async move |editor, cx| {
5382 if let Some(transaction) = on_type_formatting.await? {
5383 if push_to_client_history {
5384 buffer
5385 .update(cx, |buffer, _| {
5386 buffer.push_transaction(transaction, Instant::now());
5387 buffer.finalize_last_transaction();
5388 })
5389 .ok();
5390 }
5391 editor.update(cx, |editor, cx| {
5392 editor.refresh_document_highlights(cx);
5393 })?;
5394 }
5395 Ok(())
5396 }))
5397 }
5398
5399 pub fn show_word_completions(
5400 &mut self,
5401 _: &ShowWordCompletions,
5402 window: &mut Window,
5403 cx: &mut Context<Self>,
5404 ) {
5405 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5406 }
5407
5408 pub fn show_completions(
5409 &mut self,
5410 options: &ShowCompletions,
5411 window: &mut Window,
5412 cx: &mut Context<Self>,
5413 ) {
5414 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5415 }
5416
5417 fn open_or_update_completions_menu(
5418 &mut self,
5419 requested_source: Option<CompletionsMenuSource>,
5420 trigger: Option<&str>,
5421 window: &mut Window,
5422 cx: &mut Context<Self>,
5423 ) {
5424 if self.pending_rename.is_some() {
5425 return;
5426 }
5427
5428 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5429
5430 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5431 // inserted and selected. To handle that case, the start of the selection is used so that
5432 // the menu starts with all choices.
5433 let position = self
5434 .selections
5435 .newest_anchor()
5436 .start
5437 .bias_right(&multibuffer_snapshot);
5438 if position.diff_base_anchor.is_some() {
5439 return;
5440 }
5441 let (buffer, buffer_position) =
5442 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5443 output
5444 } else {
5445 return;
5446 };
5447 let buffer_snapshot = buffer.read(cx).snapshot();
5448
5449 let query: Option<Arc<String>> =
5450 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5451
5452 drop(multibuffer_snapshot);
5453
5454 let provider = match requested_source {
5455 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5456 Some(CompletionsMenuSource::Words) => None,
5457 Some(CompletionsMenuSource::SnippetChoices) => {
5458 log::error!("bug: SnippetChoices requested_source is not handled");
5459 None
5460 }
5461 };
5462
5463 let sort_completions = provider
5464 .as_ref()
5465 .is_some_and(|provider| provider.sort_completions());
5466
5467 let filter_completions = provider
5468 .as_ref()
5469 .is_none_or(|provider| provider.filter_completions());
5470
5471 let trigger_kind = match trigger {
5472 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5473 CompletionTriggerKind::TRIGGER_CHARACTER
5474 }
5475 _ => CompletionTriggerKind::INVOKED,
5476 };
5477 let completion_context = CompletionContext {
5478 trigger_character: trigger.and_then(|trigger| {
5479 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5480 Some(String::from(trigger))
5481 } else {
5482 None
5483 }
5484 }),
5485 trigger_kind,
5486 };
5487
5488 // Hide the current completions menu when a trigger char is typed. Without this, cached
5489 // completions from before the trigger char may be reused (#32774). Snippet choices could
5490 // involve trigger chars, so this is skipped in that case.
5491 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5492 {
5493 let menu_is_open = matches!(
5494 self.context_menu.borrow().as_ref(),
5495 Some(CodeContextMenu::Completions(_))
5496 );
5497 if menu_is_open {
5498 self.hide_context_menu(window, cx);
5499 }
5500 }
5501
5502 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5503 if filter_completions {
5504 menu.filter(query.clone(), provider.clone(), window, cx);
5505 }
5506 // When `is_incomplete` is false, no need to re-query completions when the current query
5507 // is a suffix of the initial query.
5508 if !menu.is_incomplete {
5509 // If the new query is a suffix of the old query (typing more characters) and
5510 // the previous result was complete, the existing completions can be filtered.
5511 //
5512 // Note that this is always true for snippet completions.
5513 let query_matches = match (&menu.initial_query, &query) {
5514 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5515 (None, _) => true,
5516 _ => false,
5517 };
5518 if query_matches {
5519 let position_matches = if menu.initial_position == position {
5520 true
5521 } else {
5522 let snapshot = self.buffer.read(cx).read(cx);
5523 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5524 };
5525 if position_matches {
5526 return;
5527 }
5528 }
5529 }
5530 };
5531
5532 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5533 buffer_snapshot.surrounding_word(buffer_position, false)
5534 {
5535 let word_to_exclude = buffer_snapshot
5536 .text_for_range(word_range.clone())
5537 .collect::<String>();
5538 (
5539 buffer_snapshot.anchor_before(word_range.start)
5540 ..buffer_snapshot.anchor_after(buffer_position),
5541 Some(word_to_exclude),
5542 )
5543 } else {
5544 (buffer_position..buffer_position, None)
5545 };
5546
5547 let language = buffer_snapshot
5548 .language_at(buffer_position)
5549 .map(|language| language.name());
5550
5551 let completion_settings =
5552 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5553
5554 let show_completion_documentation = buffer_snapshot
5555 .settings_at(buffer_position, cx)
5556 .show_completion_documentation;
5557
5558 // The document can be large, so stay in reasonable bounds when searching for words,
5559 // otherwise completion pop-up might be slow to appear.
5560 const WORD_LOOKUP_ROWS: u32 = 5_000;
5561 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5562 let min_word_search = buffer_snapshot.clip_point(
5563 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5564 Bias::Left,
5565 );
5566 let max_word_search = buffer_snapshot.clip_point(
5567 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5568 Bias::Right,
5569 );
5570 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5571 ..buffer_snapshot.point_to_offset(max_word_search);
5572
5573 let skip_digits = query
5574 .as_ref()
5575 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5576
5577 let omit_word_completions = match &query {
5578 Some(query) => query.chars().count() < completion_settings.words_min_length,
5579 None => completion_settings.words_min_length != 0,
5580 };
5581
5582 let (mut words, provider_responses) = match &provider {
5583 Some(provider) => {
5584 let provider_responses = provider.completions(
5585 position.excerpt_id,
5586 &buffer,
5587 buffer_position,
5588 completion_context,
5589 window,
5590 cx,
5591 );
5592
5593 let words = match (omit_word_completions, completion_settings.words) {
5594 (true, _) | (_, WordsCompletionMode::Disabled) => {
5595 Task::ready(BTreeMap::default())
5596 }
5597 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5598 .background_spawn(async move {
5599 buffer_snapshot.words_in_range(WordsQuery {
5600 fuzzy_contents: None,
5601 range: word_search_range,
5602 skip_digits,
5603 })
5604 }),
5605 };
5606
5607 (words, provider_responses)
5608 }
5609 None => {
5610 let words = if omit_word_completions {
5611 Task::ready(BTreeMap::default())
5612 } else {
5613 cx.background_spawn(async move {
5614 buffer_snapshot.words_in_range(WordsQuery {
5615 fuzzy_contents: None,
5616 range: word_search_range,
5617 skip_digits,
5618 })
5619 })
5620 };
5621 (words, Task::ready(Ok(Vec::new())))
5622 }
5623 };
5624
5625 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5626
5627 let id = post_inc(&mut self.next_completion_id);
5628 let task = cx.spawn_in(window, async move |editor, cx| {
5629 let Ok(()) = editor.update(cx, |this, _| {
5630 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5631 }) else {
5632 return;
5633 };
5634
5635 // TODO: Ideally completions from different sources would be selectively re-queried, so
5636 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5637 let mut completions = Vec::new();
5638 let mut is_incomplete = false;
5639 let mut display_options: Option<CompletionDisplayOptions> = None;
5640 if let Some(provider_responses) = provider_responses.await.log_err()
5641 && !provider_responses.is_empty()
5642 {
5643 for response in provider_responses {
5644 completions.extend(response.completions);
5645 is_incomplete = is_incomplete || response.is_incomplete;
5646 match display_options.as_mut() {
5647 None => {
5648 display_options = Some(response.display_options);
5649 }
5650 Some(options) => options.merge(&response.display_options),
5651 }
5652 }
5653 if completion_settings.words == WordsCompletionMode::Fallback {
5654 words = Task::ready(BTreeMap::default());
5655 }
5656 }
5657 let display_options = display_options.unwrap_or_default();
5658
5659 let mut words = words.await;
5660 if let Some(word_to_exclude) = &word_to_exclude {
5661 words.remove(word_to_exclude);
5662 }
5663 for lsp_completion in &completions {
5664 words.remove(&lsp_completion.new_text);
5665 }
5666 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5667 replace_range: word_replace_range.clone(),
5668 new_text: word.clone(),
5669 label: CodeLabel::plain(word, None),
5670 icon_path: None,
5671 documentation: None,
5672 source: CompletionSource::BufferWord {
5673 word_range,
5674 resolved: false,
5675 },
5676 insert_text_mode: Some(InsertTextMode::AS_IS),
5677 confirm: None,
5678 }));
5679
5680 let menu = if completions.is_empty() {
5681 None
5682 } else {
5683 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5684 let languages = editor
5685 .workspace
5686 .as_ref()
5687 .and_then(|(workspace, _)| workspace.upgrade())
5688 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5689 let menu = CompletionsMenu::new(
5690 id,
5691 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5692 sort_completions,
5693 show_completion_documentation,
5694 position,
5695 query.clone(),
5696 is_incomplete,
5697 buffer.clone(),
5698 completions.into(),
5699 display_options,
5700 snippet_sort_order,
5701 languages,
5702 language,
5703 cx,
5704 );
5705
5706 let query = if filter_completions { query } else { None };
5707 let matches_task = if let Some(query) = query {
5708 menu.do_async_filtering(query, cx)
5709 } else {
5710 Task::ready(menu.unfiltered_matches())
5711 };
5712 (menu, matches_task)
5713 }) else {
5714 return;
5715 };
5716
5717 let matches = matches_task.await;
5718
5719 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5720 // Newer menu already set, so exit.
5721 if let Some(CodeContextMenu::Completions(prev_menu)) =
5722 editor.context_menu.borrow().as_ref()
5723 && prev_menu.id > id
5724 {
5725 return;
5726 };
5727
5728 // Only valid to take prev_menu because it the new menu is immediately set
5729 // below, or the menu is hidden.
5730 if let Some(CodeContextMenu::Completions(prev_menu)) =
5731 editor.context_menu.borrow_mut().take()
5732 {
5733 let position_matches =
5734 if prev_menu.initial_position == menu.initial_position {
5735 true
5736 } else {
5737 let snapshot = editor.buffer.read(cx).read(cx);
5738 prev_menu.initial_position.to_offset(&snapshot)
5739 == menu.initial_position.to_offset(&snapshot)
5740 };
5741 if position_matches {
5742 // Preserve markdown cache before `set_filter_results` because it will
5743 // try to populate the documentation cache.
5744 menu.preserve_markdown_cache(prev_menu);
5745 }
5746 };
5747
5748 menu.set_filter_results(matches, provider, window, cx);
5749 }) else {
5750 return;
5751 };
5752
5753 menu.visible().then_some(menu)
5754 };
5755
5756 editor
5757 .update_in(cx, |editor, window, cx| {
5758 if editor.focus_handle.is_focused(window)
5759 && let Some(menu) = menu
5760 {
5761 *editor.context_menu.borrow_mut() =
5762 Some(CodeContextMenu::Completions(menu));
5763
5764 crate::hover_popover::hide_hover(editor, cx);
5765 if editor.show_edit_predictions_in_menu() {
5766 editor.update_visible_edit_prediction(window, cx);
5767 } else {
5768 editor.discard_edit_prediction(false, cx);
5769 }
5770
5771 cx.notify();
5772 return;
5773 }
5774
5775 if editor.completion_tasks.len() <= 1 {
5776 // If there are no more completion tasks and the last menu was empty, we should hide it.
5777 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5778 // If it was already hidden and we don't show edit predictions in the menu,
5779 // we should also show the edit prediction when available.
5780 if was_hidden && editor.show_edit_predictions_in_menu() {
5781 editor.update_visible_edit_prediction(window, cx);
5782 }
5783 }
5784 })
5785 .ok();
5786 });
5787
5788 self.completion_tasks.push((id, task));
5789 }
5790
5791 #[cfg(feature = "test-support")]
5792 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5793 let menu = self.context_menu.borrow();
5794 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5795 let completions = menu.completions.borrow();
5796 Some(completions.to_vec())
5797 } else {
5798 None
5799 }
5800 }
5801
5802 pub fn with_completions_menu_matching_id<R>(
5803 &self,
5804 id: CompletionId,
5805 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5806 ) -> R {
5807 let mut context_menu = self.context_menu.borrow_mut();
5808 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5809 return f(None);
5810 };
5811 if completions_menu.id != id {
5812 return f(None);
5813 }
5814 f(Some(completions_menu))
5815 }
5816
5817 pub fn confirm_completion(
5818 &mut self,
5819 action: &ConfirmCompletion,
5820 window: &mut Window,
5821 cx: &mut Context<Self>,
5822 ) -> Option<Task<Result<()>>> {
5823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5824 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5825 }
5826
5827 pub fn confirm_completion_insert(
5828 &mut self,
5829 _: &ConfirmCompletionInsert,
5830 window: &mut Window,
5831 cx: &mut Context<Self>,
5832 ) -> Option<Task<Result<()>>> {
5833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5834 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5835 }
5836
5837 pub fn confirm_completion_replace(
5838 &mut self,
5839 _: &ConfirmCompletionReplace,
5840 window: &mut Window,
5841 cx: &mut Context<Self>,
5842 ) -> Option<Task<Result<()>>> {
5843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5844 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5845 }
5846
5847 pub fn compose_completion(
5848 &mut self,
5849 action: &ComposeCompletion,
5850 window: &mut Window,
5851 cx: &mut Context<Self>,
5852 ) -> Option<Task<Result<()>>> {
5853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5854 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5855 }
5856
5857 fn do_completion(
5858 &mut self,
5859 item_ix: Option<usize>,
5860 intent: CompletionIntent,
5861 window: &mut Window,
5862 cx: &mut Context<Editor>,
5863 ) -> Option<Task<Result<()>>> {
5864 use language::ToOffset as _;
5865
5866 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5867 else {
5868 return None;
5869 };
5870
5871 let candidate_id = {
5872 let entries = completions_menu.entries.borrow();
5873 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5874 if self.show_edit_predictions_in_menu() {
5875 self.discard_edit_prediction(true, cx);
5876 }
5877 mat.candidate_id
5878 };
5879
5880 let completion = completions_menu
5881 .completions
5882 .borrow()
5883 .get(candidate_id)?
5884 .clone();
5885 cx.stop_propagation();
5886
5887 let buffer_handle = completions_menu.buffer.clone();
5888
5889 let CompletionEdit {
5890 new_text,
5891 snippet,
5892 replace_range,
5893 } = process_completion_for_edit(
5894 &completion,
5895 intent,
5896 &buffer_handle,
5897 &completions_menu.initial_position.text_anchor,
5898 cx,
5899 );
5900
5901 let buffer = buffer_handle.read(cx);
5902 let snapshot = self.buffer.read(cx).snapshot(cx);
5903 let newest_anchor = self.selections.newest_anchor();
5904 let replace_range_multibuffer = {
5905 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5906 let multibuffer_anchor = snapshot
5907 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5908 .unwrap()
5909 ..snapshot
5910 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5911 .unwrap();
5912 multibuffer_anchor.start.to_offset(&snapshot)
5913 ..multibuffer_anchor.end.to_offset(&snapshot)
5914 };
5915 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5916 return None;
5917 }
5918
5919 let old_text = buffer
5920 .text_for_range(replace_range.clone())
5921 .collect::<String>();
5922 let lookbehind = newest_anchor
5923 .start
5924 .text_anchor
5925 .to_offset(buffer)
5926 .saturating_sub(replace_range.start);
5927 let lookahead = replace_range
5928 .end
5929 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5930 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5931 let suffix = &old_text[lookbehind.min(old_text.len())..];
5932
5933 let selections = self.selections.all::<usize>(cx);
5934 let mut ranges = Vec::new();
5935 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5936
5937 for selection in &selections {
5938 let range = if selection.id == newest_anchor.id {
5939 replace_range_multibuffer.clone()
5940 } else {
5941 let mut range = selection.range();
5942
5943 // if prefix is present, don't duplicate it
5944 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5945 range.start = range.start.saturating_sub(lookbehind);
5946
5947 // if suffix is also present, mimic the newest cursor and replace it
5948 if selection.id != newest_anchor.id
5949 && snapshot.contains_str_at(range.end, suffix)
5950 {
5951 range.end += lookahead;
5952 }
5953 }
5954 range
5955 };
5956
5957 ranges.push(range.clone());
5958
5959 if !self.linked_edit_ranges.is_empty() {
5960 let start_anchor = snapshot.anchor_before(range.start);
5961 let end_anchor = snapshot.anchor_after(range.end);
5962 if let Some(ranges) = self
5963 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5964 {
5965 for (buffer, edits) in ranges {
5966 linked_edits
5967 .entry(buffer.clone())
5968 .or_default()
5969 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5970 }
5971 }
5972 }
5973 }
5974
5975 let common_prefix_len = old_text
5976 .chars()
5977 .zip(new_text.chars())
5978 .take_while(|(a, b)| a == b)
5979 .map(|(a, _)| a.len_utf8())
5980 .sum::<usize>();
5981
5982 cx.emit(EditorEvent::InputHandled {
5983 utf16_range_to_replace: None,
5984 text: new_text[common_prefix_len..].into(),
5985 });
5986
5987 self.transact(window, cx, |editor, window, cx| {
5988 if let Some(mut snippet) = snippet {
5989 snippet.text = new_text.to_string();
5990 editor
5991 .insert_snippet(&ranges, snippet, window, cx)
5992 .log_err();
5993 } else {
5994 editor.buffer.update(cx, |multi_buffer, cx| {
5995 let auto_indent = match completion.insert_text_mode {
5996 Some(InsertTextMode::AS_IS) => None,
5997 _ => editor.autoindent_mode.clone(),
5998 };
5999 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6000 multi_buffer.edit(edits, auto_indent, cx);
6001 });
6002 }
6003 for (buffer, edits) in linked_edits {
6004 buffer.update(cx, |buffer, cx| {
6005 let snapshot = buffer.snapshot();
6006 let edits = edits
6007 .into_iter()
6008 .map(|(range, text)| {
6009 use text::ToPoint as TP;
6010 let end_point = TP::to_point(&range.end, &snapshot);
6011 let start_point = TP::to_point(&range.start, &snapshot);
6012 (start_point..end_point, text)
6013 })
6014 .sorted_by_key(|(range, _)| range.start);
6015 buffer.edit(edits, None, cx);
6016 })
6017 }
6018
6019 editor.refresh_edit_prediction(true, false, window, cx);
6020 });
6021 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6022
6023 let show_new_completions_on_confirm = completion
6024 .confirm
6025 .as_ref()
6026 .is_some_and(|confirm| confirm(intent, window, cx));
6027 if show_new_completions_on_confirm {
6028 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6029 }
6030
6031 let provider = self.completion_provider.as_ref()?;
6032 drop(completion);
6033 let apply_edits = provider.apply_additional_edits_for_completion(
6034 buffer_handle,
6035 completions_menu.completions.clone(),
6036 candidate_id,
6037 true,
6038 cx,
6039 );
6040
6041 let editor_settings = EditorSettings::get_global(cx);
6042 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6043 // After the code completion is finished, users often want to know what signatures are needed.
6044 // so we should automatically call signature_help
6045 self.show_signature_help(&ShowSignatureHelp, window, cx);
6046 }
6047
6048 Some(cx.foreground_executor().spawn(async move {
6049 apply_edits.await?;
6050 Ok(())
6051 }))
6052 }
6053
6054 pub fn toggle_code_actions(
6055 &mut self,
6056 action: &ToggleCodeActions,
6057 window: &mut Window,
6058 cx: &mut Context<Self>,
6059 ) {
6060 let quick_launch = action.quick_launch;
6061 let mut context_menu = self.context_menu.borrow_mut();
6062 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6063 if code_actions.deployed_from == action.deployed_from {
6064 // Toggle if we're selecting the same one
6065 *context_menu = None;
6066 cx.notify();
6067 return;
6068 } else {
6069 // Otherwise, clear it and start a new one
6070 *context_menu = None;
6071 cx.notify();
6072 }
6073 }
6074 drop(context_menu);
6075 let snapshot = self.snapshot(window, cx);
6076 let deployed_from = action.deployed_from.clone();
6077 let action = action.clone();
6078 self.completion_tasks.clear();
6079 self.discard_edit_prediction(false, cx);
6080
6081 let multibuffer_point = match &action.deployed_from {
6082 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6083 DisplayPoint::new(*row, 0).to_point(&snapshot)
6084 }
6085 _ => self.selections.newest::<Point>(cx).head(),
6086 };
6087 let Some((buffer, buffer_row)) = snapshot
6088 .buffer_snapshot
6089 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6090 .and_then(|(buffer_snapshot, range)| {
6091 self.buffer()
6092 .read(cx)
6093 .buffer(buffer_snapshot.remote_id())
6094 .map(|buffer| (buffer, range.start.row))
6095 })
6096 else {
6097 return;
6098 };
6099 let buffer_id = buffer.read(cx).remote_id();
6100 let tasks = self
6101 .tasks
6102 .get(&(buffer_id, buffer_row))
6103 .map(|t| Arc::new(t.to_owned()));
6104
6105 if !self.focus_handle.is_focused(window) {
6106 return;
6107 }
6108 let project = self.project.clone();
6109
6110 let code_actions_task = match deployed_from {
6111 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6112 _ => self.code_actions(buffer_row, window, cx),
6113 };
6114
6115 let runnable_task = match deployed_from {
6116 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6117 _ => {
6118 let mut task_context_task = Task::ready(None);
6119 if let Some(tasks) = &tasks
6120 && let Some(project) = project
6121 {
6122 task_context_task =
6123 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6124 }
6125
6126 cx.spawn_in(window, {
6127 let buffer = buffer.clone();
6128 async move |editor, cx| {
6129 let task_context = task_context_task.await;
6130
6131 let resolved_tasks =
6132 tasks
6133 .zip(task_context.clone())
6134 .map(|(tasks, task_context)| ResolvedTasks {
6135 templates: tasks.resolve(&task_context).collect(),
6136 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6137 multibuffer_point.row,
6138 tasks.column,
6139 )),
6140 });
6141 let debug_scenarios = editor
6142 .update(cx, |editor, cx| {
6143 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6144 })?
6145 .await;
6146 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6147 }
6148 })
6149 }
6150 };
6151
6152 cx.spawn_in(window, async move |editor, cx| {
6153 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6154 let code_actions = code_actions_task.await;
6155 let spawn_straight_away = quick_launch
6156 && resolved_tasks
6157 .as_ref()
6158 .is_some_and(|tasks| tasks.templates.len() == 1)
6159 && code_actions
6160 .as_ref()
6161 .is_none_or(|actions| actions.is_empty())
6162 && debug_scenarios.is_empty();
6163
6164 editor.update_in(cx, |editor, window, cx| {
6165 crate::hover_popover::hide_hover(editor, cx);
6166 let actions = CodeActionContents::new(
6167 resolved_tasks,
6168 code_actions,
6169 debug_scenarios,
6170 task_context.unwrap_or_default(),
6171 );
6172
6173 // Don't show the menu if there are no actions available
6174 if actions.is_empty() {
6175 cx.notify();
6176 return Task::ready(Ok(()));
6177 }
6178
6179 *editor.context_menu.borrow_mut() =
6180 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6181 buffer,
6182 actions,
6183 selected_item: Default::default(),
6184 scroll_handle: UniformListScrollHandle::default(),
6185 deployed_from,
6186 }));
6187 cx.notify();
6188 if spawn_straight_away
6189 && let Some(task) = editor.confirm_code_action(
6190 &ConfirmCodeAction { item_ix: Some(0) },
6191 window,
6192 cx,
6193 )
6194 {
6195 return task;
6196 }
6197
6198 Task::ready(Ok(()))
6199 })
6200 })
6201 .detach_and_log_err(cx);
6202 }
6203
6204 fn debug_scenarios(
6205 &mut self,
6206 resolved_tasks: &Option<ResolvedTasks>,
6207 buffer: &Entity<Buffer>,
6208 cx: &mut App,
6209 ) -> Task<Vec<task::DebugScenario>> {
6210 maybe!({
6211 let project = self.project()?;
6212 let dap_store = project.read(cx).dap_store();
6213 let mut scenarios = vec![];
6214 let resolved_tasks = resolved_tasks.as_ref()?;
6215 let buffer = buffer.read(cx);
6216 let language = buffer.language()?;
6217 let file = buffer.file();
6218 let debug_adapter = language_settings(language.name().into(), file, cx)
6219 .debuggers
6220 .first()
6221 .map(SharedString::from)
6222 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6223
6224 dap_store.update(cx, |dap_store, cx| {
6225 for (_, task) in &resolved_tasks.templates {
6226 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6227 task.original_task().clone(),
6228 debug_adapter.clone().into(),
6229 task.display_label().to_owned().into(),
6230 cx,
6231 );
6232 scenarios.push(maybe_scenario);
6233 }
6234 });
6235 Some(cx.background_spawn(async move {
6236 futures::future::join_all(scenarios)
6237 .await
6238 .into_iter()
6239 .flatten()
6240 .collect::<Vec<_>>()
6241 }))
6242 })
6243 .unwrap_or_else(|| Task::ready(vec![]))
6244 }
6245
6246 fn code_actions(
6247 &mut self,
6248 buffer_row: u32,
6249 window: &mut Window,
6250 cx: &mut Context<Self>,
6251 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6252 let mut task = self.code_actions_task.take();
6253 cx.spawn_in(window, async move |editor, cx| {
6254 while let Some(prev_task) = task {
6255 prev_task.await.log_err();
6256 task = editor
6257 .update(cx, |this, _| this.code_actions_task.take())
6258 .ok()?;
6259 }
6260
6261 editor
6262 .update(cx, |editor, cx| {
6263 editor
6264 .available_code_actions
6265 .clone()
6266 .and_then(|(location, code_actions)| {
6267 let snapshot = location.buffer.read(cx).snapshot();
6268 let point_range = location.range.to_point(&snapshot);
6269 let point_range = point_range.start.row..=point_range.end.row;
6270 if point_range.contains(&buffer_row) {
6271 Some(code_actions)
6272 } else {
6273 None
6274 }
6275 })
6276 })
6277 .ok()
6278 .flatten()
6279 })
6280 }
6281
6282 pub fn confirm_code_action(
6283 &mut self,
6284 action: &ConfirmCodeAction,
6285 window: &mut Window,
6286 cx: &mut Context<Self>,
6287 ) -> Option<Task<Result<()>>> {
6288 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6289
6290 let actions_menu =
6291 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6292 menu
6293 } else {
6294 return None;
6295 };
6296
6297 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6298 let action = actions_menu.actions.get(action_ix)?;
6299 let title = action.label();
6300 let buffer = actions_menu.buffer;
6301 let workspace = self.workspace()?;
6302
6303 match action {
6304 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6305 workspace.update(cx, |workspace, cx| {
6306 workspace.schedule_resolved_task(
6307 task_source_kind,
6308 resolved_task,
6309 false,
6310 window,
6311 cx,
6312 );
6313
6314 Some(Task::ready(Ok(())))
6315 })
6316 }
6317 CodeActionsItem::CodeAction {
6318 excerpt_id,
6319 action,
6320 provider,
6321 } => {
6322 let apply_code_action =
6323 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6324 let workspace = workspace.downgrade();
6325 Some(cx.spawn_in(window, async move |editor, cx| {
6326 let project_transaction = apply_code_action.await?;
6327 Self::open_project_transaction(
6328 &editor,
6329 workspace,
6330 project_transaction,
6331 title,
6332 cx,
6333 )
6334 .await
6335 }))
6336 }
6337 CodeActionsItem::DebugScenario(scenario) => {
6338 let context = actions_menu.actions.context;
6339
6340 workspace.update(cx, |workspace, cx| {
6341 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6342 workspace.start_debug_session(
6343 scenario,
6344 context,
6345 Some(buffer),
6346 None,
6347 window,
6348 cx,
6349 );
6350 });
6351 Some(Task::ready(Ok(())))
6352 }
6353 }
6354 }
6355
6356 pub async fn open_project_transaction(
6357 editor: &WeakEntity<Editor>,
6358 workspace: WeakEntity<Workspace>,
6359 transaction: ProjectTransaction,
6360 title: String,
6361 cx: &mut AsyncWindowContext,
6362 ) -> Result<()> {
6363 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6364 cx.update(|_, cx| {
6365 entries.sort_unstable_by_key(|(buffer, _)| {
6366 buffer.read(cx).file().map(|f| f.path().clone())
6367 });
6368 })?;
6369
6370 // If the project transaction's edits are all contained within this editor, then
6371 // avoid opening a new editor to display them.
6372
6373 if let Some((buffer, transaction)) = entries.first() {
6374 if entries.len() == 1 {
6375 let excerpt = editor.update(cx, |editor, cx| {
6376 editor
6377 .buffer()
6378 .read(cx)
6379 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6380 })?;
6381 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6382 && excerpted_buffer == *buffer
6383 {
6384 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6385 let excerpt_range = excerpt_range.to_offset(buffer);
6386 buffer
6387 .edited_ranges_for_transaction::<usize>(transaction)
6388 .all(|range| {
6389 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6390 })
6391 })?;
6392
6393 if all_edits_within_excerpt {
6394 return Ok(());
6395 }
6396 }
6397 }
6398 } else {
6399 return Ok(());
6400 }
6401
6402 let mut ranges_to_highlight = Vec::new();
6403 let excerpt_buffer = cx.new(|cx| {
6404 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6405 for (buffer_handle, transaction) in &entries {
6406 let edited_ranges = buffer_handle
6407 .read(cx)
6408 .edited_ranges_for_transaction::<Point>(transaction)
6409 .collect::<Vec<_>>();
6410 let (ranges, _) = multibuffer.set_excerpts_for_path(
6411 PathKey::for_buffer(buffer_handle, cx),
6412 buffer_handle.clone(),
6413 edited_ranges,
6414 multibuffer_context_lines(cx),
6415 cx,
6416 );
6417
6418 ranges_to_highlight.extend(ranges);
6419 }
6420 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6421 multibuffer
6422 })?;
6423
6424 workspace.update_in(cx, |workspace, window, cx| {
6425 let project = workspace.project().clone();
6426 let editor =
6427 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6428 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6429 editor.update(cx, |editor, cx| {
6430 editor.highlight_background::<Self>(
6431 &ranges_to_highlight,
6432 |theme| theme.colors().editor_highlighted_line_background,
6433 cx,
6434 );
6435 });
6436 })?;
6437
6438 Ok(())
6439 }
6440
6441 pub fn clear_code_action_providers(&mut self) {
6442 self.code_action_providers.clear();
6443 self.available_code_actions.take();
6444 }
6445
6446 pub fn add_code_action_provider(
6447 &mut self,
6448 provider: Rc<dyn CodeActionProvider>,
6449 window: &mut Window,
6450 cx: &mut Context<Self>,
6451 ) {
6452 if self
6453 .code_action_providers
6454 .iter()
6455 .any(|existing_provider| existing_provider.id() == provider.id())
6456 {
6457 return;
6458 }
6459
6460 self.code_action_providers.push(provider);
6461 self.refresh_code_actions(window, cx);
6462 }
6463
6464 pub fn remove_code_action_provider(
6465 &mut self,
6466 id: Arc<str>,
6467 window: &mut Window,
6468 cx: &mut Context<Self>,
6469 ) {
6470 self.code_action_providers
6471 .retain(|provider| provider.id() != id);
6472 self.refresh_code_actions(window, cx);
6473 }
6474
6475 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6476 !self.code_action_providers.is_empty()
6477 && EditorSettings::get_global(cx).toolbar.code_actions
6478 }
6479
6480 pub fn has_available_code_actions(&self) -> bool {
6481 self.available_code_actions
6482 .as_ref()
6483 .is_some_and(|(_, actions)| !actions.is_empty())
6484 }
6485
6486 fn render_inline_code_actions(
6487 &self,
6488 icon_size: ui::IconSize,
6489 display_row: DisplayRow,
6490 is_active: bool,
6491 cx: &mut Context<Self>,
6492 ) -> AnyElement {
6493 let show_tooltip = !self.context_menu_visible();
6494 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6495 .icon_size(icon_size)
6496 .shape(ui::IconButtonShape::Square)
6497 .icon_color(ui::Color::Hidden)
6498 .toggle_state(is_active)
6499 .when(show_tooltip, |this| {
6500 this.tooltip({
6501 let focus_handle = self.focus_handle.clone();
6502 move |window, cx| {
6503 Tooltip::for_action_in(
6504 "Toggle Code Actions",
6505 &ToggleCodeActions {
6506 deployed_from: None,
6507 quick_launch: false,
6508 },
6509 &focus_handle,
6510 window,
6511 cx,
6512 )
6513 }
6514 })
6515 })
6516 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6517 window.focus(&editor.focus_handle(cx));
6518 editor.toggle_code_actions(
6519 &crate::actions::ToggleCodeActions {
6520 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6521 display_row,
6522 )),
6523 quick_launch: false,
6524 },
6525 window,
6526 cx,
6527 );
6528 }))
6529 .into_any_element()
6530 }
6531
6532 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6533 &self.context_menu
6534 }
6535
6536 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6537 let newest_selection = self.selections.newest_anchor().clone();
6538 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6539 let buffer = self.buffer.read(cx);
6540 if newest_selection.head().diff_base_anchor.is_some() {
6541 return None;
6542 }
6543 let (start_buffer, start) =
6544 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6545 let (end_buffer, end) =
6546 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6547 if start_buffer != end_buffer {
6548 return None;
6549 }
6550
6551 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6552 cx.background_executor()
6553 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6554 .await;
6555
6556 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6557 let providers = this.code_action_providers.clone();
6558 let tasks = this
6559 .code_action_providers
6560 .iter()
6561 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6562 .collect::<Vec<_>>();
6563 (providers, tasks)
6564 })?;
6565
6566 let mut actions = Vec::new();
6567 for (provider, provider_actions) in
6568 providers.into_iter().zip(future::join_all(tasks).await)
6569 {
6570 if let Some(provider_actions) = provider_actions.log_err() {
6571 actions.extend(provider_actions.into_iter().map(|action| {
6572 AvailableCodeAction {
6573 excerpt_id: newest_selection.start.excerpt_id,
6574 action,
6575 provider: provider.clone(),
6576 }
6577 }));
6578 }
6579 }
6580
6581 this.update(cx, |this, cx| {
6582 this.available_code_actions = if actions.is_empty() {
6583 None
6584 } else {
6585 Some((
6586 Location {
6587 buffer: start_buffer,
6588 range: start..end,
6589 },
6590 actions.into(),
6591 ))
6592 };
6593 cx.notify();
6594 })
6595 }));
6596 None
6597 }
6598
6599 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6600 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6601 self.show_git_blame_inline = false;
6602
6603 self.show_git_blame_inline_delay_task =
6604 Some(cx.spawn_in(window, async move |this, cx| {
6605 cx.background_executor().timer(delay).await;
6606
6607 this.update(cx, |this, cx| {
6608 this.show_git_blame_inline = true;
6609 cx.notify();
6610 })
6611 .log_err();
6612 }));
6613 }
6614 }
6615
6616 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6617 let snapshot = self.snapshot(window, cx);
6618 let cursor = self.selections.newest::<Point>(cx).head();
6619 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6620 else {
6621 return;
6622 };
6623
6624 let Some(blame) = self.blame.as_ref() else {
6625 return;
6626 };
6627
6628 let row_info = RowInfo {
6629 buffer_id: Some(buffer.remote_id()),
6630 buffer_row: Some(point.row),
6631 ..Default::default()
6632 };
6633 let Some(blame_entry) = blame
6634 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6635 .flatten()
6636 else {
6637 return;
6638 };
6639
6640 let anchor = self.selections.newest_anchor().head();
6641 let position = self.to_pixel_point(anchor, &snapshot, window);
6642 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6643 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6644 };
6645 }
6646
6647 fn show_blame_popover(
6648 &mut self,
6649 blame_entry: &BlameEntry,
6650 position: gpui::Point<Pixels>,
6651 ignore_timeout: bool,
6652 cx: &mut Context<Self>,
6653 ) {
6654 if let Some(state) = &mut self.inline_blame_popover {
6655 state.hide_task.take();
6656 } else {
6657 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6658 let blame_entry = blame_entry.clone();
6659 let show_task = cx.spawn(async move |editor, cx| {
6660 if !ignore_timeout {
6661 cx.background_executor()
6662 .timer(std::time::Duration::from_millis(blame_popover_delay))
6663 .await;
6664 }
6665 editor
6666 .update(cx, |editor, cx| {
6667 editor.inline_blame_popover_show_task.take();
6668 let Some(blame) = editor.blame.as_ref() else {
6669 return;
6670 };
6671 let blame = blame.read(cx);
6672 let details = blame.details_for_entry(&blame_entry);
6673 let markdown = cx.new(|cx| {
6674 Markdown::new(
6675 details
6676 .as_ref()
6677 .map(|message| message.message.clone())
6678 .unwrap_or_default(),
6679 None,
6680 None,
6681 cx,
6682 )
6683 });
6684 editor.inline_blame_popover = Some(InlineBlamePopover {
6685 position,
6686 hide_task: None,
6687 popover_bounds: None,
6688 popover_state: InlineBlamePopoverState {
6689 scroll_handle: ScrollHandle::new(),
6690 commit_message: details,
6691 markdown,
6692 },
6693 keyboard_grace: ignore_timeout,
6694 });
6695 cx.notify();
6696 })
6697 .ok();
6698 });
6699 self.inline_blame_popover_show_task = Some(show_task);
6700 }
6701 }
6702
6703 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6704 self.inline_blame_popover_show_task.take();
6705 if let Some(state) = &mut self.inline_blame_popover {
6706 let hide_task = cx.spawn(async move |editor, cx| {
6707 cx.background_executor()
6708 .timer(std::time::Duration::from_millis(100))
6709 .await;
6710 editor
6711 .update(cx, |editor, cx| {
6712 editor.inline_blame_popover.take();
6713 cx.notify();
6714 })
6715 .ok();
6716 });
6717 state.hide_task = Some(hide_task);
6718 }
6719 }
6720
6721 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6722 if self.pending_rename.is_some() {
6723 return None;
6724 }
6725
6726 let provider = self.semantics_provider.clone()?;
6727 let buffer = self.buffer.read(cx);
6728 let newest_selection = self.selections.newest_anchor().clone();
6729 let cursor_position = newest_selection.head();
6730 let (cursor_buffer, cursor_buffer_position) =
6731 buffer.text_anchor_for_position(cursor_position, cx)?;
6732 let (tail_buffer, tail_buffer_position) =
6733 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6734 if cursor_buffer != tail_buffer {
6735 return None;
6736 }
6737
6738 let snapshot = cursor_buffer.read(cx).snapshot();
6739 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6740 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6741 if start_word_range != end_word_range {
6742 self.document_highlights_task.take();
6743 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6744 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6745 return None;
6746 }
6747
6748 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6749 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6750 cx.background_executor()
6751 .timer(Duration::from_millis(debounce))
6752 .await;
6753
6754 let highlights = if let Some(highlights) = cx
6755 .update(|cx| {
6756 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6757 })
6758 .ok()
6759 .flatten()
6760 {
6761 highlights.await.log_err()
6762 } else {
6763 None
6764 };
6765
6766 if let Some(highlights) = highlights {
6767 this.update(cx, |this, cx| {
6768 if this.pending_rename.is_some() {
6769 return;
6770 }
6771
6772 let buffer = this.buffer.read(cx);
6773 if buffer
6774 .text_anchor_for_position(cursor_position, cx)
6775 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6776 {
6777 return;
6778 }
6779
6780 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6781 let mut write_ranges = Vec::new();
6782 let mut read_ranges = Vec::new();
6783 for highlight in highlights {
6784 let buffer_id = cursor_buffer.read(cx).remote_id();
6785 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6786 {
6787 let start = highlight
6788 .range
6789 .start
6790 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6791 let end = highlight
6792 .range
6793 .end
6794 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6795 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6796 continue;
6797 }
6798
6799 let range = Anchor {
6800 buffer_id: Some(buffer_id),
6801 excerpt_id,
6802 text_anchor: start,
6803 diff_base_anchor: None,
6804 }..Anchor {
6805 buffer_id: Some(buffer_id),
6806 excerpt_id,
6807 text_anchor: end,
6808 diff_base_anchor: None,
6809 };
6810 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6811 write_ranges.push(range);
6812 } else {
6813 read_ranges.push(range);
6814 }
6815 }
6816 }
6817
6818 this.highlight_background::<DocumentHighlightRead>(
6819 &read_ranges,
6820 |theme| theme.colors().editor_document_highlight_read_background,
6821 cx,
6822 );
6823 this.highlight_background::<DocumentHighlightWrite>(
6824 &write_ranges,
6825 |theme| theme.colors().editor_document_highlight_write_background,
6826 cx,
6827 );
6828 cx.notify();
6829 })
6830 .log_err();
6831 }
6832 }));
6833 None
6834 }
6835
6836 fn prepare_highlight_query_from_selection(
6837 &mut self,
6838 cx: &mut Context<Editor>,
6839 ) -> Option<(String, Range<Anchor>)> {
6840 if matches!(self.mode, EditorMode::SingleLine) {
6841 return None;
6842 }
6843 if !EditorSettings::get_global(cx).selection_highlight {
6844 return None;
6845 }
6846 if self.selections.count() != 1 || self.selections.line_mode {
6847 return None;
6848 }
6849 let selection = self.selections.newest::<Point>(cx);
6850 if selection.is_empty() || selection.start.row != selection.end.row {
6851 return None;
6852 }
6853 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6854 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6855 let query = multi_buffer_snapshot
6856 .text_for_range(selection_anchor_range.clone())
6857 .collect::<String>();
6858 if query.trim().is_empty() {
6859 return None;
6860 }
6861 Some((query, selection_anchor_range))
6862 }
6863
6864 fn update_selection_occurrence_highlights(
6865 &mut self,
6866 query_text: String,
6867 query_range: Range<Anchor>,
6868 multi_buffer_range_to_query: Range<Point>,
6869 use_debounce: bool,
6870 window: &mut Window,
6871 cx: &mut Context<Editor>,
6872 ) -> Task<()> {
6873 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6874 cx.spawn_in(window, async move |editor, cx| {
6875 if use_debounce {
6876 cx.background_executor()
6877 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6878 .await;
6879 }
6880 let match_task = cx.background_spawn(async move {
6881 let buffer_ranges = multi_buffer_snapshot
6882 .range_to_buffer_ranges(multi_buffer_range_to_query)
6883 .into_iter()
6884 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6885 let mut match_ranges = Vec::new();
6886 let Ok(regex) = project::search::SearchQuery::text(
6887 query_text.clone(),
6888 false,
6889 false,
6890 false,
6891 Default::default(),
6892 Default::default(),
6893 false,
6894 None,
6895 ) else {
6896 return Vec::default();
6897 };
6898 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6899 match_ranges.extend(
6900 regex
6901 .search(buffer_snapshot, Some(search_range.clone()))
6902 .await
6903 .into_iter()
6904 .filter_map(|match_range| {
6905 let match_start = buffer_snapshot
6906 .anchor_after(search_range.start + match_range.start);
6907 let match_end = buffer_snapshot
6908 .anchor_before(search_range.start + match_range.end);
6909 let match_anchor_range = Anchor::range_in_buffer(
6910 excerpt_id,
6911 buffer_snapshot.remote_id(),
6912 match_start..match_end,
6913 );
6914 (match_anchor_range != query_range).then_some(match_anchor_range)
6915 }),
6916 );
6917 }
6918 match_ranges
6919 });
6920 let match_ranges = match_task.await;
6921 editor
6922 .update_in(cx, |editor, _, cx| {
6923 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6924 if !match_ranges.is_empty() {
6925 editor.highlight_background::<SelectedTextHighlight>(
6926 &match_ranges,
6927 |theme| theme.colors().editor_document_highlight_bracket_background,
6928 cx,
6929 )
6930 }
6931 })
6932 .log_err();
6933 })
6934 }
6935
6936 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6937 struct NewlineFold;
6938 let type_id = std::any::TypeId::of::<NewlineFold>();
6939 if !self.mode.is_single_line() {
6940 return;
6941 }
6942 let snapshot = self.snapshot(window, cx);
6943 if snapshot.buffer_snapshot.max_point().row == 0 {
6944 return;
6945 }
6946 let task = cx.background_spawn(async move {
6947 let new_newlines = snapshot
6948 .buffer_chars_at(0)
6949 .filter_map(|(c, i)| {
6950 if c == '\n' {
6951 Some(
6952 snapshot.buffer_snapshot.anchor_after(i)
6953 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6954 )
6955 } else {
6956 None
6957 }
6958 })
6959 .collect::<Vec<_>>();
6960 let existing_newlines = snapshot
6961 .folds_in_range(0..snapshot.buffer_snapshot.len())
6962 .filter_map(|fold| {
6963 if fold.placeholder.type_tag == Some(type_id) {
6964 Some(fold.range.start..fold.range.end)
6965 } else {
6966 None
6967 }
6968 })
6969 .collect::<Vec<_>>();
6970
6971 (new_newlines, existing_newlines)
6972 });
6973 self.folding_newlines = cx.spawn(async move |this, cx| {
6974 let (new_newlines, existing_newlines) = task.await;
6975 if new_newlines == existing_newlines {
6976 return;
6977 }
6978 let placeholder = FoldPlaceholder {
6979 render: Arc::new(move |_, _, cx| {
6980 div()
6981 .bg(cx.theme().status().hint_background)
6982 .border_b_1()
6983 .size_full()
6984 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6985 .border_color(cx.theme().status().hint)
6986 .child("\\n")
6987 .into_any()
6988 }),
6989 constrain_width: false,
6990 merge_adjacent: false,
6991 type_tag: Some(type_id),
6992 };
6993 let creases = new_newlines
6994 .into_iter()
6995 .map(|range| Crease::simple(range, placeholder.clone()))
6996 .collect();
6997 this.update(cx, |this, cx| {
6998 this.display_map.update(cx, |display_map, cx| {
6999 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7000 display_map.fold(creases, cx);
7001 });
7002 })
7003 .ok();
7004 });
7005 }
7006
7007 fn refresh_selected_text_highlights(
7008 &mut self,
7009 on_buffer_edit: bool,
7010 window: &mut Window,
7011 cx: &mut Context<Editor>,
7012 ) {
7013 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7014 else {
7015 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7016 self.quick_selection_highlight_task.take();
7017 self.debounced_selection_highlight_task.take();
7018 return;
7019 };
7020 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7021 if on_buffer_edit
7022 || self
7023 .quick_selection_highlight_task
7024 .as_ref()
7025 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7026 {
7027 let multi_buffer_visible_start = self
7028 .scroll_manager
7029 .anchor()
7030 .anchor
7031 .to_point(&multi_buffer_snapshot);
7032 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7033 multi_buffer_visible_start
7034 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7035 Bias::Left,
7036 );
7037 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7038 self.quick_selection_highlight_task = Some((
7039 query_range.clone(),
7040 self.update_selection_occurrence_highlights(
7041 query_text.clone(),
7042 query_range.clone(),
7043 multi_buffer_visible_range,
7044 false,
7045 window,
7046 cx,
7047 ),
7048 ));
7049 }
7050 if on_buffer_edit
7051 || self
7052 .debounced_selection_highlight_task
7053 .as_ref()
7054 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7055 {
7056 let multi_buffer_start = multi_buffer_snapshot
7057 .anchor_before(0)
7058 .to_point(&multi_buffer_snapshot);
7059 let multi_buffer_end = multi_buffer_snapshot
7060 .anchor_after(multi_buffer_snapshot.len())
7061 .to_point(&multi_buffer_snapshot);
7062 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7063 self.debounced_selection_highlight_task = Some((
7064 query_range.clone(),
7065 self.update_selection_occurrence_highlights(
7066 query_text,
7067 query_range,
7068 multi_buffer_full_range,
7069 true,
7070 window,
7071 cx,
7072 ),
7073 ));
7074 }
7075 }
7076
7077 pub fn refresh_edit_prediction(
7078 &mut self,
7079 debounce: bool,
7080 user_requested: bool,
7081 window: &mut Window,
7082 cx: &mut Context<Self>,
7083 ) -> Option<()> {
7084 if DisableAiSettings::get_global(cx).disable_ai {
7085 return None;
7086 }
7087
7088 let provider = self.edit_prediction_provider()?;
7089 let cursor = self.selections.newest_anchor().head();
7090 let (buffer, cursor_buffer_position) =
7091 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7092
7093 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7094 self.discard_edit_prediction(false, cx);
7095 return None;
7096 }
7097
7098 if !user_requested
7099 && (!self.should_show_edit_predictions()
7100 || !self.is_focused(window)
7101 || buffer.read(cx).is_empty())
7102 {
7103 self.discard_edit_prediction(false, cx);
7104 return None;
7105 }
7106
7107 self.update_visible_edit_prediction(window, cx);
7108 provider.refresh(
7109 self.project.clone(),
7110 buffer,
7111 cursor_buffer_position,
7112 debounce,
7113 cx,
7114 );
7115 Some(())
7116 }
7117
7118 fn show_edit_predictions_in_menu(&self) -> bool {
7119 match self.edit_prediction_settings {
7120 EditPredictionSettings::Disabled => false,
7121 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7122 }
7123 }
7124
7125 pub fn edit_predictions_enabled(&self) -> bool {
7126 match self.edit_prediction_settings {
7127 EditPredictionSettings::Disabled => false,
7128 EditPredictionSettings::Enabled { .. } => true,
7129 }
7130 }
7131
7132 fn edit_prediction_requires_modifier(&self) -> bool {
7133 match self.edit_prediction_settings {
7134 EditPredictionSettings::Disabled => false,
7135 EditPredictionSettings::Enabled {
7136 preview_requires_modifier,
7137 ..
7138 } => preview_requires_modifier,
7139 }
7140 }
7141
7142 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7143 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7144 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7145 self.discard_edit_prediction(false, cx);
7146 } else {
7147 let selection = self.selections.newest_anchor();
7148 let cursor = selection.head();
7149
7150 if let Some((buffer, cursor_buffer_position)) =
7151 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7152 {
7153 self.edit_prediction_settings =
7154 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7155 }
7156 }
7157 }
7158
7159 fn edit_prediction_settings_at_position(
7160 &self,
7161 buffer: &Entity<Buffer>,
7162 buffer_position: language::Anchor,
7163 cx: &App,
7164 ) -> EditPredictionSettings {
7165 if !self.mode.is_full()
7166 || !self.show_edit_predictions_override.unwrap_or(true)
7167 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7168 {
7169 return EditPredictionSettings::Disabled;
7170 }
7171
7172 let buffer = buffer.read(cx);
7173
7174 let file = buffer.file();
7175
7176 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7177 return EditPredictionSettings::Disabled;
7178 };
7179
7180 let by_provider = matches!(
7181 self.menu_edit_predictions_policy,
7182 MenuEditPredictionsPolicy::ByProvider
7183 );
7184
7185 let show_in_menu = by_provider
7186 && self
7187 .edit_prediction_provider
7188 .as_ref()
7189 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7190
7191 let preview_requires_modifier =
7192 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7193
7194 EditPredictionSettings::Enabled {
7195 show_in_menu,
7196 preview_requires_modifier,
7197 }
7198 }
7199
7200 fn should_show_edit_predictions(&self) -> bool {
7201 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7202 }
7203
7204 pub fn edit_prediction_preview_is_active(&self) -> bool {
7205 matches!(
7206 self.edit_prediction_preview,
7207 EditPredictionPreview::Active { .. }
7208 )
7209 }
7210
7211 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7212 let cursor = self.selections.newest_anchor().head();
7213 if let Some((buffer, cursor_position)) =
7214 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7215 {
7216 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7217 } else {
7218 false
7219 }
7220 }
7221
7222 pub fn supports_minimap(&self, cx: &App) -> bool {
7223 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7224 }
7225
7226 fn edit_predictions_enabled_in_buffer(
7227 &self,
7228 buffer: &Entity<Buffer>,
7229 buffer_position: language::Anchor,
7230 cx: &App,
7231 ) -> bool {
7232 maybe!({
7233 if self.read_only(cx) {
7234 return Some(false);
7235 }
7236 let provider = self.edit_prediction_provider()?;
7237 if !provider.is_enabled(buffer, buffer_position, cx) {
7238 return Some(false);
7239 }
7240 let buffer = buffer.read(cx);
7241 let Some(file) = buffer.file() else {
7242 return Some(true);
7243 };
7244 let settings = all_language_settings(Some(file), cx);
7245 Some(settings.edit_predictions_enabled_for_file(file, cx))
7246 })
7247 .unwrap_or(false)
7248 }
7249
7250 fn cycle_edit_prediction(
7251 &mut self,
7252 direction: Direction,
7253 window: &mut Window,
7254 cx: &mut Context<Self>,
7255 ) -> Option<()> {
7256 let provider = self.edit_prediction_provider()?;
7257 let cursor = self.selections.newest_anchor().head();
7258 let (buffer, cursor_buffer_position) =
7259 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7260 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7261 return None;
7262 }
7263
7264 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7265 self.update_visible_edit_prediction(window, cx);
7266
7267 Some(())
7268 }
7269
7270 pub fn show_edit_prediction(
7271 &mut self,
7272 _: &ShowEditPrediction,
7273 window: &mut Window,
7274 cx: &mut Context<Self>,
7275 ) {
7276 if !self.has_active_edit_prediction() {
7277 self.refresh_edit_prediction(false, true, window, cx);
7278 return;
7279 }
7280
7281 self.update_visible_edit_prediction(window, cx);
7282 }
7283
7284 pub fn display_cursor_names(
7285 &mut self,
7286 _: &DisplayCursorNames,
7287 window: &mut Window,
7288 cx: &mut Context<Self>,
7289 ) {
7290 self.show_cursor_names(window, cx);
7291 }
7292
7293 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7294 self.show_cursor_names = true;
7295 cx.notify();
7296 cx.spawn_in(window, async move |this, cx| {
7297 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7298 this.update(cx, |this, cx| {
7299 this.show_cursor_names = false;
7300 cx.notify()
7301 })
7302 .ok()
7303 })
7304 .detach();
7305 }
7306
7307 pub fn next_edit_prediction(
7308 &mut self,
7309 _: &NextEditPrediction,
7310 window: &mut Window,
7311 cx: &mut Context<Self>,
7312 ) {
7313 if self.has_active_edit_prediction() {
7314 self.cycle_edit_prediction(Direction::Next, window, cx);
7315 } else {
7316 let is_copilot_disabled = self
7317 .refresh_edit_prediction(false, true, window, cx)
7318 .is_none();
7319 if is_copilot_disabled {
7320 cx.propagate();
7321 }
7322 }
7323 }
7324
7325 pub fn previous_edit_prediction(
7326 &mut self,
7327 _: &PreviousEditPrediction,
7328 window: &mut Window,
7329 cx: &mut Context<Self>,
7330 ) {
7331 if self.has_active_edit_prediction() {
7332 self.cycle_edit_prediction(Direction::Prev, window, cx);
7333 } else {
7334 let is_copilot_disabled = self
7335 .refresh_edit_prediction(false, true, window, cx)
7336 .is_none();
7337 if is_copilot_disabled {
7338 cx.propagate();
7339 }
7340 }
7341 }
7342
7343 pub fn accept_edit_prediction(
7344 &mut self,
7345 _: &AcceptEditPrediction,
7346 window: &mut Window,
7347 cx: &mut Context<Self>,
7348 ) {
7349 if self.show_edit_predictions_in_menu() {
7350 self.hide_context_menu(window, cx);
7351 }
7352
7353 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7354 return;
7355 };
7356
7357 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7358
7359 match &active_edit_prediction.completion {
7360 EditPrediction::Move { target, .. } => {
7361 let target = *target;
7362
7363 if let Some(position_map) = &self.last_position_map {
7364 if position_map
7365 .visible_row_range
7366 .contains(&target.to_display_point(&position_map.snapshot).row())
7367 || !self.edit_prediction_requires_modifier()
7368 {
7369 self.unfold_ranges(&[target..target], true, false, cx);
7370 // Note that this is also done in vim's handler of the Tab action.
7371 self.change_selections(
7372 SelectionEffects::scroll(Autoscroll::newest()),
7373 window,
7374 cx,
7375 |selections| {
7376 selections.select_anchor_ranges([target..target]);
7377 },
7378 );
7379 self.clear_row_highlights::<EditPredictionPreview>();
7380
7381 self.edit_prediction_preview
7382 .set_previous_scroll_position(None);
7383 } else {
7384 self.edit_prediction_preview
7385 .set_previous_scroll_position(Some(
7386 position_map.snapshot.scroll_anchor,
7387 ));
7388
7389 self.highlight_rows::<EditPredictionPreview>(
7390 target..target,
7391 cx.theme().colors().editor_highlighted_line_background,
7392 RowHighlightOptions {
7393 autoscroll: true,
7394 ..Default::default()
7395 },
7396 cx,
7397 );
7398 self.request_autoscroll(Autoscroll::fit(), cx);
7399 }
7400 }
7401 }
7402 EditPrediction::Edit { edits, .. } => {
7403 if let Some(provider) = self.edit_prediction_provider() {
7404 provider.accept(cx);
7405 }
7406
7407 // Store the transaction ID and selections before applying the edit
7408 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7409
7410 let snapshot = self.buffer.read(cx).snapshot(cx);
7411 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7412
7413 self.buffer.update(cx, |buffer, cx| {
7414 buffer.edit(edits.iter().cloned(), None, cx)
7415 });
7416
7417 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7418 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7419 });
7420
7421 let selections = self.selections.disjoint_anchors();
7422 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7423 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7424 if has_new_transaction {
7425 self.selection_history
7426 .insert_transaction(transaction_id_now, selections);
7427 }
7428 }
7429
7430 self.update_visible_edit_prediction(window, cx);
7431 if self.active_edit_prediction.is_none() {
7432 self.refresh_edit_prediction(true, true, window, cx);
7433 }
7434
7435 cx.notify();
7436 }
7437 }
7438
7439 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7440 }
7441
7442 pub fn accept_partial_edit_prediction(
7443 &mut self,
7444 _: &AcceptPartialEditPrediction,
7445 window: &mut Window,
7446 cx: &mut Context<Self>,
7447 ) {
7448 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7449 return;
7450 };
7451 if self.selections.count() != 1 {
7452 return;
7453 }
7454
7455 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7456
7457 match &active_edit_prediction.completion {
7458 EditPrediction::Move { target, .. } => {
7459 let target = *target;
7460 self.change_selections(
7461 SelectionEffects::scroll(Autoscroll::newest()),
7462 window,
7463 cx,
7464 |selections| {
7465 selections.select_anchor_ranges([target..target]);
7466 },
7467 );
7468 }
7469 EditPrediction::Edit { edits, .. } => {
7470 // Find an insertion that starts at the cursor position.
7471 let snapshot = self.buffer.read(cx).snapshot(cx);
7472 let cursor_offset = self.selections.newest::<usize>(cx).head();
7473 let insertion = edits.iter().find_map(|(range, text)| {
7474 let range = range.to_offset(&snapshot);
7475 if range.is_empty() && range.start == cursor_offset {
7476 Some(text)
7477 } else {
7478 None
7479 }
7480 });
7481
7482 if let Some(text) = insertion {
7483 let mut partial_completion = text
7484 .chars()
7485 .by_ref()
7486 .take_while(|c| c.is_alphabetic())
7487 .collect::<String>();
7488 if partial_completion.is_empty() {
7489 partial_completion = text
7490 .chars()
7491 .by_ref()
7492 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7493 .collect::<String>();
7494 }
7495
7496 cx.emit(EditorEvent::InputHandled {
7497 utf16_range_to_replace: None,
7498 text: partial_completion.clone().into(),
7499 });
7500
7501 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7502
7503 self.refresh_edit_prediction(true, true, window, cx);
7504 cx.notify();
7505 } else {
7506 self.accept_edit_prediction(&Default::default(), window, cx);
7507 }
7508 }
7509 }
7510 }
7511
7512 fn discard_edit_prediction(
7513 &mut self,
7514 should_report_edit_prediction_event: bool,
7515 cx: &mut Context<Self>,
7516 ) -> bool {
7517 if should_report_edit_prediction_event {
7518 let completion_id = self
7519 .active_edit_prediction
7520 .as_ref()
7521 .and_then(|active_completion| active_completion.completion_id.clone());
7522
7523 self.report_edit_prediction_event(completion_id, false, cx);
7524 }
7525
7526 if let Some(provider) = self.edit_prediction_provider() {
7527 provider.discard(cx);
7528 }
7529
7530 self.take_active_edit_prediction(cx)
7531 }
7532
7533 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7534 let Some(provider) = self.edit_prediction_provider() else {
7535 return;
7536 };
7537
7538 let Some((_, buffer, _)) = self
7539 .buffer
7540 .read(cx)
7541 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7542 else {
7543 return;
7544 };
7545
7546 let extension = buffer
7547 .read(cx)
7548 .file()
7549 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7550
7551 let event_type = match accepted {
7552 true => "Edit Prediction Accepted",
7553 false => "Edit Prediction Discarded",
7554 };
7555 telemetry::event!(
7556 event_type,
7557 provider = provider.name(),
7558 prediction_id = id,
7559 suggestion_accepted = accepted,
7560 file_extension = extension,
7561 );
7562 }
7563
7564 pub fn has_active_edit_prediction(&self) -> bool {
7565 self.active_edit_prediction.is_some()
7566 }
7567
7568 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7569 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7570 return false;
7571 };
7572
7573 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7574 self.clear_highlights::<EditPredictionHighlight>(cx);
7575 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7576 true
7577 }
7578
7579 /// Returns true when we're displaying the edit prediction popover below the cursor
7580 /// like we are not previewing and the LSP autocomplete menu is visible
7581 /// or we are in `when_holding_modifier` mode.
7582 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7583 if self.edit_prediction_preview_is_active()
7584 || !self.show_edit_predictions_in_menu()
7585 || !self.edit_predictions_enabled()
7586 {
7587 return false;
7588 }
7589
7590 if self.has_visible_completions_menu() {
7591 return true;
7592 }
7593
7594 has_completion && self.edit_prediction_requires_modifier()
7595 }
7596
7597 fn handle_modifiers_changed(
7598 &mut self,
7599 modifiers: Modifiers,
7600 position_map: &PositionMap,
7601 window: &mut Window,
7602 cx: &mut Context<Self>,
7603 ) {
7604 if self.show_edit_predictions_in_menu() {
7605 self.update_edit_prediction_preview(&modifiers, window, cx);
7606 }
7607
7608 self.update_selection_mode(&modifiers, position_map, window, cx);
7609
7610 let mouse_position = window.mouse_position();
7611 if !position_map.text_hitbox.is_hovered(window) {
7612 return;
7613 }
7614
7615 self.update_hovered_link(
7616 position_map.point_for_position(mouse_position),
7617 &position_map.snapshot,
7618 modifiers,
7619 window,
7620 cx,
7621 )
7622 }
7623
7624 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7625 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7626 if invert {
7627 match multi_cursor_setting {
7628 MultiCursorModifier::Alt => modifiers.alt,
7629 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7630 }
7631 } else {
7632 match multi_cursor_setting {
7633 MultiCursorModifier::Alt => modifiers.secondary(),
7634 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7635 }
7636 }
7637 }
7638
7639 fn columnar_selection_mode(
7640 modifiers: &Modifiers,
7641 cx: &mut Context<Self>,
7642 ) -> Option<ColumnarMode> {
7643 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7644 if Self::multi_cursor_modifier(false, modifiers, cx) {
7645 Some(ColumnarMode::FromMouse)
7646 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7647 Some(ColumnarMode::FromSelection)
7648 } else {
7649 None
7650 }
7651 } else {
7652 None
7653 }
7654 }
7655
7656 fn update_selection_mode(
7657 &mut self,
7658 modifiers: &Modifiers,
7659 position_map: &PositionMap,
7660 window: &mut Window,
7661 cx: &mut Context<Self>,
7662 ) {
7663 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7664 return;
7665 };
7666 if self.selections.pending.is_none() {
7667 return;
7668 }
7669
7670 let mouse_position = window.mouse_position();
7671 let point_for_position = position_map.point_for_position(mouse_position);
7672 let position = point_for_position.previous_valid;
7673
7674 self.select(
7675 SelectPhase::BeginColumnar {
7676 position,
7677 reset: false,
7678 mode,
7679 goal_column: point_for_position.exact_unclipped.column(),
7680 },
7681 window,
7682 cx,
7683 );
7684 }
7685
7686 fn update_edit_prediction_preview(
7687 &mut self,
7688 modifiers: &Modifiers,
7689 window: &mut Window,
7690 cx: &mut Context<Self>,
7691 ) {
7692 let mut modifiers_held = false;
7693 if let Some(accept_keystroke) = self
7694 .accept_edit_prediction_keybind(false, window, cx)
7695 .keystroke()
7696 {
7697 modifiers_held = modifiers_held
7698 || (accept_keystroke.modifiers() == modifiers
7699 && accept_keystroke.modifiers().modified());
7700 };
7701 if let Some(accept_partial_keystroke) = self
7702 .accept_edit_prediction_keybind(true, window, cx)
7703 .keystroke()
7704 {
7705 modifiers_held = modifiers_held
7706 || (accept_partial_keystroke.modifiers() == modifiers
7707 && accept_partial_keystroke.modifiers().modified());
7708 }
7709
7710 if modifiers_held {
7711 if matches!(
7712 self.edit_prediction_preview,
7713 EditPredictionPreview::Inactive { .. }
7714 ) {
7715 self.edit_prediction_preview = EditPredictionPreview::Active {
7716 previous_scroll_position: None,
7717 since: Instant::now(),
7718 };
7719
7720 self.update_visible_edit_prediction(window, cx);
7721 cx.notify();
7722 }
7723 } else if let EditPredictionPreview::Active {
7724 previous_scroll_position,
7725 since,
7726 } = self.edit_prediction_preview
7727 {
7728 if let (Some(previous_scroll_position), Some(position_map)) =
7729 (previous_scroll_position, self.last_position_map.as_ref())
7730 {
7731 self.set_scroll_position(
7732 previous_scroll_position
7733 .scroll_position(&position_map.snapshot.display_snapshot),
7734 window,
7735 cx,
7736 );
7737 }
7738
7739 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7740 released_too_fast: since.elapsed() < Duration::from_millis(200),
7741 };
7742 self.clear_row_highlights::<EditPredictionPreview>();
7743 self.update_visible_edit_prediction(window, cx);
7744 cx.notify();
7745 }
7746 }
7747
7748 fn update_visible_edit_prediction(
7749 &mut self,
7750 _window: &mut Window,
7751 cx: &mut Context<Self>,
7752 ) -> Option<()> {
7753 if DisableAiSettings::get_global(cx).disable_ai {
7754 return None;
7755 }
7756
7757 if self.ime_transaction.is_some() {
7758 self.discard_edit_prediction(false, cx);
7759 return None;
7760 }
7761
7762 let selection = self.selections.newest_anchor();
7763 let cursor = selection.head();
7764 let multibuffer = self.buffer.read(cx).snapshot(cx);
7765 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7766 let excerpt_id = cursor.excerpt_id;
7767
7768 let show_in_menu = self.show_edit_predictions_in_menu();
7769 let completions_menu_has_precedence = !show_in_menu
7770 && (self.context_menu.borrow().is_some()
7771 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7772
7773 if completions_menu_has_precedence
7774 || !offset_selection.is_empty()
7775 || self
7776 .active_edit_prediction
7777 .as_ref()
7778 .is_some_and(|completion| {
7779 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7780 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7781 !invalidation_range.contains(&offset_selection.head())
7782 })
7783 {
7784 self.discard_edit_prediction(false, cx);
7785 return None;
7786 }
7787
7788 self.take_active_edit_prediction(cx);
7789 let Some(provider) = self.edit_prediction_provider() else {
7790 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7791 return None;
7792 };
7793
7794 let (buffer, cursor_buffer_position) =
7795 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7796
7797 self.edit_prediction_settings =
7798 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7799
7800 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7801 self.discard_edit_prediction(false, cx);
7802 return None;
7803 };
7804
7805 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7806
7807 if self.edit_prediction_indent_conflict {
7808 let cursor_point = cursor.to_point(&multibuffer);
7809
7810 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7811
7812 if let Some((_, indent)) = indents.iter().next()
7813 && indent.len == cursor_point.column
7814 {
7815 self.edit_prediction_indent_conflict = false;
7816 }
7817 }
7818
7819 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7820 let edits = edit_prediction
7821 .edits
7822 .into_iter()
7823 .flat_map(|(range, new_text)| {
7824 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7825 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7826 Some((start..end, new_text))
7827 })
7828 .collect::<Vec<_>>();
7829 if edits.is_empty() {
7830 return None;
7831 }
7832
7833 let first_edit_start = edits.first().unwrap().0.start;
7834 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7835 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7836
7837 let last_edit_end = edits.last().unwrap().0.end;
7838 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7839 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7840
7841 let cursor_row = cursor.to_point(&multibuffer).row;
7842
7843 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7844
7845 let mut inlay_ids = Vec::new();
7846 let invalidation_row_range;
7847 let move_invalidation_row_range = if cursor_row < edit_start_row {
7848 Some(cursor_row..edit_end_row)
7849 } else if cursor_row > edit_end_row {
7850 Some(edit_start_row..cursor_row)
7851 } else {
7852 None
7853 };
7854 let supports_jump = self
7855 .edit_prediction_provider
7856 .as_ref()
7857 .map(|provider| provider.provider.supports_jump_to_edit())
7858 .unwrap_or(true);
7859
7860 let is_move = supports_jump
7861 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7862 let completion = if is_move {
7863 invalidation_row_range =
7864 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7865 let target = first_edit_start;
7866 EditPrediction::Move { target, snapshot }
7867 } else {
7868 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7869 && !self.edit_predictions_hidden_for_vim_mode;
7870
7871 if show_completions_in_buffer {
7872 if edits
7873 .iter()
7874 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7875 {
7876 let mut inlays = Vec::new();
7877 for (range, new_text) in &edits {
7878 let inlay = Inlay::edit_prediction(
7879 post_inc(&mut self.next_inlay_id),
7880 range.start,
7881 new_text.as_str(),
7882 );
7883 inlay_ids.push(inlay.id);
7884 inlays.push(inlay);
7885 }
7886
7887 self.splice_inlays(&[], inlays, cx);
7888 } else {
7889 let background_color = cx.theme().status().deleted_background;
7890 self.highlight_text::<EditPredictionHighlight>(
7891 edits.iter().map(|(range, _)| range.clone()).collect(),
7892 HighlightStyle {
7893 background_color: Some(background_color),
7894 ..Default::default()
7895 },
7896 cx,
7897 );
7898 }
7899 }
7900
7901 invalidation_row_range = edit_start_row..edit_end_row;
7902
7903 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7904 if provider.show_tab_accept_marker() {
7905 EditDisplayMode::TabAccept
7906 } else {
7907 EditDisplayMode::Inline
7908 }
7909 } else {
7910 EditDisplayMode::DiffPopover
7911 };
7912
7913 EditPrediction::Edit {
7914 edits,
7915 edit_preview: edit_prediction.edit_preview,
7916 display_mode,
7917 snapshot,
7918 }
7919 };
7920
7921 let invalidation_range = multibuffer
7922 .anchor_before(Point::new(invalidation_row_range.start, 0))
7923 ..multibuffer.anchor_after(Point::new(
7924 invalidation_row_range.end,
7925 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7926 ));
7927
7928 self.stale_edit_prediction_in_menu = None;
7929 self.active_edit_prediction = Some(EditPredictionState {
7930 inlay_ids,
7931 completion,
7932 completion_id: edit_prediction.id,
7933 invalidation_range,
7934 });
7935
7936 cx.notify();
7937
7938 Some(())
7939 }
7940
7941 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7942 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7943 }
7944
7945 fn clear_tasks(&mut self) {
7946 self.tasks.clear()
7947 }
7948
7949 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7950 if self.tasks.insert(key, value).is_some() {
7951 // This case should hopefully be rare, but just in case...
7952 log::error!(
7953 "multiple different run targets found on a single line, only the last target will be rendered"
7954 )
7955 }
7956 }
7957
7958 /// Get all display points of breakpoints that will be rendered within editor
7959 ///
7960 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7961 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7962 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7963 fn active_breakpoints(
7964 &self,
7965 range: Range<DisplayRow>,
7966 window: &mut Window,
7967 cx: &mut Context<Self>,
7968 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7969 let mut breakpoint_display_points = HashMap::default();
7970
7971 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7972 return breakpoint_display_points;
7973 };
7974
7975 let snapshot = self.snapshot(window, cx);
7976
7977 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7978 let Some(project) = self.project() else {
7979 return breakpoint_display_points;
7980 };
7981
7982 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7983 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7984
7985 for (buffer_snapshot, range, excerpt_id) in
7986 multi_buffer_snapshot.range_to_buffer_ranges(range)
7987 {
7988 let Some(buffer) = project
7989 .read(cx)
7990 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7991 else {
7992 continue;
7993 };
7994 let breakpoints = breakpoint_store.read(cx).breakpoints(
7995 &buffer,
7996 Some(
7997 buffer_snapshot.anchor_before(range.start)
7998 ..buffer_snapshot.anchor_after(range.end),
7999 ),
8000 buffer_snapshot,
8001 cx,
8002 );
8003 for (breakpoint, state) in breakpoints {
8004 let multi_buffer_anchor =
8005 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8006 let position = multi_buffer_anchor
8007 .to_point(multi_buffer_snapshot)
8008 .to_display_point(&snapshot);
8009
8010 breakpoint_display_points.insert(
8011 position.row(),
8012 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8013 );
8014 }
8015 }
8016
8017 breakpoint_display_points
8018 }
8019
8020 fn breakpoint_context_menu(
8021 &self,
8022 anchor: Anchor,
8023 window: &mut Window,
8024 cx: &mut Context<Self>,
8025 ) -> Entity<ui::ContextMenu> {
8026 let weak_editor = cx.weak_entity();
8027 let focus_handle = self.focus_handle(cx);
8028
8029 let row = self
8030 .buffer
8031 .read(cx)
8032 .snapshot(cx)
8033 .summary_for_anchor::<Point>(&anchor)
8034 .row;
8035
8036 let breakpoint = self
8037 .breakpoint_at_row(row, window, cx)
8038 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8039
8040 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8041 "Edit Log Breakpoint"
8042 } else {
8043 "Set Log Breakpoint"
8044 };
8045
8046 let condition_breakpoint_msg = if breakpoint
8047 .as_ref()
8048 .is_some_and(|bp| bp.1.condition.is_some())
8049 {
8050 "Edit Condition Breakpoint"
8051 } else {
8052 "Set Condition Breakpoint"
8053 };
8054
8055 let hit_condition_breakpoint_msg = if breakpoint
8056 .as_ref()
8057 .is_some_and(|bp| bp.1.hit_condition.is_some())
8058 {
8059 "Edit Hit Condition Breakpoint"
8060 } else {
8061 "Set Hit Condition Breakpoint"
8062 };
8063
8064 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8065 "Unset Breakpoint"
8066 } else {
8067 "Set Breakpoint"
8068 };
8069
8070 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8071
8072 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8073 BreakpointState::Enabled => Some("Disable"),
8074 BreakpointState::Disabled => Some("Enable"),
8075 });
8076
8077 let (anchor, breakpoint) =
8078 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8079
8080 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8081 menu.on_blur_subscription(Subscription::new(|| {}))
8082 .context(focus_handle)
8083 .when(run_to_cursor, |this| {
8084 let weak_editor = weak_editor.clone();
8085 this.entry("Run to cursor", None, move |window, cx| {
8086 weak_editor
8087 .update(cx, |editor, cx| {
8088 editor.change_selections(
8089 SelectionEffects::no_scroll(),
8090 window,
8091 cx,
8092 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8093 );
8094 })
8095 .ok();
8096
8097 window.dispatch_action(Box::new(RunToCursor), cx);
8098 })
8099 .separator()
8100 })
8101 .when_some(toggle_state_msg, |this, msg| {
8102 this.entry(msg, None, {
8103 let weak_editor = weak_editor.clone();
8104 let breakpoint = breakpoint.clone();
8105 move |_window, cx| {
8106 weak_editor
8107 .update(cx, |this, cx| {
8108 this.edit_breakpoint_at_anchor(
8109 anchor,
8110 breakpoint.as_ref().clone(),
8111 BreakpointEditAction::InvertState,
8112 cx,
8113 );
8114 })
8115 .log_err();
8116 }
8117 })
8118 })
8119 .entry(set_breakpoint_msg, None, {
8120 let weak_editor = weak_editor.clone();
8121 let breakpoint = breakpoint.clone();
8122 move |_window, cx| {
8123 weak_editor
8124 .update(cx, |this, cx| {
8125 this.edit_breakpoint_at_anchor(
8126 anchor,
8127 breakpoint.as_ref().clone(),
8128 BreakpointEditAction::Toggle,
8129 cx,
8130 );
8131 })
8132 .log_err();
8133 }
8134 })
8135 .entry(log_breakpoint_msg, None, {
8136 let breakpoint = breakpoint.clone();
8137 let weak_editor = weak_editor.clone();
8138 move |window, cx| {
8139 weak_editor
8140 .update(cx, |this, cx| {
8141 this.add_edit_breakpoint_block(
8142 anchor,
8143 breakpoint.as_ref(),
8144 BreakpointPromptEditAction::Log,
8145 window,
8146 cx,
8147 );
8148 })
8149 .log_err();
8150 }
8151 })
8152 .entry(condition_breakpoint_msg, None, {
8153 let breakpoint = breakpoint.clone();
8154 let weak_editor = weak_editor.clone();
8155 move |window, cx| {
8156 weak_editor
8157 .update(cx, |this, cx| {
8158 this.add_edit_breakpoint_block(
8159 anchor,
8160 breakpoint.as_ref(),
8161 BreakpointPromptEditAction::Condition,
8162 window,
8163 cx,
8164 );
8165 })
8166 .log_err();
8167 }
8168 })
8169 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8170 weak_editor
8171 .update(cx, |this, cx| {
8172 this.add_edit_breakpoint_block(
8173 anchor,
8174 breakpoint.as_ref(),
8175 BreakpointPromptEditAction::HitCondition,
8176 window,
8177 cx,
8178 );
8179 })
8180 .log_err();
8181 })
8182 })
8183 }
8184
8185 fn render_breakpoint(
8186 &self,
8187 position: Anchor,
8188 row: DisplayRow,
8189 breakpoint: &Breakpoint,
8190 state: Option<BreakpointSessionState>,
8191 cx: &mut Context<Self>,
8192 ) -> IconButton {
8193 let is_rejected = state.is_some_and(|s| !s.verified);
8194 // Is it a breakpoint that shows up when hovering over gutter?
8195 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8196 (false, false),
8197 |PhantomBreakpointIndicator {
8198 is_active,
8199 display_row,
8200 collides_with_existing_breakpoint,
8201 }| {
8202 (
8203 is_active && display_row == row,
8204 collides_with_existing_breakpoint,
8205 )
8206 },
8207 );
8208
8209 let (color, icon) = {
8210 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8211 (false, false) => ui::IconName::DebugBreakpoint,
8212 (true, false) => ui::IconName::DebugLogBreakpoint,
8213 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8214 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8215 };
8216
8217 let color = if is_phantom {
8218 Color::Hint
8219 } else if is_rejected {
8220 Color::Disabled
8221 } else {
8222 Color::Debugger
8223 };
8224
8225 (color, icon)
8226 };
8227
8228 let breakpoint = Arc::from(breakpoint.clone());
8229
8230 let alt_as_text = gpui::Keystroke {
8231 modifiers: Modifiers::secondary_key(),
8232 ..Default::default()
8233 };
8234 let primary_action_text = if breakpoint.is_disabled() {
8235 "Enable breakpoint"
8236 } else if is_phantom && !collides_with_existing {
8237 "Set breakpoint"
8238 } else {
8239 "Unset breakpoint"
8240 };
8241 let focus_handle = self.focus_handle.clone();
8242
8243 let meta = if is_rejected {
8244 SharedString::from("No executable code is associated with this line.")
8245 } else if collides_with_existing && !breakpoint.is_disabled() {
8246 SharedString::from(format!(
8247 "{alt_as_text}-click to disable,\nright-click for more options."
8248 ))
8249 } else {
8250 SharedString::from("Right-click for more options.")
8251 };
8252 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8253 .icon_size(IconSize::XSmall)
8254 .size(ui::ButtonSize::None)
8255 .when(is_rejected, |this| {
8256 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8257 })
8258 .icon_color(color)
8259 .style(ButtonStyle::Transparent)
8260 .on_click(cx.listener({
8261 move |editor, event: &ClickEvent, window, cx| {
8262 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8263 BreakpointEditAction::InvertState
8264 } else {
8265 BreakpointEditAction::Toggle
8266 };
8267
8268 window.focus(&editor.focus_handle(cx));
8269 editor.edit_breakpoint_at_anchor(
8270 position,
8271 breakpoint.as_ref().clone(),
8272 edit_action,
8273 cx,
8274 );
8275 }
8276 }))
8277 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8278 editor.set_breakpoint_context_menu(
8279 row,
8280 Some(position),
8281 event.position(),
8282 window,
8283 cx,
8284 );
8285 }))
8286 .tooltip(move |window, cx| {
8287 Tooltip::with_meta_in(
8288 primary_action_text,
8289 Some(&ToggleBreakpoint),
8290 meta.clone(),
8291 &focus_handle,
8292 window,
8293 cx,
8294 )
8295 })
8296 }
8297
8298 fn build_tasks_context(
8299 project: &Entity<Project>,
8300 buffer: &Entity<Buffer>,
8301 buffer_row: u32,
8302 tasks: &Arc<RunnableTasks>,
8303 cx: &mut Context<Self>,
8304 ) -> Task<Option<task::TaskContext>> {
8305 let position = Point::new(buffer_row, tasks.column);
8306 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8307 let location = Location {
8308 buffer: buffer.clone(),
8309 range: range_start..range_start,
8310 };
8311 // Fill in the environmental variables from the tree-sitter captures
8312 let mut captured_task_variables = TaskVariables::default();
8313 for (capture_name, value) in tasks.extra_variables.clone() {
8314 captured_task_variables.insert(
8315 task::VariableName::Custom(capture_name.into()),
8316 value.clone(),
8317 );
8318 }
8319 project.update(cx, |project, cx| {
8320 project.task_store().update(cx, |task_store, cx| {
8321 task_store.task_context_for_location(captured_task_variables, location, cx)
8322 })
8323 })
8324 }
8325
8326 pub fn spawn_nearest_task(
8327 &mut self,
8328 action: &SpawnNearestTask,
8329 window: &mut Window,
8330 cx: &mut Context<Self>,
8331 ) {
8332 let Some((workspace, _)) = self.workspace.clone() else {
8333 return;
8334 };
8335 let Some(project) = self.project.clone() else {
8336 return;
8337 };
8338
8339 // Try to find a closest, enclosing node using tree-sitter that has a task
8340 let Some((buffer, buffer_row, tasks)) = self
8341 .find_enclosing_node_task(cx)
8342 // Or find the task that's closest in row-distance.
8343 .or_else(|| self.find_closest_task(cx))
8344 else {
8345 return;
8346 };
8347
8348 let reveal_strategy = action.reveal;
8349 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8350 cx.spawn_in(window, async move |_, cx| {
8351 let context = task_context.await?;
8352 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8353
8354 let resolved = &mut resolved_task.resolved;
8355 resolved.reveal = reveal_strategy;
8356
8357 workspace
8358 .update_in(cx, |workspace, window, cx| {
8359 workspace.schedule_resolved_task(
8360 task_source_kind,
8361 resolved_task,
8362 false,
8363 window,
8364 cx,
8365 );
8366 })
8367 .ok()
8368 })
8369 .detach();
8370 }
8371
8372 fn find_closest_task(
8373 &mut self,
8374 cx: &mut Context<Self>,
8375 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8376 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8377
8378 let ((buffer_id, row), tasks) = self
8379 .tasks
8380 .iter()
8381 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8382
8383 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8384 let tasks = Arc::new(tasks.to_owned());
8385 Some((buffer, *row, tasks))
8386 }
8387
8388 fn find_enclosing_node_task(
8389 &mut self,
8390 cx: &mut Context<Self>,
8391 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8392 let snapshot = self.buffer.read(cx).snapshot(cx);
8393 let offset = self.selections.newest::<usize>(cx).head();
8394 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8395 let buffer_id = excerpt.buffer().remote_id();
8396
8397 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8398 let mut cursor = layer.node().walk();
8399
8400 while cursor.goto_first_child_for_byte(offset).is_some() {
8401 if cursor.node().end_byte() == offset {
8402 cursor.goto_next_sibling();
8403 }
8404 }
8405
8406 // Ascend to the smallest ancestor that contains the range and has a task.
8407 loop {
8408 let node = cursor.node();
8409 let node_range = node.byte_range();
8410 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8411
8412 // Check if this node contains our offset
8413 if node_range.start <= offset && node_range.end >= offset {
8414 // If it contains offset, check for task
8415 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8416 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8417 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8418 }
8419 }
8420
8421 if !cursor.goto_parent() {
8422 break;
8423 }
8424 }
8425 None
8426 }
8427
8428 fn render_run_indicator(
8429 &self,
8430 _style: &EditorStyle,
8431 is_active: bool,
8432 row: DisplayRow,
8433 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8434 cx: &mut Context<Self>,
8435 ) -> IconButton {
8436 let color = Color::Muted;
8437 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8438
8439 IconButton::new(
8440 ("run_indicator", row.0 as usize),
8441 ui::IconName::PlayOutlined,
8442 )
8443 .shape(ui::IconButtonShape::Square)
8444 .icon_size(IconSize::XSmall)
8445 .icon_color(color)
8446 .toggle_state(is_active)
8447 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8448 let quick_launch = match e {
8449 ClickEvent::Keyboard(_) => true,
8450 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8451 };
8452
8453 window.focus(&editor.focus_handle(cx));
8454 editor.toggle_code_actions(
8455 &ToggleCodeActions {
8456 deployed_from: Some(CodeActionSource::RunMenu(row)),
8457 quick_launch,
8458 },
8459 window,
8460 cx,
8461 );
8462 }))
8463 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8464 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8465 }))
8466 }
8467
8468 pub fn context_menu_visible(&self) -> bool {
8469 !self.edit_prediction_preview_is_active()
8470 && self
8471 .context_menu
8472 .borrow()
8473 .as_ref()
8474 .is_some_and(|menu| menu.visible())
8475 }
8476
8477 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8478 self.context_menu
8479 .borrow()
8480 .as_ref()
8481 .map(|menu| menu.origin())
8482 }
8483
8484 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8485 self.context_menu_options = Some(options);
8486 }
8487
8488 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8489 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8490
8491 fn render_edit_prediction_popover(
8492 &mut self,
8493 text_bounds: &Bounds<Pixels>,
8494 content_origin: gpui::Point<Pixels>,
8495 right_margin: Pixels,
8496 editor_snapshot: &EditorSnapshot,
8497 visible_row_range: Range<DisplayRow>,
8498 scroll_top: f32,
8499 scroll_bottom: f32,
8500 line_layouts: &[LineWithInvisibles],
8501 line_height: Pixels,
8502 scroll_pixel_position: gpui::Point<Pixels>,
8503 newest_selection_head: Option<DisplayPoint>,
8504 editor_width: Pixels,
8505 style: &EditorStyle,
8506 window: &mut Window,
8507 cx: &mut App,
8508 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8509 if self.mode().is_minimap() {
8510 return None;
8511 }
8512 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8513
8514 if self.edit_prediction_visible_in_cursor_popover(true) {
8515 return None;
8516 }
8517
8518 match &active_edit_prediction.completion {
8519 EditPrediction::Move { target, .. } => {
8520 let target_display_point = target.to_display_point(editor_snapshot);
8521
8522 if self.edit_prediction_requires_modifier() {
8523 if !self.edit_prediction_preview_is_active() {
8524 return None;
8525 }
8526
8527 self.render_edit_prediction_modifier_jump_popover(
8528 text_bounds,
8529 content_origin,
8530 visible_row_range,
8531 line_layouts,
8532 line_height,
8533 scroll_pixel_position,
8534 newest_selection_head,
8535 target_display_point,
8536 window,
8537 cx,
8538 )
8539 } else {
8540 self.render_edit_prediction_eager_jump_popover(
8541 text_bounds,
8542 content_origin,
8543 editor_snapshot,
8544 visible_row_range,
8545 scroll_top,
8546 scroll_bottom,
8547 line_height,
8548 scroll_pixel_position,
8549 target_display_point,
8550 editor_width,
8551 window,
8552 cx,
8553 )
8554 }
8555 }
8556 EditPrediction::Edit {
8557 display_mode: EditDisplayMode::Inline,
8558 ..
8559 } => None,
8560 EditPrediction::Edit {
8561 display_mode: EditDisplayMode::TabAccept,
8562 edits,
8563 ..
8564 } => {
8565 let range = &edits.first()?.0;
8566 let target_display_point = range.end.to_display_point(editor_snapshot);
8567
8568 self.render_edit_prediction_end_of_line_popover(
8569 "Accept",
8570 editor_snapshot,
8571 visible_row_range,
8572 target_display_point,
8573 line_height,
8574 scroll_pixel_position,
8575 content_origin,
8576 editor_width,
8577 window,
8578 cx,
8579 )
8580 }
8581 EditPrediction::Edit {
8582 edits,
8583 edit_preview,
8584 display_mode: EditDisplayMode::DiffPopover,
8585 snapshot,
8586 } => self.render_edit_prediction_diff_popover(
8587 text_bounds,
8588 content_origin,
8589 right_margin,
8590 editor_snapshot,
8591 visible_row_range,
8592 line_layouts,
8593 line_height,
8594 scroll_pixel_position,
8595 newest_selection_head,
8596 editor_width,
8597 style,
8598 edits,
8599 edit_preview,
8600 snapshot,
8601 window,
8602 cx,
8603 ),
8604 }
8605 }
8606
8607 fn render_edit_prediction_modifier_jump_popover(
8608 &mut self,
8609 text_bounds: &Bounds<Pixels>,
8610 content_origin: gpui::Point<Pixels>,
8611 visible_row_range: Range<DisplayRow>,
8612 line_layouts: &[LineWithInvisibles],
8613 line_height: Pixels,
8614 scroll_pixel_position: gpui::Point<Pixels>,
8615 newest_selection_head: Option<DisplayPoint>,
8616 target_display_point: DisplayPoint,
8617 window: &mut Window,
8618 cx: &mut App,
8619 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8620 let scrolled_content_origin =
8621 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8622
8623 const SCROLL_PADDING_Y: Pixels = px(12.);
8624
8625 if target_display_point.row() < visible_row_range.start {
8626 return self.render_edit_prediction_scroll_popover(
8627 |_| SCROLL_PADDING_Y,
8628 IconName::ArrowUp,
8629 visible_row_range,
8630 line_layouts,
8631 newest_selection_head,
8632 scrolled_content_origin,
8633 window,
8634 cx,
8635 );
8636 } else if target_display_point.row() >= visible_row_range.end {
8637 return self.render_edit_prediction_scroll_popover(
8638 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8639 IconName::ArrowDown,
8640 visible_row_range,
8641 line_layouts,
8642 newest_selection_head,
8643 scrolled_content_origin,
8644 window,
8645 cx,
8646 );
8647 }
8648
8649 const POLE_WIDTH: Pixels = px(2.);
8650
8651 let line_layout =
8652 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8653 let target_column = target_display_point.column() as usize;
8654
8655 let target_x = line_layout.x_for_index(target_column);
8656 let target_y =
8657 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8658
8659 let flag_on_right = target_x < text_bounds.size.width / 2.;
8660
8661 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8662 border_color.l += 0.001;
8663
8664 let mut element = v_flex()
8665 .items_end()
8666 .when(flag_on_right, |el| el.items_start())
8667 .child(if flag_on_right {
8668 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8669 .rounded_bl(px(0.))
8670 .rounded_tl(px(0.))
8671 .border_l_2()
8672 .border_color(border_color)
8673 } else {
8674 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8675 .rounded_br(px(0.))
8676 .rounded_tr(px(0.))
8677 .border_r_2()
8678 .border_color(border_color)
8679 })
8680 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8681 .into_any();
8682
8683 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8684
8685 let mut origin = scrolled_content_origin + point(target_x, target_y)
8686 - point(
8687 if flag_on_right {
8688 POLE_WIDTH
8689 } else {
8690 size.width - POLE_WIDTH
8691 },
8692 size.height - line_height,
8693 );
8694
8695 origin.x = origin.x.max(content_origin.x);
8696
8697 element.prepaint_at(origin, window, cx);
8698
8699 Some((element, origin))
8700 }
8701
8702 fn render_edit_prediction_scroll_popover(
8703 &mut self,
8704 to_y: impl Fn(Size<Pixels>) -> Pixels,
8705 scroll_icon: IconName,
8706 visible_row_range: Range<DisplayRow>,
8707 line_layouts: &[LineWithInvisibles],
8708 newest_selection_head: Option<DisplayPoint>,
8709 scrolled_content_origin: gpui::Point<Pixels>,
8710 window: &mut Window,
8711 cx: &mut App,
8712 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8713 let mut element = self
8714 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8715 .into_any();
8716
8717 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8718
8719 let cursor = newest_selection_head?;
8720 let cursor_row_layout =
8721 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8722 let cursor_column = cursor.column() as usize;
8723
8724 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8725
8726 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8727
8728 element.prepaint_at(origin, window, cx);
8729 Some((element, origin))
8730 }
8731
8732 fn render_edit_prediction_eager_jump_popover(
8733 &mut self,
8734 text_bounds: &Bounds<Pixels>,
8735 content_origin: gpui::Point<Pixels>,
8736 editor_snapshot: &EditorSnapshot,
8737 visible_row_range: Range<DisplayRow>,
8738 scroll_top: f32,
8739 scroll_bottom: f32,
8740 line_height: Pixels,
8741 scroll_pixel_position: gpui::Point<Pixels>,
8742 target_display_point: DisplayPoint,
8743 editor_width: Pixels,
8744 window: &mut Window,
8745 cx: &mut App,
8746 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8747 if target_display_point.row().as_f32() < scroll_top {
8748 let mut element = self
8749 .render_edit_prediction_line_popover(
8750 "Jump to Edit",
8751 Some(IconName::ArrowUp),
8752 window,
8753 cx,
8754 )?
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758 let offset = point(
8759 (text_bounds.size.width - size.width) / 2.,
8760 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8761 );
8762
8763 let origin = text_bounds.origin + offset;
8764 element.prepaint_at(origin, window, cx);
8765 Some((element, origin))
8766 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8767 let mut element = self
8768 .render_edit_prediction_line_popover(
8769 "Jump to Edit",
8770 Some(IconName::ArrowDown),
8771 window,
8772 cx,
8773 )?
8774 .into_any();
8775
8776 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8777 let offset = point(
8778 (text_bounds.size.width - size.width) / 2.,
8779 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8780 );
8781
8782 let origin = text_bounds.origin + offset;
8783 element.prepaint_at(origin, window, cx);
8784 Some((element, origin))
8785 } else {
8786 self.render_edit_prediction_end_of_line_popover(
8787 "Jump to Edit",
8788 editor_snapshot,
8789 visible_row_range,
8790 target_display_point,
8791 line_height,
8792 scroll_pixel_position,
8793 content_origin,
8794 editor_width,
8795 window,
8796 cx,
8797 )
8798 }
8799 }
8800
8801 fn render_edit_prediction_end_of_line_popover(
8802 self: &mut Editor,
8803 label: &'static str,
8804 editor_snapshot: &EditorSnapshot,
8805 visible_row_range: Range<DisplayRow>,
8806 target_display_point: DisplayPoint,
8807 line_height: Pixels,
8808 scroll_pixel_position: gpui::Point<Pixels>,
8809 content_origin: gpui::Point<Pixels>,
8810 editor_width: Pixels,
8811 window: &mut Window,
8812 cx: &mut App,
8813 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8814 let target_line_end = DisplayPoint::new(
8815 target_display_point.row(),
8816 editor_snapshot.line_len(target_display_point.row()),
8817 );
8818
8819 let mut element = self
8820 .render_edit_prediction_line_popover(label, None, window, cx)?
8821 .into_any();
8822
8823 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8824
8825 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8826
8827 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8828 let mut origin = start_point
8829 + line_origin
8830 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8831 origin.x = origin.x.max(content_origin.x);
8832
8833 let max_x = content_origin.x + editor_width - size.width;
8834
8835 if origin.x > max_x {
8836 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8837
8838 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8839 origin.y += offset;
8840 IconName::ArrowUp
8841 } else {
8842 origin.y -= offset;
8843 IconName::ArrowDown
8844 };
8845
8846 element = self
8847 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8848 .into_any();
8849
8850 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8851
8852 origin.x = content_origin.x + editor_width - size.width - px(2.);
8853 }
8854
8855 element.prepaint_at(origin, window, cx);
8856 Some((element, origin))
8857 }
8858
8859 fn render_edit_prediction_diff_popover(
8860 self: &Editor,
8861 text_bounds: &Bounds<Pixels>,
8862 content_origin: gpui::Point<Pixels>,
8863 right_margin: Pixels,
8864 editor_snapshot: &EditorSnapshot,
8865 visible_row_range: Range<DisplayRow>,
8866 line_layouts: &[LineWithInvisibles],
8867 line_height: Pixels,
8868 scroll_pixel_position: gpui::Point<Pixels>,
8869 newest_selection_head: Option<DisplayPoint>,
8870 editor_width: Pixels,
8871 style: &EditorStyle,
8872 edits: &Vec<(Range<Anchor>, String)>,
8873 edit_preview: &Option<language::EditPreview>,
8874 snapshot: &language::BufferSnapshot,
8875 window: &mut Window,
8876 cx: &mut App,
8877 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8878 let edit_start = edits
8879 .first()
8880 .unwrap()
8881 .0
8882 .start
8883 .to_display_point(editor_snapshot);
8884 let edit_end = edits
8885 .last()
8886 .unwrap()
8887 .0
8888 .end
8889 .to_display_point(editor_snapshot);
8890
8891 let is_visible = visible_row_range.contains(&edit_start.row())
8892 || visible_row_range.contains(&edit_end.row());
8893 if !is_visible {
8894 return None;
8895 }
8896
8897 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8898 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8899 } else {
8900 // Fallback for providers without edit_preview
8901 crate::edit_prediction_fallback_text(edits, cx)
8902 };
8903
8904 let styled_text = highlighted_edits.to_styled_text(&style.text);
8905 let line_count = highlighted_edits.text.lines().count();
8906
8907 const BORDER_WIDTH: Pixels = px(1.);
8908
8909 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8910 let has_keybind = keybind.is_some();
8911
8912 let mut element = h_flex()
8913 .items_start()
8914 .child(
8915 h_flex()
8916 .bg(cx.theme().colors().editor_background)
8917 .border(BORDER_WIDTH)
8918 .shadow_xs()
8919 .border_color(cx.theme().colors().border)
8920 .rounded_l_lg()
8921 .when(line_count > 1, |el| el.rounded_br_lg())
8922 .pr_1()
8923 .child(styled_text),
8924 )
8925 .child(
8926 h_flex()
8927 .h(line_height + BORDER_WIDTH * 2.)
8928 .px_1p5()
8929 .gap_1()
8930 // Workaround: For some reason, there's a gap if we don't do this
8931 .ml(-BORDER_WIDTH)
8932 .shadow(vec![gpui::BoxShadow {
8933 color: gpui::black().opacity(0.05),
8934 offset: point(px(1.), px(1.)),
8935 blur_radius: px(2.),
8936 spread_radius: px(0.),
8937 }])
8938 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8939 .border(BORDER_WIDTH)
8940 .border_color(cx.theme().colors().border)
8941 .rounded_r_lg()
8942 .id("edit_prediction_diff_popover_keybind")
8943 .when(!has_keybind, |el| {
8944 let status_colors = cx.theme().status();
8945
8946 el.bg(status_colors.error_background)
8947 .border_color(status_colors.error.opacity(0.6))
8948 .child(Icon::new(IconName::Info).color(Color::Error))
8949 .cursor_default()
8950 .hoverable_tooltip(move |_window, cx| {
8951 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8952 })
8953 })
8954 .children(keybind),
8955 )
8956 .into_any();
8957
8958 let longest_row =
8959 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8960 let longest_line_width = if visible_row_range.contains(&longest_row) {
8961 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8962 } else {
8963 layout_line(
8964 longest_row,
8965 editor_snapshot,
8966 style,
8967 editor_width,
8968 |_| false,
8969 window,
8970 cx,
8971 )
8972 .width
8973 };
8974
8975 let viewport_bounds =
8976 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8977 right: -right_margin,
8978 ..Default::default()
8979 });
8980
8981 let x_after_longest =
8982 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8983 - scroll_pixel_position.x;
8984
8985 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8986
8987 // Fully visible if it can be displayed within the window (allow overlapping other
8988 // panes). However, this is only allowed if the popover starts within text_bounds.
8989 let can_position_to_the_right = x_after_longest < text_bounds.right()
8990 && x_after_longest + element_bounds.width < viewport_bounds.right();
8991
8992 let mut origin = if can_position_to_the_right {
8993 point(
8994 x_after_longest,
8995 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8996 - scroll_pixel_position.y,
8997 )
8998 } else {
8999 let cursor_row = newest_selection_head.map(|head| head.row());
9000 let above_edit = edit_start
9001 .row()
9002 .0
9003 .checked_sub(line_count as u32)
9004 .map(DisplayRow);
9005 let below_edit = Some(edit_end.row() + 1);
9006 let above_cursor =
9007 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9008 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9009
9010 // Place the edit popover adjacent to the edit if there is a location
9011 // available that is onscreen and does not obscure the cursor. Otherwise,
9012 // place it adjacent to the cursor.
9013 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9014 .into_iter()
9015 .flatten()
9016 .find(|&start_row| {
9017 let end_row = start_row + line_count as u32;
9018 visible_row_range.contains(&start_row)
9019 && visible_row_range.contains(&end_row)
9020 && cursor_row
9021 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9022 })?;
9023
9024 content_origin
9025 + point(
9026 -scroll_pixel_position.x,
9027 row_target.as_f32() * line_height - scroll_pixel_position.y,
9028 )
9029 };
9030
9031 origin.x -= BORDER_WIDTH;
9032
9033 window.defer_draw(element, origin, 1);
9034
9035 // Do not return an element, since it will already be drawn due to defer_draw.
9036 None
9037 }
9038
9039 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9040 px(30.)
9041 }
9042
9043 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9044 if self.read_only(cx) {
9045 cx.theme().players().read_only()
9046 } else {
9047 self.style.as_ref().unwrap().local_player
9048 }
9049 }
9050
9051 fn render_edit_prediction_accept_keybind(
9052 &self,
9053 window: &mut Window,
9054 cx: &App,
9055 ) -> Option<AnyElement> {
9056 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9057 let accept_keystroke = accept_binding.keystroke()?;
9058
9059 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9060
9061 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9062 Color::Accent
9063 } else {
9064 Color::Muted
9065 };
9066
9067 h_flex()
9068 .px_0p5()
9069 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9070 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9071 .text_size(TextSize::XSmall.rems(cx))
9072 .child(h_flex().children(ui::render_modifiers(
9073 accept_keystroke.modifiers(),
9074 PlatformStyle::platform(),
9075 Some(modifiers_color),
9076 Some(IconSize::XSmall.rems().into()),
9077 true,
9078 )))
9079 .when(is_platform_style_mac, |parent| {
9080 parent.child(accept_keystroke.key().to_string())
9081 })
9082 .when(!is_platform_style_mac, |parent| {
9083 parent.child(
9084 Key::new(
9085 util::capitalize(accept_keystroke.key()),
9086 Some(Color::Default),
9087 )
9088 .size(Some(IconSize::XSmall.rems().into())),
9089 )
9090 })
9091 .into_any()
9092 .into()
9093 }
9094
9095 fn render_edit_prediction_line_popover(
9096 &self,
9097 label: impl Into<SharedString>,
9098 icon: Option<IconName>,
9099 window: &mut Window,
9100 cx: &App,
9101 ) -> Option<Stateful<Div>> {
9102 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9103
9104 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9105 let has_keybind = keybind.is_some();
9106
9107 let result = h_flex()
9108 .id("ep-line-popover")
9109 .py_0p5()
9110 .pl_1()
9111 .pr(padding_right)
9112 .gap_1()
9113 .rounded_md()
9114 .border_1()
9115 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9116 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9117 .shadow_xs()
9118 .when(!has_keybind, |el| {
9119 let status_colors = cx.theme().status();
9120
9121 el.bg(status_colors.error_background)
9122 .border_color(status_colors.error.opacity(0.6))
9123 .pl_2()
9124 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9125 .cursor_default()
9126 .hoverable_tooltip(move |_window, cx| {
9127 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9128 })
9129 })
9130 .children(keybind)
9131 .child(
9132 Label::new(label)
9133 .size(LabelSize::Small)
9134 .when(!has_keybind, |el| {
9135 el.color(cx.theme().status().error.into()).strikethrough()
9136 }),
9137 )
9138 .when(!has_keybind, |el| {
9139 el.child(
9140 h_flex().ml_1().child(
9141 Icon::new(IconName::Info)
9142 .size(IconSize::Small)
9143 .color(cx.theme().status().error.into()),
9144 ),
9145 )
9146 })
9147 .when_some(icon, |element, icon| {
9148 element.child(
9149 div()
9150 .mt(px(1.5))
9151 .child(Icon::new(icon).size(IconSize::Small)),
9152 )
9153 });
9154
9155 Some(result)
9156 }
9157
9158 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9159 let accent_color = cx.theme().colors().text_accent;
9160 let editor_bg_color = cx.theme().colors().editor_background;
9161 editor_bg_color.blend(accent_color.opacity(0.1))
9162 }
9163
9164 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9165 let accent_color = cx.theme().colors().text_accent;
9166 let editor_bg_color = cx.theme().colors().editor_background;
9167 editor_bg_color.blend(accent_color.opacity(0.6))
9168 }
9169 fn get_prediction_provider_icon_name(
9170 provider: &Option<RegisteredEditPredictionProvider>,
9171 ) -> IconName {
9172 match provider {
9173 Some(provider) => match provider.provider.name() {
9174 "copilot" => IconName::Copilot,
9175 "supermaven" => IconName::Supermaven,
9176 _ => IconName::ZedPredict,
9177 },
9178 None => IconName::ZedPredict,
9179 }
9180 }
9181
9182 fn render_edit_prediction_cursor_popover(
9183 &self,
9184 min_width: Pixels,
9185 max_width: Pixels,
9186 cursor_point: Point,
9187 style: &EditorStyle,
9188 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9189 _window: &Window,
9190 cx: &mut Context<Editor>,
9191 ) -> Option<AnyElement> {
9192 let provider = self.edit_prediction_provider.as_ref()?;
9193 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9194
9195 let is_refreshing = provider.provider.is_refreshing(cx);
9196
9197 fn pending_completion_container(icon: IconName) -> Div {
9198 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9199 }
9200
9201 let completion = match &self.active_edit_prediction {
9202 Some(prediction) => {
9203 if !self.has_visible_completions_menu() {
9204 const RADIUS: Pixels = px(6.);
9205 const BORDER_WIDTH: Pixels = px(1.);
9206
9207 return Some(
9208 h_flex()
9209 .elevation_2(cx)
9210 .border(BORDER_WIDTH)
9211 .border_color(cx.theme().colors().border)
9212 .when(accept_keystroke.is_none(), |el| {
9213 el.border_color(cx.theme().status().error)
9214 })
9215 .rounded(RADIUS)
9216 .rounded_tl(px(0.))
9217 .overflow_hidden()
9218 .child(div().px_1p5().child(match &prediction.completion {
9219 EditPrediction::Move { target, snapshot } => {
9220 use text::ToPoint as _;
9221 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9222 {
9223 Icon::new(IconName::ZedPredictDown)
9224 } else {
9225 Icon::new(IconName::ZedPredictUp)
9226 }
9227 }
9228 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9229 }))
9230 .child(
9231 h_flex()
9232 .gap_1()
9233 .py_1()
9234 .px_2()
9235 .rounded_r(RADIUS - BORDER_WIDTH)
9236 .border_l_1()
9237 .border_color(cx.theme().colors().border)
9238 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9239 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9240 el.child(
9241 Label::new("Hold")
9242 .size(LabelSize::Small)
9243 .when(accept_keystroke.is_none(), |el| {
9244 el.strikethrough()
9245 })
9246 .line_height_style(LineHeightStyle::UiLabel),
9247 )
9248 })
9249 .id("edit_prediction_cursor_popover_keybind")
9250 .when(accept_keystroke.is_none(), |el| {
9251 let status_colors = cx.theme().status();
9252
9253 el.bg(status_colors.error_background)
9254 .border_color(status_colors.error.opacity(0.6))
9255 .child(Icon::new(IconName::Info).color(Color::Error))
9256 .cursor_default()
9257 .hoverable_tooltip(move |_window, cx| {
9258 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9259 .into()
9260 })
9261 })
9262 .when_some(
9263 accept_keystroke.as_ref(),
9264 |el, accept_keystroke| {
9265 el.child(h_flex().children(ui::render_modifiers(
9266 accept_keystroke.modifiers(),
9267 PlatformStyle::platform(),
9268 Some(Color::Default),
9269 Some(IconSize::XSmall.rems().into()),
9270 false,
9271 )))
9272 },
9273 ),
9274 )
9275 .into_any(),
9276 );
9277 }
9278
9279 self.render_edit_prediction_cursor_popover_preview(
9280 prediction,
9281 cursor_point,
9282 style,
9283 cx,
9284 )?
9285 }
9286
9287 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9288 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9289 stale_completion,
9290 cursor_point,
9291 style,
9292 cx,
9293 )?,
9294
9295 None => pending_completion_container(provider_icon)
9296 .child(Label::new("...").size(LabelSize::Small)),
9297 },
9298
9299 None => pending_completion_container(provider_icon)
9300 .child(Label::new("...").size(LabelSize::Small)),
9301 };
9302
9303 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9304 completion
9305 .with_animation(
9306 "loading-completion",
9307 Animation::new(Duration::from_secs(2))
9308 .repeat()
9309 .with_easing(pulsating_between(0.4, 0.8)),
9310 |label, delta| label.opacity(delta),
9311 )
9312 .into_any_element()
9313 } else {
9314 completion.into_any_element()
9315 };
9316
9317 let has_completion = self.active_edit_prediction.is_some();
9318
9319 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9320 Some(
9321 h_flex()
9322 .min_w(min_width)
9323 .max_w(max_width)
9324 .flex_1()
9325 .elevation_2(cx)
9326 .border_color(cx.theme().colors().border)
9327 .child(
9328 div()
9329 .flex_1()
9330 .py_1()
9331 .px_2()
9332 .overflow_hidden()
9333 .child(completion),
9334 )
9335 .when_some(accept_keystroke, |el, accept_keystroke| {
9336 if !accept_keystroke.modifiers().modified() {
9337 return el;
9338 }
9339
9340 el.child(
9341 h_flex()
9342 .h_full()
9343 .border_l_1()
9344 .rounded_r_lg()
9345 .border_color(cx.theme().colors().border)
9346 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9347 .gap_1()
9348 .py_1()
9349 .px_2()
9350 .child(
9351 h_flex()
9352 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9353 .when(is_platform_style_mac, |parent| parent.gap_1())
9354 .child(h_flex().children(ui::render_modifiers(
9355 accept_keystroke.modifiers(),
9356 PlatformStyle::platform(),
9357 Some(if !has_completion {
9358 Color::Muted
9359 } else {
9360 Color::Default
9361 }),
9362 None,
9363 false,
9364 ))),
9365 )
9366 .child(Label::new("Preview").into_any_element())
9367 .opacity(if has_completion { 1.0 } else { 0.4 }),
9368 )
9369 })
9370 .into_any(),
9371 )
9372 }
9373
9374 fn render_edit_prediction_cursor_popover_preview(
9375 &self,
9376 completion: &EditPredictionState,
9377 cursor_point: Point,
9378 style: &EditorStyle,
9379 cx: &mut Context<Editor>,
9380 ) -> Option<Div> {
9381 use text::ToPoint as _;
9382
9383 fn render_relative_row_jump(
9384 prefix: impl Into<String>,
9385 current_row: u32,
9386 target_row: u32,
9387 ) -> Div {
9388 let (row_diff, arrow) = if target_row < current_row {
9389 (current_row - target_row, IconName::ArrowUp)
9390 } else {
9391 (target_row - current_row, IconName::ArrowDown)
9392 };
9393
9394 h_flex()
9395 .child(
9396 Label::new(format!("{}{}", prefix.into(), row_diff))
9397 .color(Color::Muted)
9398 .size(LabelSize::Small),
9399 )
9400 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9401 }
9402
9403 let supports_jump = self
9404 .edit_prediction_provider
9405 .as_ref()
9406 .map(|provider| provider.provider.supports_jump_to_edit())
9407 .unwrap_or(true);
9408
9409 match &completion.completion {
9410 EditPrediction::Move {
9411 target, snapshot, ..
9412 } => {
9413 if !supports_jump {
9414 return None;
9415 }
9416
9417 Some(
9418 h_flex()
9419 .px_2()
9420 .gap_2()
9421 .flex_1()
9422 .child(
9423 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9424 Icon::new(IconName::ZedPredictDown)
9425 } else {
9426 Icon::new(IconName::ZedPredictUp)
9427 },
9428 )
9429 .child(Label::new("Jump to Edit")),
9430 )
9431 }
9432
9433 EditPrediction::Edit {
9434 edits,
9435 edit_preview,
9436 snapshot,
9437 display_mode: _,
9438 } => {
9439 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9440
9441 let (highlighted_edits, has_more_lines) =
9442 if let Some(edit_preview) = edit_preview.as_ref() {
9443 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9444 .first_line_preview()
9445 } else {
9446 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9447 };
9448
9449 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9450 .with_default_highlights(&style.text, highlighted_edits.highlights);
9451
9452 let preview = h_flex()
9453 .gap_1()
9454 .min_w_16()
9455 .child(styled_text)
9456 .when(has_more_lines, |parent| parent.child("…"));
9457
9458 let left = if supports_jump && first_edit_row != cursor_point.row {
9459 render_relative_row_jump("", cursor_point.row, first_edit_row)
9460 .into_any_element()
9461 } else {
9462 let icon_name =
9463 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9464 Icon::new(icon_name).into_any_element()
9465 };
9466
9467 Some(
9468 h_flex()
9469 .h_full()
9470 .flex_1()
9471 .gap_2()
9472 .pr_1()
9473 .overflow_x_hidden()
9474 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9475 .child(left)
9476 .child(preview),
9477 )
9478 }
9479 }
9480 }
9481
9482 pub fn render_context_menu(
9483 &self,
9484 style: &EditorStyle,
9485 max_height_in_lines: u32,
9486 window: &mut Window,
9487 cx: &mut Context<Editor>,
9488 ) -> Option<AnyElement> {
9489 let menu = self.context_menu.borrow();
9490 let menu = menu.as_ref()?;
9491 if !menu.visible() {
9492 return None;
9493 };
9494 Some(menu.render(style, max_height_in_lines, window, cx))
9495 }
9496
9497 fn render_context_menu_aside(
9498 &mut self,
9499 max_size: Size<Pixels>,
9500 window: &mut Window,
9501 cx: &mut Context<Editor>,
9502 ) -> Option<AnyElement> {
9503 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9504 if menu.visible() {
9505 menu.render_aside(max_size, window, cx)
9506 } else {
9507 None
9508 }
9509 })
9510 }
9511
9512 fn hide_context_menu(
9513 &mut self,
9514 window: &mut Window,
9515 cx: &mut Context<Self>,
9516 ) -> Option<CodeContextMenu> {
9517 cx.notify();
9518 self.completion_tasks.clear();
9519 let context_menu = self.context_menu.borrow_mut().take();
9520 self.stale_edit_prediction_in_menu.take();
9521 self.update_visible_edit_prediction(window, cx);
9522 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9523 && let Some(completion_provider) = &self.completion_provider
9524 {
9525 completion_provider.selection_changed(None, window, cx);
9526 }
9527 context_menu
9528 }
9529
9530 fn show_snippet_choices(
9531 &mut self,
9532 choices: &Vec<String>,
9533 selection: Range<Anchor>,
9534 cx: &mut Context<Self>,
9535 ) {
9536 let Some((_, buffer, _)) = self
9537 .buffer()
9538 .read(cx)
9539 .excerpt_containing(selection.start, cx)
9540 else {
9541 return;
9542 };
9543 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9544 else {
9545 return;
9546 };
9547 if buffer != end_buffer {
9548 log::error!("expected anchor range to have matching buffer IDs");
9549 return;
9550 }
9551
9552 let id = post_inc(&mut self.next_completion_id);
9553 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9554 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9555 CompletionsMenu::new_snippet_choices(
9556 id,
9557 true,
9558 choices,
9559 selection,
9560 buffer,
9561 snippet_sort_order,
9562 ),
9563 ));
9564 }
9565
9566 pub fn insert_snippet(
9567 &mut self,
9568 insertion_ranges: &[Range<usize>],
9569 snippet: Snippet,
9570 window: &mut Window,
9571 cx: &mut Context<Self>,
9572 ) -> Result<()> {
9573 struct Tabstop<T> {
9574 is_end_tabstop: bool,
9575 ranges: Vec<Range<T>>,
9576 choices: Option<Vec<String>>,
9577 }
9578
9579 let tabstops = self.buffer.update(cx, |buffer, cx| {
9580 let snippet_text: Arc<str> = snippet.text.clone().into();
9581 let edits = insertion_ranges
9582 .iter()
9583 .cloned()
9584 .map(|range| (range, snippet_text.clone()));
9585 let autoindent_mode = AutoindentMode::Block {
9586 original_indent_columns: Vec::new(),
9587 };
9588 buffer.edit(edits, Some(autoindent_mode), cx);
9589
9590 let snapshot = &*buffer.read(cx);
9591 let snippet = &snippet;
9592 snippet
9593 .tabstops
9594 .iter()
9595 .map(|tabstop| {
9596 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9597 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9598 });
9599 let mut tabstop_ranges = tabstop
9600 .ranges
9601 .iter()
9602 .flat_map(|tabstop_range| {
9603 let mut delta = 0_isize;
9604 insertion_ranges.iter().map(move |insertion_range| {
9605 let insertion_start = insertion_range.start as isize + delta;
9606 delta +=
9607 snippet.text.len() as isize - insertion_range.len() as isize;
9608
9609 let start = ((insertion_start + tabstop_range.start) as usize)
9610 .min(snapshot.len());
9611 let end = ((insertion_start + tabstop_range.end) as usize)
9612 .min(snapshot.len());
9613 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9614 })
9615 })
9616 .collect::<Vec<_>>();
9617 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9618
9619 Tabstop {
9620 is_end_tabstop,
9621 ranges: tabstop_ranges,
9622 choices: tabstop.choices.clone(),
9623 }
9624 })
9625 .collect::<Vec<_>>()
9626 });
9627 if let Some(tabstop) = tabstops.first() {
9628 self.change_selections(Default::default(), window, cx, |s| {
9629 // Reverse order so that the first range is the newest created selection.
9630 // Completions will use it and autoscroll will prioritize it.
9631 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9632 });
9633
9634 if let Some(choices) = &tabstop.choices
9635 && let Some(selection) = tabstop.ranges.first()
9636 {
9637 self.show_snippet_choices(choices, selection.clone(), cx)
9638 }
9639
9640 // If we're already at the last tabstop and it's at the end of the snippet,
9641 // we're done, we don't need to keep the state around.
9642 if !tabstop.is_end_tabstop {
9643 let choices = tabstops
9644 .iter()
9645 .map(|tabstop| tabstop.choices.clone())
9646 .collect();
9647
9648 let ranges = tabstops
9649 .into_iter()
9650 .map(|tabstop| tabstop.ranges)
9651 .collect::<Vec<_>>();
9652
9653 self.snippet_stack.push(SnippetState {
9654 active_index: 0,
9655 ranges,
9656 choices,
9657 });
9658 }
9659
9660 // Check whether the just-entered snippet ends with an auto-closable bracket.
9661 if self.autoclose_regions.is_empty() {
9662 let snapshot = self.buffer.read(cx).snapshot(cx);
9663 let mut all_selections = self.selections.all::<Point>(cx);
9664 for selection in &mut all_selections {
9665 let selection_head = selection.head();
9666 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9667 continue;
9668 };
9669
9670 let mut bracket_pair = None;
9671 let max_lookup_length = scope
9672 .brackets()
9673 .map(|(pair, _)| {
9674 pair.start
9675 .as_str()
9676 .chars()
9677 .count()
9678 .max(pair.end.as_str().chars().count())
9679 })
9680 .max();
9681 if let Some(max_lookup_length) = max_lookup_length {
9682 let next_text = snapshot
9683 .chars_at(selection_head)
9684 .take(max_lookup_length)
9685 .collect::<String>();
9686 let prev_text = snapshot
9687 .reversed_chars_at(selection_head)
9688 .take(max_lookup_length)
9689 .collect::<String>();
9690
9691 for (pair, enabled) in scope.brackets() {
9692 if enabled
9693 && pair.close
9694 && prev_text.starts_with(pair.start.as_str())
9695 && next_text.starts_with(pair.end.as_str())
9696 {
9697 bracket_pair = Some(pair.clone());
9698 break;
9699 }
9700 }
9701 }
9702
9703 if let Some(pair) = bracket_pair {
9704 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9705 let autoclose_enabled =
9706 self.use_autoclose && snapshot_settings.use_autoclose;
9707 if autoclose_enabled {
9708 let start = snapshot.anchor_after(selection_head);
9709 let end = snapshot.anchor_after(selection_head);
9710 self.autoclose_regions.push(AutocloseRegion {
9711 selection_id: selection.id,
9712 range: start..end,
9713 pair,
9714 });
9715 }
9716 }
9717 }
9718 }
9719 }
9720 Ok(())
9721 }
9722
9723 pub fn move_to_next_snippet_tabstop(
9724 &mut self,
9725 window: &mut Window,
9726 cx: &mut Context<Self>,
9727 ) -> bool {
9728 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9729 }
9730
9731 pub fn move_to_prev_snippet_tabstop(
9732 &mut self,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) -> bool {
9736 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9737 }
9738
9739 pub fn move_to_snippet_tabstop(
9740 &mut self,
9741 bias: Bias,
9742 window: &mut Window,
9743 cx: &mut Context<Self>,
9744 ) -> bool {
9745 if let Some(mut snippet) = self.snippet_stack.pop() {
9746 match bias {
9747 Bias::Left => {
9748 if snippet.active_index > 0 {
9749 snippet.active_index -= 1;
9750 } else {
9751 self.snippet_stack.push(snippet);
9752 return false;
9753 }
9754 }
9755 Bias::Right => {
9756 if snippet.active_index + 1 < snippet.ranges.len() {
9757 snippet.active_index += 1;
9758 } else {
9759 self.snippet_stack.push(snippet);
9760 return false;
9761 }
9762 }
9763 }
9764 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9765 self.change_selections(Default::default(), window, cx, |s| {
9766 // Reverse order so that the first range is the newest created selection.
9767 // Completions will use it and autoscroll will prioritize it.
9768 s.select_ranges(current_ranges.iter().rev().cloned())
9769 });
9770
9771 if let Some(choices) = &snippet.choices[snippet.active_index]
9772 && let Some(selection) = current_ranges.first()
9773 {
9774 self.show_snippet_choices(choices, selection.clone(), cx);
9775 }
9776
9777 // If snippet state is not at the last tabstop, push it back on the stack
9778 if snippet.active_index + 1 < snippet.ranges.len() {
9779 self.snippet_stack.push(snippet);
9780 }
9781 return true;
9782 }
9783 }
9784
9785 false
9786 }
9787
9788 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9789 self.transact(window, cx, |this, window, cx| {
9790 this.select_all(&SelectAll, window, cx);
9791 this.insert("", window, cx);
9792 });
9793 }
9794
9795 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9796 if self.read_only(cx) {
9797 return;
9798 }
9799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9800 self.transact(window, cx, |this, window, cx| {
9801 this.select_autoclose_pair(window, cx);
9802 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9803 if !this.linked_edit_ranges.is_empty() {
9804 let selections = this.selections.all::<MultiBufferPoint>(cx);
9805 let snapshot = this.buffer.read(cx).snapshot(cx);
9806
9807 for selection in selections.iter() {
9808 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9809 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9810 if selection_start.buffer_id != selection_end.buffer_id {
9811 continue;
9812 }
9813 if let Some(ranges) =
9814 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9815 {
9816 for (buffer, entries) in ranges {
9817 linked_ranges.entry(buffer).or_default().extend(entries);
9818 }
9819 }
9820 }
9821 }
9822
9823 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9824 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9825 for selection in &mut selections {
9826 if selection.is_empty() {
9827 let old_head = selection.head();
9828 let mut new_head =
9829 movement::left(&display_map, old_head.to_display_point(&display_map))
9830 .to_point(&display_map);
9831 if let Some((buffer, line_buffer_range)) = display_map
9832 .buffer_snapshot
9833 .buffer_line_for_row(MultiBufferRow(old_head.row))
9834 {
9835 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9836 let indent_len = match indent_size.kind {
9837 IndentKind::Space => {
9838 buffer.settings_at(line_buffer_range.start, cx).tab_size
9839 }
9840 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9841 };
9842 if old_head.column <= indent_size.len && old_head.column > 0 {
9843 let indent_len = indent_len.get();
9844 new_head = cmp::min(
9845 new_head,
9846 MultiBufferPoint::new(
9847 old_head.row,
9848 ((old_head.column - 1) / indent_len) * indent_len,
9849 ),
9850 );
9851 }
9852 }
9853
9854 selection.set_head(new_head, SelectionGoal::None);
9855 }
9856 }
9857
9858 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9859 this.insert("", window, cx);
9860 let empty_str: Arc<str> = Arc::from("");
9861 for (buffer, edits) in linked_ranges {
9862 let snapshot = buffer.read(cx).snapshot();
9863 use text::ToPoint as TP;
9864
9865 let edits = edits
9866 .into_iter()
9867 .map(|range| {
9868 let end_point = TP::to_point(&range.end, &snapshot);
9869 let mut start_point = TP::to_point(&range.start, &snapshot);
9870
9871 if end_point == start_point {
9872 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9873 .saturating_sub(1);
9874 start_point =
9875 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9876 };
9877
9878 (start_point..end_point, empty_str.clone())
9879 })
9880 .sorted_by_key(|(range, _)| range.start)
9881 .collect::<Vec<_>>();
9882 buffer.update(cx, |this, cx| {
9883 this.edit(edits, None, cx);
9884 })
9885 }
9886 this.refresh_edit_prediction(true, false, window, cx);
9887 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9888 });
9889 }
9890
9891 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9892 if self.read_only(cx) {
9893 return;
9894 }
9895 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9896 self.transact(window, cx, |this, window, cx| {
9897 this.change_selections(Default::default(), window, cx, |s| {
9898 s.move_with(|map, selection| {
9899 if selection.is_empty() {
9900 let cursor = movement::right(map, selection.head());
9901 selection.end = cursor;
9902 selection.reversed = true;
9903 selection.goal = SelectionGoal::None;
9904 }
9905 })
9906 });
9907 this.insert("", window, cx);
9908 this.refresh_edit_prediction(true, false, window, cx);
9909 });
9910 }
9911
9912 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9913 if self.mode.is_single_line() {
9914 cx.propagate();
9915 return;
9916 }
9917
9918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9919 if self.move_to_prev_snippet_tabstop(window, cx) {
9920 return;
9921 }
9922 self.outdent(&Outdent, window, cx);
9923 }
9924
9925 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9926 if self.mode.is_single_line() {
9927 cx.propagate();
9928 return;
9929 }
9930
9931 if self.move_to_next_snippet_tabstop(window, cx) {
9932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9933 return;
9934 }
9935 if self.read_only(cx) {
9936 return;
9937 }
9938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9939 let mut selections = self.selections.all_adjusted(cx);
9940 let buffer = self.buffer.read(cx);
9941 let snapshot = buffer.snapshot(cx);
9942 let rows_iter = selections.iter().map(|s| s.head().row);
9943 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9944
9945 let has_some_cursor_in_whitespace = selections
9946 .iter()
9947 .filter(|selection| selection.is_empty())
9948 .any(|selection| {
9949 let cursor = selection.head();
9950 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9951 cursor.column < current_indent.len
9952 });
9953
9954 let mut edits = Vec::new();
9955 let mut prev_edited_row = 0;
9956 let mut row_delta = 0;
9957 for selection in &mut selections {
9958 if selection.start.row != prev_edited_row {
9959 row_delta = 0;
9960 }
9961 prev_edited_row = selection.end.row;
9962
9963 // If the selection is non-empty, then increase the indentation of the selected lines.
9964 if !selection.is_empty() {
9965 row_delta =
9966 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9967 continue;
9968 }
9969
9970 let cursor = selection.head();
9971 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9972 if let Some(suggested_indent) =
9973 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9974 {
9975 // Don't do anything if already at suggested indent
9976 // and there is any other cursor which is not
9977 if has_some_cursor_in_whitespace
9978 && cursor.column == current_indent.len
9979 && current_indent.len == suggested_indent.len
9980 {
9981 continue;
9982 }
9983
9984 // Adjust line and move cursor to suggested indent
9985 // if cursor is not at suggested indent
9986 if cursor.column < suggested_indent.len
9987 && cursor.column <= current_indent.len
9988 && current_indent.len <= suggested_indent.len
9989 {
9990 selection.start = Point::new(cursor.row, suggested_indent.len);
9991 selection.end = selection.start;
9992 if row_delta == 0 {
9993 edits.extend(Buffer::edit_for_indent_size_adjustment(
9994 cursor.row,
9995 current_indent,
9996 suggested_indent,
9997 ));
9998 row_delta = suggested_indent.len - current_indent.len;
9999 }
10000 continue;
10001 }
10002
10003 // If current indent is more than suggested indent
10004 // only move cursor to current indent and skip indent
10005 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10006 selection.start = Point::new(cursor.row, current_indent.len);
10007 selection.end = selection.start;
10008 continue;
10009 }
10010 }
10011
10012 // Otherwise, insert a hard or soft tab.
10013 let settings = buffer.language_settings_at(cursor, cx);
10014 let tab_size = if settings.hard_tabs {
10015 IndentSize::tab()
10016 } else {
10017 let tab_size = settings.tab_size.get();
10018 let indent_remainder = snapshot
10019 .text_for_range(Point::new(cursor.row, 0)..cursor)
10020 .flat_map(str::chars)
10021 .fold(row_delta % tab_size, |counter: u32, c| {
10022 if c == '\t' {
10023 0
10024 } else {
10025 (counter + 1) % tab_size
10026 }
10027 });
10028
10029 let chars_to_next_tab_stop = tab_size - indent_remainder;
10030 IndentSize::spaces(chars_to_next_tab_stop)
10031 };
10032 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10033 selection.end = selection.start;
10034 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10035 row_delta += tab_size.len;
10036 }
10037
10038 self.transact(window, cx, |this, window, cx| {
10039 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10040 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10041 this.refresh_edit_prediction(true, false, window, cx);
10042 });
10043 }
10044
10045 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10046 if self.read_only(cx) {
10047 return;
10048 }
10049 if self.mode.is_single_line() {
10050 cx.propagate();
10051 return;
10052 }
10053
10054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10055 let mut selections = self.selections.all::<Point>(cx);
10056 let mut prev_edited_row = 0;
10057 let mut row_delta = 0;
10058 let mut edits = Vec::new();
10059 let buffer = self.buffer.read(cx);
10060 let snapshot = buffer.snapshot(cx);
10061 for selection in &mut selections {
10062 if selection.start.row != prev_edited_row {
10063 row_delta = 0;
10064 }
10065 prev_edited_row = selection.end.row;
10066
10067 row_delta =
10068 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10069 }
10070
10071 self.transact(window, cx, |this, window, cx| {
10072 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10073 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10074 });
10075 }
10076
10077 fn indent_selection(
10078 buffer: &MultiBuffer,
10079 snapshot: &MultiBufferSnapshot,
10080 selection: &mut Selection<Point>,
10081 edits: &mut Vec<(Range<Point>, String)>,
10082 delta_for_start_row: u32,
10083 cx: &App,
10084 ) -> u32 {
10085 let settings = buffer.language_settings_at(selection.start, cx);
10086 let tab_size = settings.tab_size.get();
10087 let indent_kind = if settings.hard_tabs {
10088 IndentKind::Tab
10089 } else {
10090 IndentKind::Space
10091 };
10092 let mut start_row = selection.start.row;
10093 let mut end_row = selection.end.row + 1;
10094
10095 // If a selection ends at the beginning of a line, don't indent
10096 // that last line.
10097 if selection.end.column == 0 && selection.end.row > selection.start.row {
10098 end_row -= 1;
10099 }
10100
10101 // Avoid re-indenting a row that has already been indented by a
10102 // previous selection, but still update this selection's column
10103 // to reflect that indentation.
10104 if delta_for_start_row > 0 {
10105 start_row += 1;
10106 selection.start.column += delta_for_start_row;
10107 if selection.end.row == selection.start.row {
10108 selection.end.column += delta_for_start_row;
10109 }
10110 }
10111
10112 let mut delta_for_end_row = 0;
10113 let has_multiple_rows = start_row + 1 != end_row;
10114 for row in start_row..end_row {
10115 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10116 let indent_delta = match (current_indent.kind, indent_kind) {
10117 (IndentKind::Space, IndentKind::Space) => {
10118 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10119 IndentSize::spaces(columns_to_next_tab_stop)
10120 }
10121 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10122 (_, IndentKind::Tab) => IndentSize::tab(),
10123 };
10124
10125 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10126 0
10127 } else {
10128 selection.start.column
10129 };
10130 let row_start = Point::new(row, start);
10131 edits.push((
10132 row_start..row_start,
10133 indent_delta.chars().collect::<String>(),
10134 ));
10135
10136 // Update this selection's endpoints to reflect the indentation.
10137 if row == selection.start.row {
10138 selection.start.column += indent_delta.len;
10139 }
10140 if row == selection.end.row {
10141 selection.end.column += indent_delta.len;
10142 delta_for_end_row = indent_delta.len;
10143 }
10144 }
10145
10146 if selection.start.row == selection.end.row {
10147 delta_for_start_row + delta_for_end_row
10148 } else {
10149 delta_for_end_row
10150 }
10151 }
10152
10153 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10154 if self.read_only(cx) {
10155 return;
10156 }
10157 if self.mode.is_single_line() {
10158 cx.propagate();
10159 return;
10160 }
10161
10162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10164 let selections = self.selections.all::<Point>(cx);
10165 let mut deletion_ranges = Vec::new();
10166 let mut last_outdent = None;
10167 {
10168 let buffer = self.buffer.read(cx);
10169 let snapshot = buffer.snapshot(cx);
10170 for selection in &selections {
10171 let settings = buffer.language_settings_at(selection.start, cx);
10172 let tab_size = settings.tab_size.get();
10173 let mut rows = selection.spanned_rows(false, &display_map);
10174
10175 // Avoid re-outdenting a row that has already been outdented by a
10176 // previous selection.
10177 if let Some(last_row) = last_outdent
10178 && last_row == rows.start
10179 {
10180 rows.start = rows.start.next_row();
10181 }
10182 let has_multiple_rows = rows.len() > 1;
10183 for row in rows.iter_rows() {
10184 let indent_size = snapshot.indent_size_for_line(row);
10185 if indent_size.len > 0 {
10186 let deletion_len = match indent_size.kind {
10187 IndentKind::Space => {
10188 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10189 if columns_to_prev_tab_stop == 0 {
10190 tab_size
10191 } else {
10192 columns_to_prev_tab_stop
10193 }
10194 }
10195 IndentKind::Tab => 1,
10196 };
10197 let start = if has_multiple_rows
10198 || deletion_len > selection.start.column
10199 || indent_size.len < selection.start.column
10200 {
10201 0
10202 } else {
10203 selection.start.column - deletion_len
10204 };
10205 deletion_ranges.push(
10206 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10207 );
10208 last_outdent = Some(row);
10209 }
10210 }
10211 }
10212 }
10213
10214 self.transact(window, cx, |this, window, cx| {
10215 this.buffer.update(cx, |buffer, cx| {
10216 let empty_str: Arc<str> = Arc::default();
10217 buffer.edit(
10218 deletion_ranges
10219 .into_iter()
10220 .map(|range| (range, empty_str.clone())),
10221 None,
10222 cx,
10223 );
10224 });
10225 let selections = this.selections.all::<usize>(cx);
10226 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10227 });
10228 }
10229
10230 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10231 if self.read_only(cx) {
10232 return;
10233 }
10234 if self.mode.is_single_line() {
10235 cx.propagate();
10236 return;
10237 }
10238
10239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10240 let selections = self
10241 .selections
10242 .all::<usize>(cx)
10243 .into_iter()
10244 .map(|s| s.range());
10245
10246 self.transact(window, cx, |this, window, cx| {
10247 this.buffer.update(cx, |buffer, cx| {
10248 buffer.autoindent_ranges(selections, cx);
10249 });
10250 let selections = this.selections.all::<usize>(cx);
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10256 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10258 let selections = self.selections.all::<Point>(cx);
10259
10260 let mut new_cursors = Vec::new();
10261 let mut edit_ranges = Vec::new();
10262 let mut selections = selections.iter().peekable();
10263 while let Some(selection) = selections.next() {
10264 let mut rows = selection.spanned_rows(false, &display_map);
10265 let goal_display_column = selection.head().to_display_point(&display_map).column();
10266
10267 // Accumulate contiguous regions of rows that we want to delete.
10268 while let Some(next_selection) = selections.peek() {
10269 let next_rows = next_selection.spanned_rows(false, &display_map);
10270 if next_rows.start <= rows.end {
10271 rows.end = next_rows.end;
10272 selections.next().unwrap();
10273 } else {
10274 break;
10275 }
10276 }
10277
10278 let buffer = &display_map.buffer_snapshot;
10279 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10280 let edit_end;
10281 let cursor_buffer_row;
10282 if buffer.max_point().row >= rows.end.0 {
10283 // If there's a line after the range, delete the \n from the end of the row range
10284 // and position the cursor on the next line.
10285 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10286 cursor_buffer_row = rows.end;
10287 } else {
10288 // If there isn't a line after the range, delete the \n from the line before the
10289 // start of the row range and position the cursor there.
10290 edit_start = edit_start.saturating_sub(1);
10291 edit_end = buffer.len();
10292 cursor_buffer_row = rows.start.previous_row();
10293 }
10294
10295 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10296 *cursor.column_mut() =
10297 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10298
10299 new_cursors.push((
10300 selection.id,
10301 buffer.anchor_after(cursor.to_point(&display_map)),
10302 ));
10303 edit_ranges.push(edit_start..edit_end);
10304 }
10305
10306 self.transact(window, cx, |this, window, cx| {
10307 let buffer = this.buffer.update(cx, |buffer, cx| {
10308 let empty_str: Arc<str> = Arc::default();
10309 buffer.edit(
10310 edit_ranges
10311 .into_iter()
10312 .map(|range| (range, empty_str.clone())),
10313 None,
10314 cx,
10315 );
10316 buffer.snapshot(cx)
10317 });
10318 let new_selections = new_cursors
10319 .into_iter()
10320 .map(|(id, cursor)| {
10321 let cursor = cursor.to_point(&buffer);
10322 Selection {
10323 id,
10324 start: cursor,
10325 end: cursor,
10326 reversed: false,
10327 goal: SelectionGoal::None,
10328 }
10329 })
10330 .collect();
10331
10332 this.change_selections(Default::default(), window, cx, |s| {
10333 s.select(new_selections);
10334 });
10335 });
10336 }
10337
10338 pub fn join_lines_impl(
10339 &mut self,
10340 insert_whitespace: bool,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 if self.read_only(cx) {
10345 return;
10346 }
10347 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10348 for selection in self.selections.all::<Point>(cx) {
10349 let start = MultiBufferRow(selection.start.row);
10350 // Treat single line selections as if they include the next line. Otherwise this action
10351 // would do nothing for single line selections individual cursors.
10352 let end = if selection.start.row == selection.end.row {
10353 MultiBufferRow(selection.start.row + 1)
10354 } else {
10355 MultiBufferRow(selection.end.row)
10356 };
10357
10358 if let Some(last_row_range) = row_ranges.last_mut()
10359 && start <= last_row_range.end
10360 {
10361 last_row_range.end = end;
10362 continue;
10363 }
10364 row_ranges.push(start..end);
10365 }
10366
10367 let snapshot = self.buffer.read(cx).snapshot(cx);
10368 let mut cursor_positions = Vec::new();
10369 for row_range in &row_ranges {
10370 let anchor = snapshot.anchor_before(Point::new(
10371 row_range.end.previous_row().0,
10372 snapshot.line_len(row_range.end.previous_row()),
10373 ));
10374 cursor_positions.push(anchor..anchor);
10375 }
10376
10377 self.transact(window, cx, |this, window, cx| {
10378 for row_range in row_ranges.into_iter().rev() {
10379 for row in row_range.iter_rows().rev() {
10380 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10381 let next_line_row = row.next_row();
10382 let indent = snapshot.indent_size_for_line(next_line_row);
10383 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10384
10385 let replace =
10386 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10387 " "
10388 } else {
10389 ""
10390 };
10391
10392 this.buffer.update(cx, |buffer, cx| {
10393 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10394 });
10395 }
10396 }
10397
10398 this.change_selections(Default::default(), window, cx, |s| {
10399 s.select_anchor_ranges(cursor_positions)
10400 });
10401 });
10402 }
10403
10404 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10406 self.join_lines_impl(true, window, cx);
10407 }
10408
10409 pub fn sort_lines_case_sensitive(
10410 &mut self,
10411 _: &SortLinesCaseSensitive,
10412 window: &mut Window,
10413 cx: &mut Context<Self>,
10414 ) {
10415 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10416 }
10417
10418 pub fn sort_lines_by_length(
10419 &mut self,
10420 _: &SortLinesByLength,
10421 window: &mut Window,
10422 cx: &mut Context<Self>,
10423 ) {
10424 self.manipulate_immutable_lines(window, cx, |lines| {
10425 lines.sort_by_key(|&line| line.chars().count())
10426 })
10427 }
10428
10429 pub fn sort_lines_case_insensitive(
10430 &mut self,
10431 _: &SortLinesCaseInsensitive,
10432 window: &mut Window,
10433 cx: &mut Context<Self>,
10434 ) {
10435 self.manipulate_immutable_lines(window, cx, |lines| {
10436 lines.sort_by_key(|line| line.to_lowercase())
10437 })
10438 }
10439
10440 pub fn unique_lines_case_insensitive(
10441 &mut self,
10442 _: &UniqueLinesCaseInsensitive,
10443 window: &mut Window,
10444 cx: &mut Context<Self>,
10445 ) {
10446 self.manipulate_immutable_lines(window, cx, |lines| {
10447 let mut seen = HashSet::default();
10448 lines.retain(|line| seen.insert(line.to_lowercase()));
10449 })
10450 }
10451
10452 pub fn unique_lines_case_sensitive(
10453 &mut self,
10454 _: &UniqueLinesCaseSensitive,
10455 window: &mut Window,
10456 cx: &mut Context<Self>,
10457 ) {
10458 self.manipulate_immutable_lines(window, cx, |lines| {
10459 let mut seen = HashSet::default();
10460 lines.retain(|line| seen.insert(*line));
10461 })
10462 }
10463
10464 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10465 let snapshot = self.buffer.read(cx).snapshot(cx);
10466 for selection in self.selections.disjoint_anchors().iter() {
10467 if snapshot
10468 .language_at(selection.start)
10469 .and_then(|lang| lang.config().wrap_characters.as_ref())
10470 .is_some()
10471 {
10472 return true;
10473 }
10474 }
10475 false
10476 }
10477
10478 fn wrap_selections_in_tag(
10479 &mut self,
10480 _: &WrapSelectionsInTag,
10481 window: &mut Window,
10482 cx: &mut Context<Self>,
10483 ) {
10484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10485
10486 let snapshot = self.buffer.read(cx).snapshot(cx);
10487
10488 let mut edits = Vec::new();
10489 let mut boundaries = Vec::new();
10490
10491 for selection in self.selections.all::<Point>(cx).iter() {
10492 let Some(wrap_config) = snapshot
10493 .language_at(selection.start)
10494 .and_then(|lang| lang.config().wrap_characters.clone())
10495 else {
10496 continue;
10497 };
10498
10499 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10500 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10501
10502 let start_before = snapshot.anchor_before(selection.start);
10503 let end_after = snapshot.anchor_after(selection.end);
10504
10505 edits.push((start_before..start_before, open_tag));
10506 edits.push((end_after..end_after, close_tag));
10507
10508 boundaries.push((
10509 start_before,
10510 end_after,
10511 wrap_config.start_prefix.len(),
10512 wrap_config.end_suffix.len(),
10513 ));
10514 }
10515
10516 if edits.is_empty() {
10517 return;
10518 }
10519
10520 self.transact(window, cx, |this, window, cx| {
10521 let buffer = this.buffer.update(cx, |buffer, cx| {
10522 buffer.edit(edits, None, cx);
10523 buffer.snapshot(cx)
10524 });
10525
10526 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10527 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10528 boundaries.into_iter()
10529 {
10530 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10531 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10532 new_selections.push(open_offset..open_offset);
10533 new_selections.push(close_offset..close_offset);
10534 }
10535
10536 this.change_selections(Default::default(), window, cx, |s| {
10537 s.select_ranges(new_selections);
10538 });
10539
10540 this.request_autoscroll(Autoscroll::fit(), cx);
10541 });
10542 }
10543
10544 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10545 let Some(project) = self.project.clone() else {
10546 return;
10547 };
10548 self.reload(project, window, cx)
10549 .detach_and_notify_err(window, cx);
10550 }
10551
10552 pub fn restore_file(
10553 &mut self,
10554 _: &::git::RestoreFile,
10555 window: &mut Window,
10556 cx: &mut Context<Self>,
10557 ) {
10558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10559 let mut buffer_ids = HashSet::default();
10560 let snapshot = self.buffer().read(cx).snapshot(cx);
10561 for selection in self.selections.all::<usize>(cx) {
10562 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10563 }
10564
10565 let buffer = self.buffer().read(cx);
10566 let ranges = buffer_ids
10567 .into_iter()
10568 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10569 .collect::<Vec<_>>();
10570
10571 self.restore_hunks_in_ranges(ranges, window, cx);
10572 }
10573
10574 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10576 let selections = self
10577 .selections
10578 .all(cx)
10579 .into_iter()
10580 .map(|s| s.range())
10581 .collect();
10582 self.restore_hunks_in_ranges(selections, window, cx);
10583 }
10584
10585 pub fn restore_hunks_in_ranges(
10586 &mut self,
10587 ranges: Vec<Range<Point>>,
10588 window: &mut Window,
10589 cx: &mut Context<Editor>,
10590 ) {
10591 let mut revert_changes = HashMap::default();
10592 let chunk_by = self
10593 .snapshot(window, cx)
10594 .hunks_for_ranges(ranges)
10595 .into_iter()
10596 .chunk_by(|hunk| hunk.buffer_id);
10597 for (buffer_id, hunks) in &chunk_by {
10598 let hunks = hunks.collect::<Vec<_>>();
10599 for hunk in &hunks {
10600 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10601 }
10602 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10603 }
10604 drop(chunk_by);
10605 if !revert_changes.is_empty() {
10606 self.transact(window, cx, |editor, window, cx| {
10607 editor.restore(revert_changes, window, cx);
10608 });
10609 }
10610 }
10611
10612 pub fn open_active_item_in_terminal(
10613 &mut self,
10614 _: &OpenInTerminal,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10619 let project_path = buffer.read(cx).project_path(cx)?;
10620 let project = self.project()?.read(cx);
10621 let entry = project.entry_for_path(&project_path, cx)?;
10622 let parent = match &entry.canonical_path {
10623 Some(canonical_path) => canonical_path.to_path_buf(),
10624 None => project.absolute_path(&project_path, cx)?,
10625 }
10626 .parent()?
10627 .to_path_buf();
10628 Some(parent)
10629 }) {
10630 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10631 }
10632 }
10633
10634 fn set_breakpoint_context_menu(
10635 &mut self,
10636 display_row: DisplayRow,
10637 position: Option<Anchor>,
10638 clicked_point: gpui::Point<Pixels>,
10639 window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 let source = self
10643 .buffer
10644 .read(cx)
10645 .snapshot(cx)
10646 .anchor_before(Point::new(display_row.0, 0u32));
10647
10648 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10649
10650 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10651 self,
10652 source,
10653 clicked_point,
10654 context_menu,
10655 window,
10656 cx,
10657 );
10658 }
10659
10660 fn add_edit_breakpoint_block(
10661 &mut self,
10662 anchor: Anchor,
10663 breakpoint: &Breakpoint,
10664 edit_action: BreakpointPromptEditAction,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 let weak_editor = cx.weak_entity();
10669 let bp_prompt = cx.new(|cx| {
10670 BreakpointPromptEditor::new(
10671 weak_editor,
10672 anchor,
10673 breakpoint.clone(),
10674 edit_action,
10675 window,
10676 cx,
10677 )
10678 });
10679
10680 let height = bp_prompt.update(cx, |this, cx| {
10681 this.prompt
10682 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10683 });
10684 let cloned_prompt = bp_prompt.clone();
10685 let blocks = vec![BlockProperties {
10686 style: BlockStyle::Sticky,
10687 placement: BlockPlacement::Above(anchor),
10688 height: Some(height),
10689 render: Arc::new(move |cx| {
10690 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10691 cloned_prompt.clone().into_any_element()
10692 }),
10693 priority: 0,
10694 }];
10695
10696 let focus_handle = bp_prompt.focus_handle(cx);
10697 window.focus(&focus_handle);
10698
10699 let block_ids = self.insert_blocks(blocks, None, cx);
10700 bp_prompt.update(cx, |prompt, _| {
10701 prompt.add_block_ids(block_ids);
10702 });
10703 }
10704
10705 pub(crate) fn breakpoint_at_row(
10706 &self,
10707 row: u32,
10708 window: &mut Window,
10709 cx: &mut Context<Self>,
10710 ) -> Option<(Anchor, Breakpoint)> {
10711 let snapshot = self.snapshot(window, cx);
10712 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10713
10714 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10715 }
10716
10717 pub(crate) fn breakpoint_at_anchor(
10718 &self,
10719 breakpoint_position: Anchor,
10720 snapshot: &EditorSnapshot,
10721 cx: &mut Context<Self>,
10722 ) -> Option<(Anchor, Breakpoint)> {
10723 let buffer = self
10724 .buffer
10725 .read(cx)
10726 .buffer_for_anchor(breakpoint_position, cx)?;
10727
10728 let enclosing_excerpt = breakpoint_position.excerpt_id;
10729 let buffer_snapshot = buffer.read(cx).snapshot();
10730
10731 let row = buffer_snapshot
10732 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10733 .row;
10734
10735 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10736 let anchor_end = snapshot
10737 .buffer_snapshot
10738 .anchor_after(Point::new(row, line_len));
10739
10740 self.breakpoint_store
10741 .as_ref()?
10742 .read_with(cx, |breakpoint_store, cx| {
10743 breakpoint_store
10744 .breakpoints(
10745 &buffer,
10746 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10747 &buffer_snapshot,
10748 cx,
10749 )
10750 .next()
10751 .and_then(|(bp, _)| {
10752 let breakpoint_row = buffer_snapshot
10753 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10754 .row;
10755
10756 if breakpoint_row == row {
10757 snapshot
10758 .buffer_snapshot
10759 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10760 .map(|position| (position, bp.bp.clone()))
10761 } else {
10762 None
10763 }
10764 })
10765 })
10766 }
10767
10768 pub fn edit_log_breakpoint(
10769 &mut self,
10770 _: &EditLogBreakpoint,
10771 window: &mut Window,
10772 cx: &mut Context<Self>,
10773 ) {
10774 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10775 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10776 message: None,
10777 state: BreakpointState::Enabled,
10778 condition: None,
10779 hit_condition: None,
10780 });
10781
10782 self.add_edit_breakpoint_block(
10783 anchor,
10784 &breakpoint,
10785 BreakpointPromptEditAction::Log,
10786 window,
10787 cx,
10788 );
10789 }
10790 }
10791
10792 fn breakpoints_at_cursors(
10793 &self,
10794 window: &mut Window,
10795 cx: &mut Context<Self>,
10796 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10797 let snapshot = self.snapshot(window, cx);
10798 let cursors = self
10799 .selections
10800 .disjoint_anchors()
10801 .iter()
10802 .map(|selection| {
10803 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10804
10805 let breakpoint_position = self
10806 .breakpoint_at_row(cursor_position.row, window, cx)
10807 .map(|bp| bp.0)
10808 .unwrap_or_else(|| {
10809 snapshot
10810 .display_snapshot
10811 .buffer_snapshot
10812 .anchor_after(Point::new(cursor_position.row, 0))
10813 });
10814
10815 let breakpoint = self
10816 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10817 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10818
10819 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10820 })
10821 // 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.
10822 .collect::<HashMap<Anchor, _>>();
10823
10824 cursors.into_iter().collect()
10825 }
10826
10827 pub fn enable_breakpoint(
10828 &mut self,
10829 _: &crate::actions::EnableBreakpoint,
10830 window: &mut Window,
10831 cx: &mut Context<Self>,
10832 ) {
10833 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10834 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10835 continue;
10836 };
10837 self.edit_breakpoint_at_anchor(
10838 anchor,
10839 breakpoint,
10840 BreakpointEditAction::InvertState,
10841 cx,
10842 );
10843 }
10844 }
10845
10846 pub fn disable_breakpoint(
10847 &mut self,
10848 _: &crate::actions::DisableBreakpoint,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10853 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10854 continue;
10855 };
10856 self.edit_breakpoint_at_anchor(
10857 anchor,
10858 breakpoint,
10859 BreakpointEditAction::InvertState,
10860 cx,
10861 );
10862 }
10863 }
10864
10865 pub fn toggle_breakpoint(
10866 &mut self,
10867 _: &crate::actions::ToggleBreakpoint,
10868 window: &mut Window,
10869 cx: &mut Context<Self>,
10870 ) {
10871 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10872 if let Some(breakpoint) = breakpoint {
10873 self.edit_breakpoint_at_anchor(
10874 anchor,
10875 breakpoint,
10876 BreakpointEditAction::Toggle,
10877 cx,
10878 );
10879 } else {
10880 self.edit_breakpoint_at_anchor(
10881 anchor,
10882 Breakpoint::new_standard(),
10883 BreakpointEditAction::Toggle,
10884 cx,
10885 );
10886 }
10887 }
10888 }
10889
10890 pub fn edit_breakpoint_at_anchor(
10891 &mut self,
10892 breakpoint_position: Anchor,
10893 breakpoint: Breakpoint,
10894 edit_action: BreakpointEditAction,
10895 cx: &mut Context<Self>,
10896 ) {
10897 let Some(breakpoint_store) = &self.breakpoint_store else {
10898 return;
10899 };
10900
10901 let Some(buffer) = self
10902 .buffer
10903 .read(cx)
10904 .buffer_for_anchor(breakpoint_position, cx)
10905 else {
10906 return;
10907 };
10908
10909 breakpoint_store.update(cx, |breakpoint_store, cx| {
10910 breakpoint_store.toggle_breakpoint(
10911 buffer,
10912 BreakpointWithPosition {
10913 position: breakpoint_position.text_anchor,
10914 bp: breakpoint,
10915 },
10916 edit_action,
10917 cx,
10918 );
10919 });
10920
10921 cx.notify();
10922 }
10923
10924 #[cfg(any(test, feature = "test-support"))]
10925 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10926 self.breakpoint_store.clone()
10927 }
10928
10929 pub fn prepare_restore_change(
10930 &self,
10931 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10932 hunk: &MultiBufferDiffHunk,
10933 cx: &mut App,
10934 ) -> Option<()> {
10935 if hunk.is_created_file() {
10936 return None;
10937 }
10938 let buffer = self.buffer.read(cx);
10939 let diff = buffer.diff_for(hunk.buffer_id)?;
10940 let buffer = buffer.buffer(hunk.buffer_id)?;
10941 let buffer = buffer.read(cx);
10942 let original_text = diff
10943 .read(cx)
10944 .base_text()
10945 .as_rope()
10946 .slice(hunk.diff_base_byte_range.clone());
10947 let buffer_snapshot = buffer.snapshot();
10948 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10949 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10950 probe
10951 .0
10952 .start
10953 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10954 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10955 }) {
10956 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10957 Some(())
10958 } else {
10959 None
10960 }
10961 }
10962
10963 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10964 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10965 }
10966
10967 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10968 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10969 }
10970
10971 fn manipulate_lines<M>(
10972 &mut self,
10973 window: &mut Window,
10974 cx: &mut Context<Self>,
10975 mut manipulate: M,
10976 ) where
10977 M: FnMut(&str) -> LineManipulationResult,
10978 {
10979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10980
10981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10982 let buffer = self.buffer.read(cx).snapshot(cx);
10983
10984 let mut edits = Vec::new();
10985
10986 let selections = self.selections.all::<Point>(cx);
10987 let mut selections = selections.iter().peekable();
10988 let mut contiguous_row_selections = Vec::new();
10989 let mut new_selections = Vec::new();
10990 let mut added_lines = 0;
10991 let mut removed_lines = 0;
10992
10993 while let Some(selection) = selections.next() {
10994 let (start_row, end_row) = consume_contiguous_rows(
10995 &mut contiguous_row_selections,
10996 selection,
10997 &display_map,
10998 &mut selections,
10999 );
11000
11001 let start_point = Point::new(start_row.0, 0);
11002 let end_point = Point::new(
11003 end_row.previous_row().0,
11004 buffer.line_len(end_row.previous_row()),
11005 );
11006 let text = buffer
11007 .text_for_range(start_point..end_point)
11008 .collect::<String>();
11009
11010 let LineManipulationResult {
11011 new_text,
11012 line_count_before,
11013 line_count_after,
11014 } = manipulate(&text);
11015
11016 edits.push((start_point..end_point, new_text));
11017
11018 // Selections must change based on added and removed line count
11019 let start_row =
11020 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11021 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11022 new_selections.push(Selection {
11023 id: selection.id,
11024 start: start_row,
11025 end: end_row,
11026 goal: SelectionGoal::None,
11027 reversed: selection.reversed,
11028 });
11029
11030 if line_count_after > line_count_before {
11031 added_lines += line_count_after - line_count_before;
11032 } else if line_count_before > line_count_after {
11033 removed_lines += line_count_before - line_count_after;
11034 }
11035 }
11036
11037 self.transact(window, cx, |this, window, cx| {
11038 let buffer = this.buffer.update(cx, |buffer, cx| {
11039 buffer.edit(edits, None, cx);
11040 buffer.snapshot(cx)
11041 });
11042
11043 // Recalculate offsets on newly edited buffer
11044 let new_selections = new_selections
11045 .iter()
11046 .map(|s| {
11047 let start_point = Point::new(s.start.0, 0);
11048 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11049 Selection {
11050 id: s.id,
11051 start: buffer.point_to_offset(start_point),
11052 end: buffer.point_to_offset(end_point),
11053 goal: s.goal,
11054 reversed: s.reversed,
11055 }
11056 })
11057 .collect();
11058
11059 this.change_selections(Default::default(), window, cx, |s| {
11060 s.select(new_selections);
11061 });
11062
11063 this.request_autoscroll(Autoscroll::fit(), cx);
11064 });
11065 }
11066
11067 fn manipulate_immutable_lines<Fn>(
11068 &mut self,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 mut callback: Fn,
11072 ) where
11073 Fn: FnMut(&mut Vec<&str>),
11074 {
11075 self.manipulate_lines(window, cx, |text| {
11076 let mut lines: Vec<&str> = text.split('\n').collect();
11077 let line_count_before = lines.len();
11078
11079 callback(&mut lines);
11080
11081 LineManipulationResult {
11082 new_text: lines.join("\n"),
11083 line_count_before,
11084 line_count_after: lines.len(),
11085 }
11086 });
11087 }
11088
11089 fn manipulate_mutable_lines<Fn>(
11090 &mut self,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 mut callback: Fn,
11094 ) where
11095 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11096 {
11097 self.manipulate_lines(window, cx, |text| {
11098 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11099 let line_count_before = lines.len();
11100
11101 callback(&mut lines);
11102
11103 LineManipulationResult {
11104 new_text: lines.join("\n"),
11105 line_count_before,
11106 line_count_after: lines.len(),
11107 }
11108 });
11109 }
11110
11111 pub fn convert_indentation_to_spaces(
11112 &mut self,
11113 _: &ConvertIndentationToSpaces,
11114 window: &mut Window,
11115 cx: &mut Context<Self>,
11116 ) {
11117 let settings = self.buffer.read(cx).language_settings(cx);
11118 let tab_size = settings.tab_size.get() as usize;
11119
11120 self.manipulate_mutable_lines(window, cx, |lines| {
11121 // Allocates a reasonably sized scratch buffer once for the whole loop
11122 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11123 // Avoids recomputing spaces that could be inserted many times
11124 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11125 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11126 .collect();
11127
11128 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11129 let mut chars = line.as_ref().chars();
11130 let mut col = 0;
11131 let mut changed = false;
11132
11133 for ch in chars.by_ref() {
11134 match ch {
11135 ' ' => {
11136 reindented_line.push(' ');
11137 col += 1;
11138 }
11139 '\t' => {
11140 // \t are converted to spaces depending on the current column
11141 let spaces_len = tab_size - (col % tab_size);
11142 reindented_line.extend(&space_cache[spaces_len - 1]);
11143 col += spaces_len;
11144 changed = true;
11145 }
11146 _ => {
11147 // If we dont append before break, the character is consumed
11148 reindented_line.push(ch);
11149 break;
11150 }
11151 }
11152 }
11153
11154 if !changed {
11155 reindented_line.clear();
11156 continue;
11157 }
11158 // Append the rest of the line and replace old reference with new one
11159 reindented_line.extend(chars);
11160 *line = Cow::Owned(reindented_line.clone());
11161 reindented_line.clear();
11162 }
11163 });
11164 }
11165
11166 pub fn convert_indentation_to_tabs(
11167 &mut self,
11168 _: &ConvertIndentationToTabs,
11169 window: &mut Window,
11170 cx: &mut Context<Self>,
11171 ) {
11172 let settings = self.buffer.read(cx).language_settings(cx);
11173 let tab_size = settings.tab_size.get() as usize;
11174
11175 self.manipulate_mutable_lines(window, cx, |lines| {
11176 // Allocates a reasonably sized buffer once for the whole loop
11177 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11178 // Avoids recomputing spaces that could be inserted many times
11179 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11180 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11181 .collect();
11182
11183 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11184 let mut chars = line.chars();
11185 let mut spaces_count = 0;
11186 let mut first_non_indent_char = None;
11187 let mut changed = false;
11188
11189 for ch in chars.by_ref() {
11190 match ch {
11191 ' ' => {
11192 // Keep track of spaces. Append \t when we reach tab_size
11193 spaces_count += 1;
11194 changed = true;
11195 if spaces_count == tab_size {
11196 reindented_line.push('\t');
11197 spaces_count = 0;
11198 }
11199 }
11200 '\t' => {
11201 reindented_line.push('\t');
11202 spaces_count = 0;
11203 }
11204 _ => {
11205 // Dont append it yet, we might have remaining spaces
11206 first_non_indent_char = Some(ch);
11207 break;
11208 }
11209 }
11210 }
11211
11212 if !changed {
11213 reindented_line.clear();
11214 continue;
11215 }
11216 // Remaining spaces that didn't make a full tab stop
11217 if spaces_count > 0 {
11218 reindented_line.extend(&space_cache[spaces_count - 1]);
11219 }
11220 // If we consume an extra character that was not indentation, add it back
11221 if let Some(extra_char) = first_non_indent_char {
11222 reindented_line.push(extra_char);
11223 }
11224 // Append the rest of the line and replace old reference with new one
11225 reindented_line.extend(chars);
11226 *line = Cow::Owned(reindented_line.clone());
11227 reindented_line.clear();
11228 }
11229 });
11230 }
11231
11232 pub fn convert_to_upper_case(
11233 &mut self,
11234 _: &ConvertToUpperCase,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 self.manipulate_text(window, cx, |text| text.to_uppercase())
11239 }
11240
11241 pub fn convert_to_lower_case(
11242 &mut self,
11243 _: &ConvertToLowerCase,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 self.manipulate_text(window, cx, |text| text.to_lowercase())
11248 }
11249
11250 pub fn convert_to_title_case(
11251 &mut self,
11252 _: &ConvertToTitleCase,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) {
11256 self.manipulate_text(window, cx, |text| {
11257 text.split('\n')
11258 .map(|line| line.to_case(Case::Title))
11259 .join("\n")
11260 })
11261 }
11262
11263 pub fn convert_to_snake_case(
11264 &mut self,
11265 _: &ConvertToSnakeCase,
11266 window: &mut Window,
11267 cx: &mut Context<Self>,
11268 ) {
11269 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11270 }
11271
11272 pub fn convert_to_kebab_case(
11273 &mut self,
11274 _: &ConvertToKebabCase,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11279 }
11280
11281 pub fn convert_to_upper_camel_case(
11282 &mut self,
11283 _: &ConvertToUpperCamelCase,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 self.manipulate_text(window, cx, |text| {
11288 text.split('\n')
11289 .map(|line| line.to_case(Case::UpperCamel))
11290 .join("\n")
11291 })
11292 }
11293
11294 pub fn convert_to_lower_camel_case(
11295 &mut self,
11296 _: &ConvertToLowerCamelCase,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11301 }
11302
11303 pub fn convert_to_opposite_case(
11304 &mut self,
11305 _: &ConvertToOppositeCase,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| {
11310 text.chars()
11311 .fold(String::with_capacity(text.len()), |mut t, c| {
11312 if c.is_uppercase() {
11313 t.extend(c.to_lowercase());
11314 } else {
11315 t.extend(c.to_uppercase());
11316 }
11317 t
11318 })
11319 })
11320 }
11321
11322 pub fn convert_to_sentence_case(
11323 &mut self,
11324 _: &ConvertToSentenceCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11329 }
11330
11331 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11332 self.manipulate_text(window, cx, |text| {
11333 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11334 if has_upper_case_characters {
11335 text.to_lowercase()
11336 } else {
11337 text.to_uppercase()
11338 }
11339 })
11340 }
11341
11342 pub fn convert_to_rot13(
11343 &mut self,
11344 _: &ConvertToRot13,
11345 window: &mut Window,
11346 cx: &mut Context<Self>,
11347 ) {
11348 self.manipulate_text(window, cx, |text| {
11349 text.chars()
11350 .map(|c| match c {
11351 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11352 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11353 _ => c,
11354 })
11355 .collect()
11356 })
11357 }
11358
11359 pub fn convert_to_rot47(
11360 &mut self,
11361 _: &ConvertToRot47,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 self.manipulate_text(window, cx, |text| {
11366 text.chars()
11367 .map(|c| {
11368 let code_point = c as u32;
11369 if code_point >= 33 && code_point <= 126 {
11370 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11371 }
11372 c
11373 })
11374 .collect()
11375 })
11376 }
11377
11378 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11379 where
11380 Fn: FnMut(&str) -> String,
11381 {
11382 let buffer = self.buffer.read(cx).snapshot(cx);
11383
11384 let mut new_selections = Vec::new();
11385 let mut edits = Vec::new();
11386 let mut selection_adjustment = 0i32;
11387
11388 for selection in self.selections.all::<usize>(cx) {
11389 let selection_is_empty = selection.is_empty();
11390
11391 let (start, end) = if selection_is_empty {
11392 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11393 (word_range.start, word_range.end)
11394 } else {
11395 (selection.start, selection.end)
11396 };
11397
11398 let text = buffer.text_for_range(start..end).collect::<String>();
11399 let old_length = text.len() as i32;
11400 let text = callback(&text);
11401
11402 new_selections.push(Selection {
11403 start: (start as i32 - selection_adjustment) as usize,
11404 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11405 goal: SelectionGoal::None,
11406 ..selection
11407 });
11408
11409 selection_adjustment += old_length - text.len() as i32;
11410
11411 edits.push((start..end, text));
11412 }
11413
11414 self.transact(window, cx, |this, window, cx| {
11415 this.buffer.update(cx, |buffer, cx| {
11416 buffer.edit(edits, None, cx);
11417 });
11418
11419 this.change_selections(Default::default(), window, cx, |s| {
11420 s.select(new_selections);
11421 });
11422
11423 this.request_autoscroll(Autoscroll::fit(), cx);
11424 });
11425 }
11426
11427 pub fn move_selection_on_drop(
11428 &mut self,
11429 selection: &Selection<Anchor>,
11430 target: DisplayPoint,
11431 is_cut: bool,
11432 window: &mut Window,
11433 cx: &mut Context<Self>,
11434 ) {
11435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11436 let buffer = &display_map.buffer_snapshot;
11437 let mut edits = Vec::new();
11438 let insert_point = display_map
11439 .clip_point(target, Bias::Left)
11440 .to_point(&display_map);
11441 let text = buffer
11442 .text_for_range(selection.start..selection.end)
11443 .collect::<String>();
11444 if is_cut {
11445 edits.push(((selection.start..selection.end), String::new()));
11446 }
11447 let insert_anchor = buffer.anchor_before(insert_point);
11448 edits.push(((insert_anchor..insert_anchor), text));
11449 let last_edit_start = insert_anchor.bias_left(buffer);
11450 let last_edit_end = insert_anchor.bias_right(buffer);
11451 self.transact(window, cx, |this, window, cx| {
11452 this.buffer.update(cx, |buffer, cx| {
11453 buffer.edit(edits, None, cx);
11454 });
11455 this.change_selections(Default::default(), window, cx, |s| {
11456 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11457 });
11458 });
11459 }
11460
11461 pub fn clear_selection_drag_state(&mut self) {
11462 self.selection_drag_state = SelectionDragState::None;
11463 }
11464
11465 pub fn duplicate(
11466 &mut self,
11467 upwards: bool,
11468 whole_lines: bool,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11473
11474 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11475 let buffer = &display_map.buffer_snapshot;
11476 let selections = self.selections.all::<Point>(cx);
11477
11478 let mut edits = Vec::new();
11479 let mut selections_iter = selections.iter().peekable();
11480 while let Some(selection) = selections_iter.next() {
11481 let mut rows = selection.spanned_rows(false, &display_map);
11482 // duplicate line-wise
11483 if whole_lines || selection.start == selection.end {
11484 // Avoid duplicating the same lines twice.
11485 while let Some(next_selection) = selections_iter.peek() {
11486 let next_rows = next_selection.spanned_rows(false, &display_map);
11487 if next_rows.start < rows.end {
11488 rows.end = next_rows.end;
11489 selections_iter.next().unwrap();
11490 } else {
11491 break;
11492 }
11493 }
11494
11495 // Copy the text from the selected row region and splice it either at the start
11496 // or end of the region.
11497 let start = Point::new(rows.start.0, 0);
11498 let end = Point::new(
11499 rows.end.previous_row().0,
11500 buffer.line_len(rows.end.previous_row()),
11501 );
11502 let text = buffer
11503 .text_for_range(start..end)
11504 .chain(Some("\n"))
11505 .collect::<String>();
11506 let insert_location = if upwards {
11507 Point::new(rows.end.0, 0)
11508 } else {
11509 start
11510 };
11511 edits.push((insert_location..insert_location, text));
11512 } else {
11513 // duplicate character-wise
11514 let start = selection.start;
11515 let end = selection.end;
11516 let text = buffer.text_for_range(start..end).collect::<String>();
11517 edits.push((selection.end..selection.end, text));
11518 }
11519 }
11520
11521 self.transact(window, cx, |this, _, cx| {
11522 this.buffer.update(cx, |buffer, cx| {
11523 buffer.edit(edits, None, cx);
11524 });
11525
11526 this.request_autoscroll(Autoscroll::fit(), cx);
11527 });
11528 }
11529
11530 pub fn duplicate_line_up(
11531 &mut self,
11532 _: &DuplicateLineUp,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 self.duplicate(true, true, window, cx);
11537 }
11538
11539 pub fn duplicate_line_down(
11540 &mut self,
11541 _: &DuplicateLineDown,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.duplicate(false, true, window, cx);
11546 }
11547
11548 pub fn duplicate_selection(
11549 &mut self,
11550 _: &DuplicateSelection,
11551 window: &mut Window,
11552 cx: &mut Context<Self>,
11553 ) {
11554 self.duplicate(false, false, window, cx);
11555 }
11556
11557 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11559 if self.mode.is_single_line() {
11560 cx.propagate();
11561 return;
11562 }
11563
11564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11565 let buffer = self.buffer.read(cx).snapshot(cx);
11566
11567 let mut edits = Vec::new();
11568 let mut unfold_ranges = Vec::new();
11569 let mut refold_creases = Vec::new();
11570
11571 let selections = self.selections.all::<Point>(cx);
11572 let mut selections = selections.iter().peekable();
11573 let mut contiguous_row_selections = Vec::new();
11574 let mut new_selections = Vec::new();
11575
11576 while let Some(selection) = selections.next() {
11577 // Find all the selections that span a contiguous row range
11578 let (start_row, end_row) = consume_contiguous_rows(
11579 &mut contiguous_row_selections,
11580 selection,
11581 &display_map,
11582 &mut selections,
11583 );
11584
11585 // Move the text spanned by the row range to be before the line preceding the row range
11586 if start_row.0 > 0 {
11587 let range_to_move = Point::new(
11588 start_row.previous_row().0,
11589 buffer.line_len(start_row.previous_row()),
11590 )
11591 ..Point::new(
11592 end_row.previous_row().0,
11593 buffer.line_len(end_row.previous_row()),
11594 );
11595 let insertion_point = display_map
11596 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11597 .0;
11598
11599 // Don't move lines across excerpts
11600 if buffer
11601 .excerpt_containing(insertion_point..range_to_move.end)
11602 .is_some()
11603 {
11604 let text = buffer
11605 .text_for_range(range_to_move.clone())
11606 .flat_map(|s| s.chars())
11607 .skip(1)
11608 .chain(['\n'])
11609 .collect::<String>();
11610
11611 edits.push((
11612 buffer.anchor_after(range_to_move.start)
11613 ..buffer.anchor_before(range_to_move.end),
11614 String::new(),
11615 ));
11616 let insertion_anchor = buffer.anchor_after(insertion_point);
11617 edits.push((insertion_anchor..insertion_anchor, text));
11618
11619 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11620
11621 // Move selections up
11622 new_selections.extend(contiguous_row_selections.drain(..).map(
11623 |mut selection| {
11624 selection.start.row -= row_delta;
11625 selection.end.row -= row_delta;
11626 selection
11627 },
11628 ));
11629
11630 // Move folds up
11631 unfold_ranges.push(range_to_move.clone());
11632 for fold in display_map.folds_in_range(
11633 buffer.anchor_before(range_to_move.start)
11634 ..buffer.anchor_after(range_to_move.end),
11635 ) {
11636 let mut start = fold.range.start.to_point(&buffer);
11637 let mut end = fold.range.end.to_point(&buffer);
11638 start.row -= row_delta;
11639 end.row -= row_delta;
11640 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11641 }
11642 }
11643 }
11644
11645 // If we didn't move line(s), preserve the existing selections
11646 new_selections.append(&mut contiguous_row_selections);
11647 }
11648
11649 self.transact(window, cx, |this, window, cx| {
11650 this.unfold_ranges(&unfold_ranges, true, true, cx);
11651 this.buffer.update(cx, |buffer, cx| {
11652 for (range, text) in edits {
11653 buffer.edit([(range, text)], None, cx);
11654 }
11655 });
11656 this.fold_creases(refold_creases, true, window, cx);
11657 this.change_selections(Default::default(), window, cx, |s| {
11658 s.select(new_selections);
11659 })
11660 });
11661 }
11662
11663 pub fn move_line_down(
11664 &mut self,
11665 _: &MoveLineDown,
11666 window: &mut Window,
11667 cx: &mut Context<Self>,
11668 ) {
11669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11670 if self.mode.is_single_line() {
11671 cx.propagate();
11672 return;
11673 }
11674
11675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11676 let buffer = self.buffer.read(cx).snapshot(cx);
11677
11678 let mut edits = Vec::new();
11679 let mut unfold_ranges = Vec::new();
11680 let mut refold_creases = Vec::new();
11681
11682 let selections = self.selections.all::<Point>(cx);
11683 let mut selections = selections.iter().peekable();
11684 let mut contiguous_row_selections = Vec::new();
11685 let mut new_selections = Vec::new();
11686
11687 while let Some(selection) = selections.next() {
11688 // Find all the selections that span a contiguous row range
11689 let (start_row, end_row) = consume_contiguous_rows(
11690 &mut contiguous_row_selections,
11691 selection,
11692 &display_map,
11693 &mut selections,
11694 );
11695
11696 // Move the text spanned by the row range to be after the last line of the row range
11697 if end_row.0 <= buffer.max_point().row {
11698 let range_to_move =
11699 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11700 let insertion_point = display_map
11701 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11702 .0;
11703
11704 // Don't move lines across excerpt boundaries
11705 if buffer
11706 .excerpt_containing(range_to_move.start..insertion_point)
11707 .is_some()
11708 {
11709 let mut text = String::from("\n");
11710 text.extend(buffer.text_for_range(range_to_move.clone()));
11711 text.pop(); // Drop trailing newline
11712 edits.push((
11713 buffer.anchor_after(range_to_move.start)
11714 ..buffer.anchor_before(range_to_move.end),
11715 String::new(),
11716 ));
11717 let insertion_anchor = buffer.anchor_after(insertion_point);
11718 edits.push((insertion_anchor..insertion_anchor, text));
11719
11720 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11721
11722 // Move selections down
11723 new_selections.extend(contiguous_row_selections.drain(..).map(
11724 |mut selection| {
11725 selection.start.row += row_delta;
11726 selection.end.row += row_delta;
11727 selection
11728 },
11729 ));
11730
11731 // Move folds down
11732 unfold_ranges.push(range_to_move.clone());
11733 for fold in display_map.folds_in_range(
11734 buffer.anchor_before(range_to_move.start)
11735 ..buffer.anchor_after(range_to_move.end),
11736 ) {
11737 let mut start = fold.range.start.to_point(&buffer);
11738 let mut end = fold.range.end.to_point(&buffer);
11739 start.row += row_delta;
11740 end.row += row_delta;
11741 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11742 }
11743 }
11744 }
11745
11746 // If we didn't move line(s), preserve the existing selections
11747 new_selections.append(&mut contiguous_row_selections);
11748 }
11749
11750 self.transact(window, cx, |this, window, cx| {
11751 this.unfold_ranges(&unfold_ranges, true, true, cx);
11752 this.buffer.update(cx, |buffer, cx| {
11753 for (range, text) in edits {
11754 buffer.edit([(range, text)], None, cx);
11755 }
11756 });
11757 this.fold_creases(refold_creases, true, window, cx);
11758 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11759 });
11760 }
11761
11762 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11764 let text_layout_details = &self.text_layout_details(window);
11765 self.transact(window, cx, |this, window, cx| {
11766 let edits = this.change_selections(Default::default(), window, cx, |s| {
11767 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11768 s.move_with(|display_map, selection| {
11769 if !selection.is_empty() {
11770 return;
11771 }
11772
11773 let mut head = selection.head();
11774 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11775 if head.column() == display_map.line_len(head.row()) {
11776 transpose_offset = display_map
11777 .buffer_snapshot
11778 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11779 }
11780
11781 if transpose_offset == 0 {
11782 return;
11783 }
11784
11785 *head.column_mut() += 1;
11786 head = display_map.clip_point(head, Bias::Right);
11787 let goal = SelectionGoal::HorizontalPosition(
11788 display_map
11789 .x_for_display_point(head, text_layout_details)
11790 .into(),
11791 );
11792 selection.collapse_to(head, goal);
11793
11794 let transpose_start = display_map
11795 .buffer_snapshot
11796 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11797 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11798 let transpose_end = display_map
11799 .buffer_snapshot
11800 .clip_offset(transpose_offset + 1, Bias::Right);
11801 if let Some(ch) =
11802 display_map.buffer_snapshot.chars_at(transpose_start).next()
11803 {
11804 edits.push((transpose_start..transpose_offset, String::new()));
11805 edits.push((transpose_end..transpose_end, ch.to_string()));
11806 }
11807 }
11808 });
11809 edits
11810 });
11811 this.buffer
11812 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11813 let selections = this.selections.all::<usize>(cx);
11814 this.change_selections(Default::default(), window, cx, |s| {
11815 s.select(selections);
11816 });
11817 });
11818 }
11819
11820 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11822 if self.mode.is_single_line() {
11823 cx.propagate();
11824 return;
11825 }
11826
11827 self.rewrap_impl(RewrapOptions::default(), cx)
11828 }
11829
11830 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11831 let buffer = self.buffer.read(cx).snapshot(cx);
11832 let selections = self.selections.all::<Point>(cx);
11833
11834 #[derive(Clone, Debug, PartialEq)]
11835 enum CommentFormat {
11836 /// single line comment, with prefix for line
11837 Line(String),
11838 /// single line within a block comment, with prefix for line
11839 BlockLine(String),
11840 /// a single line of a block comment that includes the initial delimiter
11841 BlockCommentWithStart(BlockCommentConfig),
11842 /// a single line of a block comment that includes the ending delimiter
11843 BlockCommentWithEnd(BlockCommentConfig),
11844 }
11845
11846 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11847 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11848 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11849 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11850 .peekable();
11851
11852 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11853 row
11854 } else {
11855 return Vec::new();
11856 };
11857
11858 let language_settings = buffer.language_settings_at(selection.head(), cx);
11859 let language_scope = buffer.language_scope_at(selection.head());
11860
11861 let indent_and_prefix_for_row =
11862 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11863 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11864 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11865 &language_scope
11866 {
11867 let indent_end = Point::new(row, indent.len);
11868 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11869 let line_text_after_indent = buffer
11870 .text_for_range(indent_end..line_end)
11871 .collect::<String>();
11872
11873 let is_within_comment_override = buffer
11874 .language_scope_at(indent_end)
11875 .is_some_and(|scope| scope.override_name() == Some("comment"));
11876 let comment_delimiters = if is_within_comment_override {
11877 // we are within a comment syntax node, but we don't
11878 // yet know what kind of comment: block, doc or line
11879 match (
11880 language_scope.documentation_comment(),
11881 language_scope.block_comment(),
11882 ) {
11883 (Some(config), _) | (_, Some(config))
11884 if buffer.contains_str_at(indent_end, &config.start) =>
11885 {
11886 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11887 }
11888 (Some(config), _) | (_, Some(config))
11889 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11890 {
11891 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11892 }
11893 (Some(config), _) | (_, Some(config))
11894 if buffer.contains_str_at(indent_end, &config.prefix) =>
11895 {
11896 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11897 }
11898 (_, _) => language_scope
11899 .line_comment_prefixes()
11900 .iter()
11901 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11902 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11903 }
11904 } else {
11905 // we not in an overridden comment node, but we may
11906 // be within a non-overridden line comment node
11907 language_scope
11908 .line_comment_prefixes()
11909 .iter()
11910 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11911 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11912 };
11913
11914 let rewrap_prefix = language_scope
11915 .rewrap_prefixes()
11916 .iter()
11917 .find_map(|prefix_regex| {
11918 prefix_regex.find(&line_text_after_indent).map(|mat| {
11919 if mat.start() == 0 {
11920 Some(mat.as_str().to_string())
11921 } else {
11922 None
11923 }
11924 })
11925 })
11926 .flatten();
11927 (comment_delimiters, rewrap_prefix)
11928 } else {
11929 (None, None)
11930 };
11931 (indent, comment_prefix, rewrap_prefix)
11932 };
11933
11934 let mut ranges = Vec::new();
11935 let from_empty_selection = selection.is_empty();
11936
11937 let mut current_range_start = first_row;
11938 let mut prev_row = first_row;
11939 let (
11940 mut current_range_indent,
11941 mut current_range_comment_delimiters,
11942 mut current_range_rewrap_prefix,
11943 ) = indent_and_prefix_for_row(first_row);
11944
11945 for row in non_blank_rows_iter.skip(1) {
11946 let has_paragraph_break = row > prev_row + 1;
11947
11948 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11949 indent_and_prefix_for_row(row);
11950
11951 let has_indent_change = row_indent != current_range_indent;
11952 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11953
11954 let has_boundary_change = has_comment_change
11955 || row_rewrap_prefix.is_some()
11956 || (has_indent_change && current_range_comment_delimiters.is_some());
11957
11958 if has_paragraph_break || has_boundary_change {
11959 ranges.push((
11960 language_settings.clone(),
11961 Point::new(current_range_start, 0)
11962 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11963 current_range_indent,
11964 current_range_comment_delimiters.clone(),
11965 current_range_rewrap_prefix.clone(),
11966 from_empty_selection,
11967 ));
11968 current_range_start = row;
11969 current_range_indent = row_indent;
11970 current_range_comment_delimiters = row_comment_delimiters;
11971 current_range_rewrap_prefix = row_rewrap_prefix;
11972 }
11973 prev_row = row;
11974 }
11975
11976 ranges.push((
11977 language_settings.clone(),
11978 Point::new(current_range_start, 0)
11979 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11980 current_range_indent,
11981 current_range_comment_delimiters,
11982 current_range_rewrap_prefix,
11983 from_empty_selection,
11984 ));
11985
11986 ranges
11987 });
11988
11989 let mut edits = Vec::new();
11990 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11991
11992 for (
11993 language_settings,
11994 wrap_range,
11995 mut indent_size,
11996 comment_prefix,
11997 rewrap_prefix,
11998 from_empty_selection,
11999 ) in wrap_ranges
12000 {
12001 let mut start_row = wrap_range.start.row;
12002 let mut end_row = wrap_range.end.row;
12003
12004 // Skip selections that overlap with a range that has already been rewrapped.
12005 let selection_range = start_row..end_row;
12006 if rewrapped_row_ranges
12007 .iter()
12008 .any(|range| range.overlaps(&selection_range))
12009 {
12010 continue;
12011 }
12012
12013 let tab_size = language_settings.tab_size;
12014
12015 let (line_prefix, inside_comment) = match &comment_prefix {
12016 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12017 (Some(prefix.as_str()), true)
12018 }
12019 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12020 (Some(prefix.as_ref()), true)
12021 }
12022 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12023 start: _,
12024 end: _,
12025 prefix,
12026 tab_size,
12027 })) => {
12028 indent_size.len += tab_size;
12029 (Some(prefix.as_ref()), true)
12030 }
12031 None => (None, false),
12032 };
12033 let indent_prefix = indent_size.chars().collect::<String>();
12034 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12035
12036 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12037 RewrapBehavior::InComments => inside_comment,
12038 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12039 RewrapBehavior::Anywhere => true,
12040 };
12041
12042 let should_rewrap = options.override_language_settings
12043 || allow_rewrap_based_on_language
12044 || self.hard_wrap.is_some();
12045 if !should_rewrap {
12046 continue;
12047 }
12048
12049 if from_empty_selection {
12050 'expand_upwards: while start_row > 0 {
12051 let prev_row = start_row - 1;
12052 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12053 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12054 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12055 {
12056 start_row = prev_row;
12057 } else {
12058 break 'expand_upwards;
12059 }
12060 }
12061
12062 'expand_downwards: while end_row < buffer.max_point().row {
12063 let next_row = end_row + 1;
12064 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12065 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12066 && !buffer.is_line_blank(MultiBufferRow(next_row))
12067 {
12068 end_row = next_row;
12069 } else {
12070 break 'expand_downwards;
12071 }
12072 }
12073 }
12074
12075 let start = Point::new(start_row, 0);
12076 let start_offset = start.to_offset(&buffer);
12077 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12078 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12079 let mut first_line_delimiter = None;
12080 let mut last_line_delimiter = None;
12081 let Some(lines_without_prefixes) = selection_text
12082 .lines()
12083 .enumerate()
12084 .map(|(ix, line)| {
12085 let line_trimmed = line.trim_start();
12086 if rewrap_prefix.is_some() && ix > 0 {
12087 Ok(line_trimmed)
12088 } else if let Some(
12089 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12090 start,
12091 prefix,
12092 end,
12093 tab_size,
12094 })
12095 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12096 start,
12097 prefix,
12098 end,
12099 tab_size,
12100 }),
12101 ) = &comment_prefix
12102 {
12103 let line_trimmed = line_trimmed
12104 .strip_prefix(start.as_ref())
12105 .map(|s| {
12106 let mut indent_size = indent_size;
12107 indent_size.len -= tab_size;
12108 let indent_prefix: String = indent_size.chars().collect();
12109 first_line_delimiter = Some((indent_prefix, start));
12110 s.trim_start()
12111 })
12112 .unwrap_or(line_trimmed);
12113 let line_trimmed = line_trimmed
12114 .strip_suffix(end.as_ref())
12115 .map(|s| {
12116 last_line_delimiter = Some(end);
12117 s.trim_end()
12118 })
12119 .unwrap_or(line_trimmed);
12120 let line_trimmed = line_trimmed
12121 .strip_prefix(prefix.as_ref())
12122 .unwrap_or(line_trimmed);
12123 Ok(line_trimmed)
12124 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12125 line_trimmed.strip_prefix(prefix).with_context(|| {
12126 format!("line did not start with prefix {prefix:?}: {line:?}")
12127 })
12128 } else {
12129 line_trimmed
12130 .strip_prefix(&line_prefix.trim_start())
12131 .with_context(|| {
12132 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12133 })
12134 }
12135 })
12136 .collect::<Result<Vec<_>, _>>()
12137 .log_err()
12138 else {
12139 continue;
12140 };
12141
12142 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12143 buffer
12144 .language_settings_at(Point::new(start_row, 0), cx)
12145 .preferred_line_length as usize
12146 });
12147
12148 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12149 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12150 } else {
12151 line_prefix.clone()
12152 };
12153
12154 let wrapped_text = {
12155 let mut wrapped_text = wrap_with_prefix(
12156 line_prefix,
12157 subsequent_lines_prefix,
12158 lines_without_prefixes.join("\n"),
12159 wrap_column,
12160 tab_size,
12161 options.preserve_existing_whitespace,
12162 );
12163
12164 if let Some((indent, delimiter)) = first_line_delimiter {
12165 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12166 }
12167 if let Some(last_line) = last_line_delimiter {
12168 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12169 }
12170
12171 wrapped_text
12172 };
12173
12174 // TODO: should always use char-based diff while still supporting cursor behavior that
12175 // matches vim.
12176 let mut diff_options = DiffOptions::default();
12177 if options.override_language_settings {
12178 diff_options.max_word_diff_len = 0;
12179 diff_options.max_word_diff_line_count = 0;
12180 } else {
12181 diff_options.max_word_diff_len = usize::MAX;
12182 diff_options.max_word_diff_line_count = usize::MAX;
12183 }
12184
12185 for (old_range, new_text) in
12186 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12187 {
12188 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12189 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12190 edits.push((edit_start..edit_end, new_text));
12191 }
12192
12193 rewrapped_row_ranges.push(start_row..=end_row);
12194 }
12195
12196 self.buffer
12197 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12198 }
12199
12200 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12201 let mut text = String::new();
12202 let buffer = self.buffer.read(cx).snapshot(cx);
12203 let mut selections = self.selections.all::<Point>(cx);
12204 let mut clipboard_selections = Vec::with_capacity(selections.len());
12205 {
12206 let max_point = buffer.max_point();
12207 let mut is_first = true;
12208 for selection in &mut selections {
12209 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12210 if is_entire_line {
12211 selection.start = Point::new(selection.start.row, 0);
12212 if !selection.is_empty() && selection.end.column == 0 {
12213 selection.end = cmp::min(max_point, selection.end);
12214 } else {
12215 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12216 }
12217 selection.goal = SelectionGoal::None;
12218 }
12219 if is_first {
12220 is_first = false;
12221 } else {
12222 text += "\n";
12223 }
12224 let mut len = 0;
12225 for chunk in buffer.text_for_range(selection.start..selection.end) {
12226 text.push_str(chunk);
12227 len += chunk.len();
12228 }
12229 clipboard_selections.push(ClipboardSelection {
12230 len,
12231 is_entire_line,
12232 first_line_indent: buffer
12233 .indent_size_for_line(MultiBufferRow(selection.start.row))
12234 .len,
12235 });
12236 }
12237 }
12238
12239 self.transact(window, cx, |this, window, cx| {
12240 this.change_selections(Default::default(), window, cx, |s| {
12241 s.select(selections);
12242 });
12243 this.insert("", window, cx);
12244 });
12245 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12246 }
12247
12248 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12249 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12250 let item = self.cut_common(window, cx);
12251 cx.write_to_clipboard(item);
12252 }
12253
12254 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12256 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12257 s.move_with(|snapshot, sel| {
12258 if sel.is_empty() {
12259 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12260 }
12261 });
12262 });
12263 let item = self.cut_common(window, cx);
12264 cx.set_global(KillRing(item))
12265 }
12266
12267 pub fn kill_ring_yank(
12268 &mut self,
12269 _: &KillRingYank,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12274 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12275 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12276 (kill_ring.text().to_string(), kill_ring.metadata_json())
12277 } else {
12278 return;
12279 }
12280 } else {
12281 return;
12282 };
12283 self.do_paste(&text, metadata, false, window, cx);
12284 }
12285
12286 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12287 self.do_copy(true, cx);
12288 }
12289
12290 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12291 self.do_copy(false, cx);
12292 }
12293
12294 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12295 let selections = self.selections.all::<Point>(cx);
12296 let buffer = self.buffer.read(cx).read(cx);
12297 let mut text = String::new();
12298
12299 let mut clipboard_selections = Vec::with_capacity(selections.len());
12300 {
12301 let max_point = buffer.max_point();
12302 let mut is_first = true;
12303 for selection in &selections {
12304 let mut start = selection.start;
12305 let mut end = selection.end;
12306 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12307 if is_entire_line {
12308 start = Point::new(start.row, 0);
12309 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12310 }
12311
12312 let mut trimmed_selections = Vec::new();
12313 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12314 let row = MultiBufferRow(start.row);
12315 let first_indent = buffer.indent_size_for_line(row);
12316 if first_indent.len == 0 || start.column > first_indent.len {
12317 trimmed_selections.push(start..end);
12318 } else {
12319 trimmed_selections.push(
12320 Point::new(row.0, first_indent.len)
12321 ..Point::new(row.0, buffer.line_len(row)),
12322 );
12323 for row in start.row + 1..=end.row {
12324 let mut line_len = buffer.line_len(MultiBufferRow(row));
12325 if row == end.row {
12326 line_len = end.column;
12327 }
12328 if line_len == 0 {
12329 trimmed_selections
12330 .push(Point::new(row, 0)..Point::new(row, line_len));
12331 continue;
12332 }
12333 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12334 if row_indent_size.len >= first_indent.len {
12335 trimmed_selections.push(
12336 Point::new(row, first_indent.len)..Point::new(row, line_len),
12337 );
12338 } else {
12339 trimmed_selections.clear();
12340 trimmed_selections.push(start..end);
12341 break;
12342 }
12343 }
12344 }
12345 } else {
12346 trimmed_selections.push(start..end);
12347 }
12348
12349 for trimmed_range in trimmed_selections {
12350 if is_first {
12351 is_first = false;
12352 } else {
12353 text += "\n";
12354 }
12355 let mut len = 0;
12356 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12357 text.push_str(chunk);
12358 len += chunk.len();
12359 }
12360 clipboard_selections.push(ClipboardSelection {
12361 len,
12362 is_entire_line,
12363 first_line_indent: buffer
12364 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12365 .len,
12366 });
12367 }
12368 }
12369 }
12370
12371 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12372 text,
12373 clipboard_selections,
12374 ));
12375 }
12376
12377 pub fn do_paste(
12378 &mut self,
12379 text: &String,
12380 clipboard_selections: Option<Vec<ClipboardSelection>>,
12381 handle_entire_lines: bool,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 if self.read_only(cx) {
12386 return;
12387 }
12388
12389 let clipboard_text = Cow::Borrowed(text);
12390
12391 self.transact(window, cx, |this, window, cx| {
12392 let had_active_edit_prediction = this.has_active_edit_prediction();
12393
12394 if let Some(mut clipboard_selections) = clipboard_selections {
12395 let old_selections = this.selections.all::<usize>(cx);
12396 let all_selections_were_entire_line =
12397 clipboard_selections.iter().all(|s| s.is_entire_line);
12398 let first_selection_indent_column =
12399 clipboard_selections.first().map(|s| s.first_line_indent);
12400 if clipboard_selections.len() != old_selections.len() {
12401 clipboard_selections.drain(..);
12402 }
12403 let cursor_offset = this.selections.last::<usize>(cx).head();
12404 let mut auto_indent_on_paste = true;
12405
12406 this.buffer.update(cx, |buffer, cx| {
12407 let snapshot = buffer.read(cx);
12408 auto_indent_on_paste = snapshot
12409 .language_settings_at(cursor_offset, cx)
12410 .auto_indent_on_paste;
12411
12412 let mut start_offset = 0;
12413 let mut edits = Vec::new();
12414 let mut original_indent_columns = Vec::new();
12415 for (ix, selection) in old_selections.iter().enumerate() {
12416 let to_insert;
12417 let entire_line;
12418 let original_indent_column;
12419 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12420 let end_offset = start_offset + clipboard_selection.len;
12421 to_insert = &clipboard_text[start_offset..end_offset];
12422 entire_line = clipboard_selection.is_entire_line;
12423 start_offset = end_offset + 1;
12424 original_indent_column = Some(clipboard_selection.first_line_indent);
12425 } else {
12426 to_insert = clipboard_text.as_str();
12427 entire_line = all_selections_were_entire_line;
12428 original_indent_column = first_selection_indent_column
12429 }
12430
12431 // If the corresponding selection was empty when this slice of the
12432 // clipboard text was written, then the entire line containing the
12433 // selection was copied. If this selection is also currently empty,
12434 // then paste the line before the current line of the buffer.
12435 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12436 let column = selection.start.to_point(&snapshot).column as usize;
12437 let line_start = selection.start - column;
12438 line_start..line_start
12439 } else {
12440 selection.range()
12441 };
12442
12443 edits.push((range, to_insert));
12444 original_indent_columns.push(original_indent_column);
12445 }
12446 drop(snapshot);
12447
12448 buffer.edit(
12449 edits,
12450 if auto_indent_on_paste {
12451 Some(AutoindentMode::Block {
12452 original_indent_columns,
12453 })
12454 } else {
12455 None
12456 },
12457 cx,
12458 );
12459 });
12460
12461 let selections = this.selections.all::<usize>(cx);
12462 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12463 } else {
12464 this.insert(&clipboard_text, window, cx);
12465 }
12466
12467 let trigger_in_words =
12468 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12469
12470 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12471 });
12472 }
12473
12474 pub fn diff_clipboard_with_selection(
12475 &mut self,
12476 _: &DiffClipboardWithSelection,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 let selections = self.selections.all::<usize>(cx);
12481
12482 if selections.is_empty() {
12483 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12484 return;
12485 };
12486
12487 let clipboard_text = match cx.read_from_clipboard() {
12488 Some(item) => match item.entries().first() {
12489 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12490 _ => None,
12491 },
12492 None => None,
12493 };
12494
12495 let Some(clipboard_text) = clipboard_text else {
12496 log::warn!("Clipboard doesn't contain text.");
12497 return;
12498 };
12499
12500 window.dispatch_action(
12501 Box::new(DiffClipboardWithSelectionData {
12502 clipboard_text,
12503 editor: cx.entity(),
12504 }),
12505 cx,
12506 );
12507 }
12508
12509 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12510 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12511 if let Some(item) = cx.read_from_clipboard() {
12512 let entries = item.entries();
12513
12514 match entries.first() {
12515 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12516 // of all the pasted entries.
12517 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12518 .do_paste(
12519 clipboard_string.text(),
12520 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12521 true,
12522 window,
12523 cx,
12524 ),
12525 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12526 }
12527 }
12528 }
12529
12530 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12531 if self.read_only(cx) {
12532 return;
12533 }
12534
12535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12536
12537 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12538 if let Some((selections, _)) =
12539 self.selection_history.transaction(transaction_id).cloned()
12540 {
12541 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12542 s.select_anchors(selections.to_vec());
12543 });
12544 } else {
12545 log::error!(
12546 "No entry in selection_history found for undo. \
12547 This may correspond to a bug where undo does not update the selection. \
12548 If this is occurring, please add details to \
12549 https://github.com/zed-industries/zed/issues/22692"
12550 );
12551 }
12552 self.request_autoscroll(Autoscroll::fit(), cx);
12553 self.unmark_text(window, cx);
12554 self.refresh_edit_prediction(true, false, window, cx);
12555 cx.emit(EditorEvent::Edited { transaction_id });
12556 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12557 }
12558 }
12559
12560 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12561 if self.read_only(cx) {
12562 return;
12563 }
12564
12565 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12566
12567 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12568 if let Some((_, Some(selections))) =
12569 self.selection_history.transaction(transaction_id).cloned()
12570 {
12571 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12572 s.select_anchors(selections.to_vec());
12573 });
12574 } else {
12575 log::error!(
12576 "No entry in selection_history found for redo. \
12577 This may correspond to a bug where undo does not update the selection. \
12578 If this is occurring, please add details to \
12579 https://github.com/zed-industries/zed/issues/22692"
12580 );
12581 }
12582 self.request_autoscroll(Autoscroll::fit(), cx);
12583 self.unmark_text(window, cx);
12584 self.refresh_edit_prediction(true, false, window, cx);
12585 cx.emit(EditorEvent::Edited { transaction_id });
12586 }
12587 }
12588
12589 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12590 self.buffer
12591 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12592 }
12593
12594 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12595 self.buffer
12596 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12597 }
12598
12599 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12601 self.change_selections(Default::default(), window, cx, |s| {
12602 s.move_with(|map, selection| {
12603 let cursor = if selection.is_empty() {
12604 movement::left(map, selection.start)
12605 } else {
12606 selection.start
12607 };
12608 selection.collapse_to(cursor, SelectionGoal::None);
12609 });
12610 })
12611 }
12612
12613 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12615 self.change_selections(Default::default(), window, cx, |s| {
12616 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12617 })
12618 }
12619
12620 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12622 self.change_selections(Default::default(), window, cx, |s| {
12623 s.move_with(|map, selection| {
12624 let cursor = if selection.is_empty() {
12625 movement::right(map, selection.end)
12626 } else {
12627 selection.end
12628 };
12629 selection.collapse_to(cursor, SelectionGoal::None)
12630 });
12631 })
12632 }
12633
12634 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12636 self.change_selections(Default::default(), window, cx, |s| {
12637 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12638 })
12639 }
12640
12641 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12642 if self.take_rename(true, window, cx).is_some() {
12643 return;
12644 }
12645
12646 if self.mode.is_single_line() {
12647 cx.propagate();
12648 return;
12649 }
12650
12651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12652
12653 let text_layout_details = &self.text_layout_details(window);
12654 let selection_count = self.selections.count();
12655 let first_selection = self.selections.first_anchor();
12656
12657 self.change_selections(Default::default(), window, cx, |s| {
12658 s.move_with(|map, selection| {
12659 if !selection.is_empty() {
12660 selection.goal = SelectionGoal::None;
12661 }
12662 let (cursor, goal) = movement::up(
12663 map,
12664 selection.start,
12665 selection.goal,
12666 false,
12667 text_layout_details,
12668 );
12669 selection.collapse_to(cursor, goal);
12670 });
12671 });
12672
12673 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12674 {
12675 cx.propagate();
12676 }
12677 }
12678
12679 pub fn move_up_by_lines(
12680 &mut self,
12681 action: &MoveUpByLines,
12682 window: &mut Window,
12683 cx: &mut Context<Self>,
12684 ) {
12685 if self.take_rename(true, window, cx).is_some() {
12686 return;
12687 }
12688
12689 if self.mode.is_single_line() {
12690 cx.propagate();
12691 return;
12692 }
12693
12694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12695
12696 let text_layout_details = &self.text_layout_details(window);
12697
12698 self.change_selections(Default::default(), window, cx, |s| {
12699 s.move_with(|map, selection| {
12700 if !selection.is_empty() {
12701 selection.goal = SelectionGoal::None;
12702 }
12703 let (cursor, goal) = movement::up_by_rows(
12704 map,
12705 selection.start,
12706 action.lines,
12707 selection.goal,
12708 false,
12709 text_layout_details,
12710 );
12711 selection.collapse_to(cursor, goal);
12712 });
12713 })
12714 }
12715
12716 pub fn move_down_by_lines(
12717 &mut self,
12718 action: &MoveDownByLines,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 if self.take_rename(true, window, cx).is_some() {
12723 return;
12724 }
12725
12726 if self.mode.is_single_line() {
12727 cx.propagate();
12728 return;
12729 }
12730
12731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12732
12733 let text_layout_details = &self.text_layout_details(window);
12734
12735 self.change_selections(Default::default(), window, cx, |s| {
12736 s.move_with(|map, selection| {
12737 if !selection.is_empty() {
12738 selection.goal = SelectionGoal::None;
12739 }
12740 let (cursor, goal) = movement::down_by_rows(
12741 map,
12742 selection.start,
12743 action.lines,
12744 selection.goal,
12745 false,
12746 text_layout_details,
12747 );
12748 selection.collapse_to(cursor, goal);
12749 });
12750 })
12751 }
12752
12753 pub fn select_down_by_lines(
12754 &mut self,
12755 action: &SelectDownByLines,
12756 window: &mut Window,
12757 cx: &mut Context<Self>,
12758 ) {
12759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12760 let text_layout_details = &self.text_layout_details(window);
12761 self.change_selections(Default::default(), window, cx, |s| {
12762 s.move_heads_with(|map, head, goal| {
12763 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12764 })
12765 })
12766 }
12767
12768 pub fn select_up_by_lines(
12769 &mut self,
12770 action: &SelectUpByLines,
12771 window: &mut Window,
12772 cx: &mut Context<Self>,
12773 ) {
12774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12775 let text_layout_details = &self.text_layout_details(window);
12776 self.change_selections(Default::default(), window, cx, |s| {
12777 s.move_heads_with(|map, head, goal| {
12778 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12779 })
12780 })
12781 }
12782
12783 pub fn select_page_up(
12784 &mut self,
12785 _: &SelectPageUp,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 let Some(row_count) = self.visible_row_count() else {
12790 return;
12791 };
12792
12793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12794
12795 let text_layout_details = &self.text_layout_details(window);
12796
12797 self.change_selections(Default::default(), window, cx, |s| {
12798 s.move_heads_with(|map, head, goal| {
12799 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12800 })
12801 })
12802 }
12803
12804 pub fn move_page_up(
12805 &mut self,
12806 action: &MovePageUp,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 if self.take_rename(true, window, cx).is_some() {
12811 return;
12812 }
12813
12814 if self
12815 .context_menu
12816 .borrow_mut()
12817 .as_mut()
12818 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12819 .unwrap_or(false)
12820 {
12821 return;
12822 }
12823
12824 if matches!(self.mode, EditorMode::SingleLine) {
12825 cx.propagate();
12826 return;
12827 }
12828
12829 let Some(row_count) = self.visible_row_count() else {
12830 return;
12831 };
12832
12833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12834
12835 let effects = if action.center_cursor {
12836 SelectionEffects::scroll(Autoscroll::center())
12837 } else {
12838 SelectionEffects::default()
12839 };
12840
12841 let text_layout_details = &self.text_layout_details(window);
12842
12843 self.change_selections(effects, window, cx, |s| {
12844 s.move_with(|map, selection| {
12845 if !selection.is_empty() {
12846 selection.goal = SelectionGoal::None;
12847 }
12848 let (cursor, goal) = movement::up_by_rows(
12849 map,
12850 selection.end,
12851 row_count,
12852 selection.goal,
12853 false,
12854 text_layout_details,
12855 );
12856 selection.collapse_to(cursor, goal);
12857 });
12858 });
12859 }
12860
12861 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12863 let text_layout_details = &self.text_layout_details(window);
12864 self.change_selections(Default::default(), window, cx, |s| {
12865 s.move_heads_with(|map, head, goal| {
12866 movement::up(map, head, goal, false, text_layout_details)
12867 })
12868 })
12869 }
12870
12871 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12872 self.take_rename(true, window, cx);
12873
12874 if self.mode.is_single_line() {
12875 cx.propagate();
12876 return;
12877 }
12878
12879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12880
12881 let text_layout_details = &self.text_layout_details(window);
12882 let selection_count = self.selections.count();
12883 let first_selection = self.selections.first_anchor();
12884
12885 self.change_selections(Default::default(), window, cx, |s| {
12886 s.move_with(|map, selection| {
12887 if !selection.is_empty() {
12888 selection.goal = SelectionGoal::None;
12889 }
12890 let (cursor, goal) = movement::down(
12891 map,
12892 selection.end,
12893 selection.goal,
12894 false,
12895 text_layout_details,
12896 );
12897 selection.collapse_to(cursor, goal);
12898 });
12899 });
12900
12901 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12902 {
12903 cx.propagate();
12904 }
12905 }
12906
12907 pub fn select_page_down(
12908 &mut self,
12909 _: &SelectPageDown,
12910 window: &mut Window,
12911 cx: &mut Context<Self>,
12912 ) {
12913 let Some(row_count) = self.visible_row_count() else {
12914 return;
12915 };
12916
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918
12919 let text_layout_details = &self.text_layout_details(window);
12920
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_heads_with(|map, head, goal| {
12923 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12924 })
12925 })
12926 }
12927
12928 pub fn move_page_down(
12929 &mut self,
12930 action: &MovePageDown,
12931 window: &mut Window,
12932 cx: &mut Context<Self>,
12933 ) {
12934 if self.take_rename(true, window, cx).is_some() {
12935 return;
12936 }
12937
12938 if self
12939 .context_menu
12940 .borrow_mut()
12941 .as_mut()
12942 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12943 .unwrap_or(false)
12944 {
12945 return;
12946 }
12947
12948 if matches!(self.mode, EditorMode::SingleLine) {
12949 cx.propagate();
12950 return;
12951 }
12952
12953 let Some(row_count) = self.visible_row_count() else {
12954 return;
12955 };
12956
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958
12959 let effects = if action.center_cursor {
12960 SelectionEffects::scroll(Autoscroll::center())
12961 } else {
12962 SelectionEffects::default()
12963 };
12964
12965 let text_layout_details = &self.text_layout_details(window);
12966 self.change_selections(effects, window, cx, |s| {
12967 s.move_with(|map, selection| {
12968 if !selection.is_empty() {
12969 selection.goal = SelectionGoal::None;
12970 }
12971 let (cursor, goal) = movement::down_by_rows(
12972 map,
12973 selection.end,
12974 row_count,
12975 selection.goal,
12976 false,
12977 text_layout_details,
12978 );
12979 selection.collapse_to(cursor, goal);
12980 });
12981 });
12982 }
12983
12984 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 let text_layout_details = &self.text_layout_details(window);
12987 self.change_selections(Default::default(), window, cx, |s| {
12988 s.move_heads_with(|map, head, goal| {
12989 movement::down(map, head, goal, false, text_layout_details)
12990 })
12991 });
12992 }
12993
12994 pub fn context_menu_first(
12995 &mut self,
12996 _: &ContextMenuFirst,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13001 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13002 }
13003 }
13004
13005 pub fn context_menu_prev(
13006 &mut self,
13007 _: &ContextMenuPrevious,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) {
13011 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13012 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13013 }
13014 }
13015
13016 pub fn context_menu_next(
13017 &mut self,
13018 _: &ContextMenuNext,
13019 window: &mut Window,
13020 cx: &mut Context<Self>,
13021 ) {
13022 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13023 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13024 }
13025 }
13026
13027 pub fn context_menu_last(
13028 &mut self,
13029 _: &ContextMenuLast,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13034 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13035 }
13036 }
13037
13038 pub fn signature_help_prev(
13039 &mut self,
13040 _: &SignatureHelpPrevious,
13041 _: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 if let Some(popover) = self.signature_help_state.popover_mut() {
13045 if popover.current_signature == 0 {
13046 popover.current_signature = popover.signatures.len() - 1;
13047 } else {
13048 popover.current_signature -= 1;
13049 }
13050 cx.notify();
13051 }
13052 }
13053
13054 pub fn signature_help_next(
13055 &mut self,
13056 _: &SignatureHelpNext,
13057 _: &mut Window,
13058 cx: &mut Context<Self>,
13059 ) {
13060 if let Some(popover) = self.signature_help_state.popover_mut() {
13061 if popover.current_signature + 1 == popover.signatures.len() {
13062 popover.current_signature = 0;
13063 } else {
13064 popover.current_signature += 1;
13065 }
13066 cx.notify();
13067 }
13068 }
13069
13070 pub fn move_to_previous_word_start(
13071 &mut self,
13072 _: &MoveToPreviousWordStart,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13077 self.change_selections(Default::default(), window, cx, |s| {
13078 s.move_cursors_with(|map, head, _| {
13079 (
13080 movement::previous_word_start(map, head),
13081 SelectionGoal::None,
13082 )
13083 });
13084 })
13085 }
13086
13087 pub fn move_to_previous_subword_start(
13088 &mut self,
13089 _: &MoveToPreviousSubwordStart,
13090 window: &mut Window,
13091 cx: &mut Context<Self>,
13092 ) {
13093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13094 self.change_selections(Default::default(), window, cx, |s| {
13095 s.move_cursors_with(|map, head, _| {
13096 (
13097 movement::previous_subword_start(map, head),
13098 SelectionGoal::None,
13099 )
13100 });
13101 })
13102 }
13103
13104 pub fn select_to_previous_word_start(
13105 &mut self,
13106 _: &SelectToPreviousWordStart,
13107 window: &mut Window,
13108 cx: &mut Context<Self>,
13109 ) {
13110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13111 self.change_selections(Default::default(), window, cx, |s| {
13112 s.move_heads_with(|map, head, _| {
13113 (
13114 movement::previous_word_start(map, head),
13115 SelectionGoal::None,
13116 )
13117 });
13118 })
13119 }
13120
13121 pub fn select_to_previous_subword_start(
13122 &mut self,
13123 _: &SelectToPreviousSubwordStart,
13124 window: &mut Window,
13125 cx: &mut Context<Self>,
13126 ) {
13127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13128 self.change_selections(Default::default(), window, cx, |s| {
13129 s.move_heads_with(|map, head, _| {
13130 (
13131 movement::previous_subword_start(map, head),
13132 SelectionGoal::None,
13133 )
13134 });
13135 })
13136 }
13137
13138 pub fn delete_to_previous_word_start(
13139 &mut self,
13140 action: &DeleteToPreviousWordStart,
13141 window: &mut Window,
13142 cx: &mut Context<Self>,
13143 ) {
13144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13145 self.transact(window, cx, |this, window, cx| {
13146 this.select_autoclose_pair(window, cx);
13147 this.change_selections(Default::default(), window, cx, |s| {
13148 s.move_with(|map, selection| {
13149 if selection.is_empty() {
13150 let cursor = if action.ignore_newlines {
13151 movement::previous_word_start(map, selection.head())
13152 } else {
13153 movement::previous_word_start_or_newline(map, selection.head())
13154 };
13155 selection.set_head(cursor, SelectionGoal::None);
13156 }
13157 });
13158 });
13159 this.insert("", window, cx);
13160 });
13161 }
13162
13163 pub fn delete_to_previous_subword_start(
13164 &mut self,
13165 _: &DeleteToPreviousSubwordStart,
13166 window: &mut Window,
13167 cx: &mut Context<Self>,
13168 ) {
13169 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13170 self.transact(window, cx, |this, window, cx| {
13171 this.select_autoclose_pair(window, cx);
13172 this.change_selections(Default::default(), window, cx, |s| {
13173 s.move_with(|map, selection| {
13174 if selection.is_empty() {
13175 let cursor = movement::previous_subword_start(map, selection.head());
13176 selection.set_head(cursor, SelectionGoal::None);
13177 }
13178 });
13179 });
13180 this.insert("", window, cx);
13181 });
13182 }
13183
13184 pub fn move_to_next_word_end(
13185 &mut self,
13186 _: &MoveToNextWordEnd,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13191 self.change_selections(Default::default(), window, cx, |s| {
13192 s.move_cursors_with(|map, head, _| {
13193 (movement::next_word_end(map, head), SelectionGoal::None)
13194 });
13195 })
13196 }
13197
13198 pub fn move_to_next_subword_end(
13199 &mut self,
13200 _: &MoveToNextSubwordEnd,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205 self.change_selections(Default::default(), window, cx, |s| {
13206 s.move_cursors_with(|map, head, _| {
13207 (movement::next_subword_end(map, head), SelectionGoal::None)
13208 });
13209 })
13210 }
13211
13212 pub fn select_to_next_word_end(
13213 &mut self,
13214 _: &SelectToNextWordEnd,
13215 window: &mut Window,
13216 cx: &mut Context<Self>,
13217 ) {
13218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13219 self.change_selections(Default::default(), window, cx, |s| {
13220 s.move_heads_with(|map, head, _| {
13221 (movement::next_word_end(map, head), SelectionGoal::None)
13222 });
13223 })
13224 }
13225
13226 pub fn select_to_next_subword_end(
13227 &mut self,
13228 _: &SelectToNextSubwordEnd,
13229 window: &mut Window,
13230 cx: &mut Context<Self>,
13231 ) {
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_heads_with(|map, head, _| {
13235 (movement::next_subword_end(map, head), SelectionGoal::None)
13236 });
13237 })
13238 }
13239
13240 pub fn delete_to_next_word_end(
13241 &mut self,
13242 action: &DeleteToNextWordEnd,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13247 self.transact(window, cx, |this, window, cx| {
13248 this.change_selections(Default::default(), window, cx, |s| {
13249 s.move_with(|map, selection| {
13250 if selection.is_empty() {
13251 let cursor = if action.ignore_newlines {
13252 movement::next_word_end(map, selection.head())
13253 } else {
13254 movement::next_word_end_or_newline(map, selection.head())
13255 };
13256 selection.set_head(cursor, SelectionGoal::None);
13257 }
13258 });
13259 });
13260 this.insert("", window, cx);
13261 });
13262 }
13263
13264 pub fn delete_to_next_subword_end(
13265 &mut self,
13266 _: &DeleteToNextSubwordEnd,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13271 self.transact(window, cx, |this, window, cx| {
13272 this.change_selections(Default::default(), window, cx, |s| {
13273 s.move_with(|map, selection| {
13274 if selection.is_empty() {
13275 let cursor = movement::next_subword_end(map, selection.head());
13276 selection.set_head(cursor, SelectionGoal::None);
13277 }
13278 });
13279 });
13280 this.insert("", window, cx);
13281 });
13282 }
13283
13284 pub fn move_to_beginning_of_line(
13285 &mut self,
13286 action: &MoveToBeginningOfLine,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13291 self.change_selections(Default::default(), window, cx, |s| {
13292 s.move_cursors_with(|map, head, _| {
13293 (
13294 movement::indented_line_beginning(
13295 map,
13296 head,
13297 action.stop_at_soft_wraps,
13298 action.stop_at_indent,
13299 ),
13300 SelectionGoal::None,
13301 )
13302 });
13303 })
13304 }
13305
13306 pub fn select_to_beginning_of_line(
13307 &mut self,
13308 action: &SelectToBeginningOfLine,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13313 self.change_selections(Default::default(), window, cx, |s| {
13314 s.move_heads_with(|map, head, _| {
13315 (
13316 movement::indented_line_beginning(
13317 map,
13318 head,
13319 action.stop_at_soft_wraps,
13320 action.stop_at_indent,
13321 ),
13322 SelectionGoal::None,
13323 )
13324 });
13325 });
13326 }
13327
13328 pub fn delete_to_beginning_of_line(
13329 &mut self,
13330 action: &DeleteToBeginningOfLine,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13335 self.transact(window, cx, |this, window, cx| {
13336 this.change_selections(Default::default(), window, cx, |s| {
13337 s.move_with(|_, selection| {
13338 selection.reversed = true;
13339 });
13340 });
13341
13342 this.select_to_beginning_of_line(
13343 &SelectToBeginningOfLine {
13344 stop_at_soft_wraps: false,
13345 stop_at_indent: action.stop_at_indent,
13346 },
13347 window,
13348 cx,
13349 );
13350 this.backspace(&Backspace, window, cx);
13351 });
13352 }
13353
13354 pub fn move_to_end_of_line(
13355 &mut self,
13356 action: &MoveToEndOfLine,
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_cursors_with(|map, head, _| {
13363 (
13364 movement::line_end(map, head, action.stop_at_soft_wraps),
13365 SelectionGoal::None,
13366 )
13367 });
13368 })
13369 }
13370
13371 pub fn select_to_end_of_line(
13372 &mut self,
13373 action: &SelectToEndOfLine,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) {
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (
13381 movement::line_end(map, head, action.stop_at_soft_wraps),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn delete_to_end_of_line(
13389 &mut self,
13390 _: &DeleteToEndOfLine,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13395 self.transact(window, cx, |this, window, cx| {
13396 this.select_to_end_of_line(
13397 &SelectToEndOfLine {
13398 stop_at_soft_wraps: false,
13399 },
13400 window,
13401 cx,
13402 );
13403 this.delete(&Delete, window, cx);
13404 });
13405 }
13406
13407 pub fn cut_to_end_of_line(
13408 &mut self,
13409 _: &CutToEndOfLine,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13414 self.transact(window, cx, |this, window, cx| {
13415 this.select_to_end_of_line(
13416 &SelectToEndOfLine {
13417 stop_at_soft_wraps: false,
13418 },
13419 window,
13420 cx,
13421 );
13422 this.cut(&Cut, window, cx);
13423 });
13424 }
13425
13426 pub fn move_to_start_of_paragraph(
13427 &mut self,
13428 _: &MoveToStartOfParagraph,
13429 window: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 if matches!(self.mode, EditorMode::SingleLine) {
13433 cx.propagate();
13434 return;
13435 }
13436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13437 self.change_selections(Default::default(), window, cx, |s| {
13438 s.move_with(|map, selection| {
13439 selection.collapse_to(
13440 movement::start_of_paragraph(map, selection.head(), 1),
13441 SelectionGoal::None,
13442 )
13443 });
13444 })
13445 }
13446
13447 pub fn move_to_end_of_paragraph(
13448 &mut self,
13449 _: &MoveToEndOfParagraph,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 if matches!(self.mode, EditorMode::SingleLine) {
13454 cx.propagate();
13455 return;
13456 }
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_with(|map, selection| {
13460 selection.collapse_to(
13461 movement::end_of_paragraph(map, selection.head(), 1),
13462 SelectionGoal::None,
13463 )
13464 });
13465 })
13466 }
13467
13468 pub fn select_to_start_of_paragraph(
13469 &mut self,
13470 _: &SelectToStartOfParagraph,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 if matches!(self.mode, EditorMode::SingleLine) {
13475 cx.propagate();
13476 return;
13477 }
13478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13479 self.change_selections(Default::default(), window, cx, |s| {
13480 s.move_heads_with(|map, head, _| {
13481 (
13482 movement::start_of_paragraph(map, head, 1),
13483 SelectionGoal::None,
13484 )
13485 });
13486 })
13487 }
13488
13489 pub fn select_to_end_of_paragraph(
13490 &mut self,
13491 _: &SelectToEndOfParagraph,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 if matches!(self.mode, EditorMode::SingleLine) {
13496 cx.propagate();
13497 return;
13498 }
13499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13500 self.change_selections(Default::default(), window, cx, |s| {
13501 s.move_heads_with(|map, head, _| {
13502 (
13503 movement::end_of_paragraph(map, head, 1),
13504 SelectionGoal::None,
13505 )
13506 });
13507 })
13508 }
13509
13510 pub fn move_to_start_of_excerpt(
13511 &mut self,
13512 _: &MoveToStartOfExcerpt,
13513 window: &mut Window,
13514 cx: &mut Context<Self>,
13515 ) {
13516 if matches!(self.mode, EditorMode::SingleLine) {
13517 cx.propagate();
13518 return;
13519 }
13520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13521 self.change_selections(Default::default(), window, cx, |s| {
13522 s.move_with(|map, selection| {
13523 selection.collapse_to(
13524 movement::start_of_excerpt(
13525 map,
13526 selection.head(),
13527 workspace::searchable::Direction::Prev,
13528 ),
13529 SelectionGoal::None,
13530 )
13531 });
13532 })
13533 }
13534
13535 pub fn move_to_start_of_next_excerpt(
13536 &mut self,
13537 _: &MoveToStartOfNextExcerpt,
13538 window: &mut Window,
13539 cx: &mut Context<Self>,
13540 ) {
13541 if matches!(self.mode, EditorMode::SingleLine) {
13542 cx.propagate();
13543 return;
13544 }
13545
13546 self.change_selections(Default::default(), window, cx, |s| {
13547 s.move_with(|map, selection| {
13548 selection.collapse_to(
13549 movement::start_of_excerpt(
13550 map,
13551 selection.head(),
13552 workspace::searchable::Direction::Next,
13553 ),
13554 SelectionGoal::None,
13555 )
13556 });
13557 })
13558 }
13559
13560 pub fn move_to_end_of_excerpt(
13561 &mut self,
13562 _: &MoveToEndOfExcerpt,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 if matches!(self.mode, EditorMode::SingleLine) {
13567 cx.propagate();
13568 return;
13569 }
13570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13571 self.change_selections(Default::default(), window, cx, |s| {
13572 s.move_with(|map, selection| {
13573 selection.collapse_to(
13574 movement::end_of_excerpt(
13575 map,
13576 selection.head(),
13577 workspace::searchable::Direction::Next,
13578 ),
13579 SelectionGoal::None,
13580 )
13581 });
13582 })
13583 }
13584
13585 pub fn move_to_end_of_previous_excerpt(
13586 &mut self,
13587 _: &MoveToEndOfPreviousExcerpt,
13588 window: &mut Window,
13589 cx: &mut Context<Self>,
13590 ) {
13591 if matches!(self.mode, EditorMode::SingleLine) {
13592 cx.propagate();
13593 return;
13594 }
13595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13596 self.change_selections(Default::default(), window, cx, |s| {
13597 s.move_with(|map, selection| {
13598 selection.collapse_to(
13599 movement::end_of_excerpt(
13600 map,
13601 selection.head(),
13602 workspace::searchable::Direction::Prev,
13603 ),
13604 SelectionGoal::None,
13605 )
13606 });
13607 })
13608 }
13609
13610 pub fn select_to_start_of_excerpt(
13611 &mut self,
13612 _: &SelectToStartOfExcerpt,
13613 window: &mut Window,
13614 cx: &mut Context<Self>,
13615 ) {
13616 if matches!(self.mode, EditorMode::SingleLine) {
13617 cx.propagate();
13618 return;
13619 }
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 self.change_selections(Default::default(), window, cx, |s| {
13622 s.move_heads_with(|map, head, _| {
13623 (
13624 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13625 SelectionGoal::None,
13626 )
13627 });
13628 })
13629 }
13630
13631 pub fn select_to_start_of_next_excerpt(
13632 &mut self,
13633 _: &SelectToStartOfNextExcerpt,
13634 window: &mut Window,
13635 cx: &mut Context<Self>,
13636 ) {
13637 if matches!(self.mode, EditorMode::SingleLine) {
13638 cx.propagate();
13639 return;
13640 }
13641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13642 self.change_selections(Default::default(), window, cx, |s| {
13643 s.move_heads_with(|map, head, _| {
13644 (
13645 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13646 SelectionGoal::None,
13647 )
13648 });
13649 })
13650 }
13651
13652 pub fn select_to_end_of_excerpt(
13653 &mut self,
13654 _: &SelectToEndOfExcerpt,
13655 window: &mut Window,
13656 cx: &mut Context<Self>,
13657 ) {
13658 if matches!(self.mode, EditorMode::SingleLine) {
13659 cx.propagate();
13660 return;
13661 }
13662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13663 self.change_selections(Default::default(), window, cx, |s| {
13664 s.move_heads_with(|map, head, _| {
13665 (
13666 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13667 SelectionGoal::None,
13668 )
13669 });
13670 })
13671 }
13672
13673 pub fn select_to_end_of_previous_excerpt(
13674 &mut self,
13675 _: &SelectToEndOfPreviousExcerpt,
13676 window: &mut Window,
13677 cx: &mut Context<Self>,
13678 ) {
13679 if matches!(self.mode, EditorMode::SingleLine) {
13680 cx.propagate();
13681 return;
13682 }
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13684 self.change_selections(Default::default(), window, cx, |s| {
13685 s.move_heads_with(|map, head, _| {
13686 (
13687 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13688 SelectionGoal::None,
13689 )
13690 });
13691 })
13692 }
13693
13694 pub fn move_to_beginning(
13695 &mut self,
13696 _: &MoveToBeginning,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 if matches!(self.mode, EditorMode::SingleLine) {
13701 cx.propagate();
13702 return;
13703 }
13704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13705 self.change_selections(Default::default(), window, cx, |s| {
13706 s.select_ranges(vec![0..0]);
13707 });
13708 }
13709
13710 pub fn select_to_beginning(
13711 &mut self,
13712 _: &SelectToBeginning,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 let mut selection = self.selections.last::<Point>(cx);
13717 selection.set_head(Point::zero(), SelectionGoal::None);
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.select(vec![selection]);
13721 });
13722 }
13723
13724 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13725 if matches!(self.mode, EditorMode::SingleLine) {
13726 cx.propagate();
13727 return;
13728 }
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13730 let cursor = self.buffer.read(cx).read(cx).len();
13731 self.change_selections(Default::default(), window, cx, |s| {
13732 s.select_ranges(vec![cursor..cursor])
13733 });
13734 }
13735
13736 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13737 self.nav_history = nav_history;
13738 }
13739
13740 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13741 self.nav_history.as_ref()
13742 }
13743
13744 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13745 self.push_to_nav_history(
13746 self.selections.newest_anchor().head(),
13747 None,
13748 false,
13749 true,
13750 cx,
13751 );
13752 }
13753
13754 fn push_to_nav_history(
13755 &mut self,
13756 cursor_anchor: Anchor,
13757 new_position: Option<Point>,
13758 is_deactivate: bool,
13759 always: bool,
13760 cx: &mut Context<Self>,
13761 ) {
13762 if let Some(nav_history) = self.nav_history.as_mut() {
13763 let buffer = self.buffer.read(cx).read(cx);
13764 let cursor_position = cursor_anchor.to_point(&buffer);
13765 let scroll_state = self.scroll_manager.anchor();
13766 let scroll_top_row = scroll_state.top_row(&buffer);
13767 drop(buffer);
13768
13769 if let Some(new_position) = new_position {
13770 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13771 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13772 return;
13773 }
13774 }
13775
13776 nav_history.push(
13777 Some(NavigationData {
13778 cursor_anchor,
13779 cursor_position,
13780 scroll_anchor: scroll_state,
13781 scroll_top_row,
13782 }),
13783 cx,
13784 );
13785 cx.emit(EditorEvent::PushedToNavHistory {
13786 anchor: cursor_anchor,
13787 is_deactivate,
13788 })
13789 }
13790 }
13791
13792 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13794 let buffer = self.buffer.read(cx).snapshot(cx);
13795 let mut selection = self.selections.first::<usize>(cx);
13796 selection.set_head(buffer.len(), SelectionGoal::None);
13797 self.change_selections(Default::default(), window, cx, |s| {
13798 s.select(vec![selection]);
13799 });
13800 }
13801
13802 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13804 let end = self.buffer.read(cx).read(cx).len();
13805 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13806 s.select_ranges(vec![0..end]);
13807 });
13808 }
13809
13810 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13813 let mut selections = self.selections.all::<Point>(cx);
13814 let max_point = display_map.buffer_snapshot.max_point();
13815 for selection in &mut selections {
13816 let rows = selection.spanned_rows(true, &display_map);
13817 selection.start = Point::new(rows.start.0, 0);
13818 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13819 selection.reversed = false;
13820 }
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.select(selections);
13823 });
13824 }
13825
13826 pub fn split_selection_into_lines(
13827 &mut self,
13828 action: &SplitSelectionIntoLines,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 let selections = self
13833 .selections
13834 .all::<Point>(cx)
13835 .into_iter()
13836 .map(|selection| selection.start..selection.end)
13837 .collect::<Vec<_>>();
13838 self.unfold_ranges(&selections, true, true, cx);
13839
13840 let mut new_selection_ranges = Vec::new();
13841 {
13842 let buffer = self.buffer.read(cx).read(cx);
13843 for selection in selections {
13844 for row in selection.start.row..selection.end.row {
13845 let line_start = Point::new(row, 0);
13846 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13847
13848 if action.keep_selections {
13849 // Keep the selection range for each line
13850 let selection_start = if row == selection.start.row {
13851 selection.start
13852 } else {
13853 line_start
13854 };
13855 new_selection_ranges.push(selection_start..line_end);
13856 } else {
13857 // Collapse to cursor at end of line
13858 new_selection_ranges.push(line_end..line_end);
13859 }
13860 }
13861
13862 let is_multiline_selection = selection.start.row != selection.end.row;
13863 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13864 // so this action feels more ergonomic when paired with other selection operations
13865 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13866 if !should_skip_last {
13867 if action.keep_selections {
13868 if is_multiline_selection {
13869 let line_start = Point::new(selection.end.row, 0);
13870 new_selection_ranges.push(line_start..selection.end);
13871 } else {
13872 new_selection_ranges.push(selection.start..selection.end);
13873 }
13874 } else {
13875 new_selection_ranges.push(selection.end..selection.end);
13876 }
13877 }
13878 }
13879 }
13880 self.change_selections(Default::default(), window, cx, |s| {
13881 s.select_ranges(new_selection_ranges);
13882 });
13883 }
13884
13885 pub fn add_selection_above(
13886 &mut self,
13887 _: &AddSelectionAbove,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 self.add_selection(true, window, cx);
13892 }
13893
13894 pub fn add_selection_below(
13895 &mut self,
13896 _: &AddSelectionBelow,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 self.add_selection(false, window, cx);
13901 }
13902
13903 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905
13906 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13907 let all_selections = self.selections.all::<Point>(cx);
13908 let text_layout_details = self.text_layout_details(window);
13909
13910 let (mut columnar_selections, new_selections_to_columnarize) = {
13911 if let Some(state) = self.add_selections_state.as_ref() {
13912 let columnar_selection_ids: HashSet<_> = state
13913 .groups
13914 .iter()
13915 .flat_map(|group| group.stack.iter())
13916 .copied()
13917 .collect();
13918
13919 all_selections
13920 .into_iter()
13921 .partition(|s| columnar_selection_ids.contains(&s.id))
13922 } else {
13923 (Vec::new(), all_selections)
13924 }
13925 };
13926
13927 let mut state = self
13928 .add_selections_state
13929 .take()
13930 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13931
13932 for selection in new_selections_to_columnarize {
13933 let range = selection.display_range(&display_map).sorted();
13934 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13935 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13936 let positions = start_x.min(end_x)..start_x.max(end_x);
13937 let mut stack = Vec::new();
13938 for row in range.start.row().0..=range.end.row().0 {
13939 if let Some(selection) = self.selections.build_columnar_selection(
13940 &display_map,
13941 DisplayRow(row),
13942 &positions,
13943 selection.reversed,
13944 &text_layout_details,
13945 ) {
13946 stack.push(selection.id);
13947 columnar_selections.push(selection);
13948 }
13949 }
13950 if !stack.is_empty() {
13951 if above {
13952 stack.reverse();
13953 }
13954 state.groups.push(AddSelectionsGroup { above, stack });
13955 }
13956 }
13957
13958 let mut final_selections = Vec::new();
13959 let end_row = if above {
13960 DisplayRow(0)
13961 } else {
13962 display_map.max_point().row()
13963 };
13964
13965 let mut last_added_item_per_group = HashMap::default();
13966 for group in state.groups.iter_mut() {
13967 if let Some(last_id) = group.stack.last() {
13968 last_added_item_per_group.insert(*last_id, group);
13969 }
13970 }
13971
13972 for selection in columnar_selections {
13973 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13974 if above == group.above {
13975 let range = selection.display_range(&display_map).sorted();
13976 debug_assert_eq!(range.start.row(), range.end.row());
13977 let mut row = range.start.row();
13978 let positions =
13979 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13980 px(start)..px(end)
13981 } else {
13982 let start_x =
13983 display_map.x_for_display_point(range.start, &text_layout_details);
13984 let end_x =
13985 display_map.x_for_display_point(range.end, &text_layout_details);
13986 start_x.min(end_x)..start_x.max(end_x)
13987 };
13988
13989 let mut maybe_new_selection = None;
13990 while row != end_row {
13991 if above {
13992 row.0 -= 1;
13993 } else {
13994 row.0 += 1;
13995 }
13996 if let Some(new_selection) = self.selections.build_columnar_selection(
13997 &display_map,
13998 row,
13999 &positions,
14000 selection.reversed,
14001 &text_layout_details,
14002 ) {
14003 maybe_new_selection = Some(new_selection);
14004 break;
14005 }
14006 }
14007
14008 if let Some(new_selection) = maybe_new_selection {
14009 group.stack.push(new_selection.id);
14010 if above {
14011 final_selections.push(new_selection);
14012 final_selections.push(selection);
14013 } else {
14014 final_selections.push(selection);
14015 final_selections.push(new_selection);
14016 }
14017 } else {
14018 final_selections.push(selection);
14019 }
14020 } else {
14021 group.stack.pop();
14022 }
14023 } else {
14024 final_selections.push(selection);
14025 }
14026 }
14027
14028 self.change_selections(Default::default(), window, cx, |s| {
14029 s.select(final_selections);
14030 });
14031
14032 let final_selection_ids: HashSet<_> = self
14033 .selections
14034 .all::<Point>(cx)
14035 .iter()
14036 .map(|s| s.id)
14037 .collect();
14038 state.groups.retain_mut(|group| {
14039 // selections might get merged above so we remove invalid items from stacks
14040 group.stack.retain(|id| final_selection_ids.contains(id));
14041
14042 // single selection in stack can be treated as initial state
14043 group.stack.len() > 1
14044 });
14045
14046 if !state.groups.is_empty() {
14047 self.add_selections_state = Some(state);
14048 }
14049 }
14050
14051 fn select_match_ranges(
14052 &mut self,
14053 range: Range<usize>,
14054 reversed: bool,
14055 replace_newest: bool,
14056 auto_scroll: Option<Autoscroll>,
14057 window: &mut Window,
14058 cx: &mut Context<Editor>,
14059 ) {
14060 self.unfold_ranges(
14061 std::slice::from_ref(&range),
14062 false,
14063 auto_scroll.is_some(),
14064 cx,
14065 );
14066 let effects = if let Some(scroll) = auto_scroll {
14067 SelectionEffects::scroll(scroll)
14068 } else {
14069 SelectionEffects::no_scroll()
14070 };
14071 self.change_selections(effects, window, cx, |s| {
14072 if replace_newest {
14073 s.delete(s.newest_anchor().id);
14074 }
14075 if reversed {
14076 s.insert_range(range.end..range.start);
14077 } else {
14078 s.insert_range(range);
14079 }
14080 });
14081 }
14082
14083 pub fn select_next_match_internal(
14084 &mut self,
14085 display_map: &DisplaySnapshot,
14086 replace_newest: bool,
14087 autoscroll: Option<Autoscroll>,
14088 window: &mut Window,
14089 cx: &mut Context<Self>,
14090 ) -> Result<()> {
14091 let buffer = &display_map.buffer_snapshot;
14092 let mut selections = self.selections.all::<usize>(cx);
14093 if let Some(mut select_next_state) = self.select_next_state.take() {
14094 let query = &select_next_state.query;
14095 if !select_next_state.done {
14096 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14097 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14098 let mut next_selected_range = None;
14099
14100 let bytes_after_last_selection =
14101 buffer.bytes_in_range(last_selection.end..buffer.len());
14102 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14103 let query_matches = query
14104 .stream_find_iter(bytes_after_last_selection)
14105 .map(|result| (last_selection.end, result))
14106 .chain(
14107 query
14108 .stream_find_iter(bytes_before_first_selection)
14109 .map(|result| (0, result)),
14110 );
14111
14112 for (start_offset, query_match) in query_matches {
14113 let query_match = query_match.unwrap(); // can only fail due to I/O
14114 let offset_range =
14115 start_offset + query_match.start()..start_offset + query_match.end();
14116
14117 if !select_next_state.wordwise
14118 || (!buffer.is_inside_word(offset_range.start, false)
14119 && !buffer.is_inside_word(offset_range.end, false))
14120 {
14121 // TODO: This is n^2, because we might check all the selections
14122 if !selections
14123 .iter()
14124 .any(|selection| selection.range().overlaps(&offset_range))
14125 {
14126 next_selected_range = Some(offset_range);
14127 break;
14128 }
14129 }
14130 }
14131
14132 if let Some(next_selected_range) = next_selected_range {
14133 self.select_match_ranges(
14134 next_selected_range,
14135 last_selection.reversed,
14136 replace_newest,
14137 autoscroll,
14138 window,
14139 cx,
14140 );
14141 } else {
14142 select_next_state.done = true;
14143 }
14144 }
14145
14146 self.select_next_state = Some(select_next_state);
14147 } else {
14148 let mut only_carets = true;
14149 let mut same_text_selected = true;
14150 let mut selected_text = None;
14151
14152 let mut selections_iter = selections.iter().peekable();
14153 while let Some(selection) = selections_iter.next() {
14154 if selection.start != selection.end {
14155 only_carets = false;
14156 }
14157
14158 if same_text_selected {
14159 if selected_text.is_none() {
14160 selected_text =
14161 Some(buffer.text_for_range(selection.range()).collect::<String>());
14162 }
14163
14164 if let Some(next_selection) = selections_iter.peek() {
14165 if next_selection.range().len() == selection.range().len() {
14166 let next_selected_text = buffer
14167 .text_for_range(next_selection.range())
14168 .collect::<String>();
14169 if Some(next_selected_text) != selected_text {
14170 same_text_selected = false;
14171 selected_text = None;
14172 }
14173 } else {
14174 same_text_selected = false;
14175 selected_text = None;
14176 }
14177 }
14178 }
14179 }
14180
14181 if only_carets {
14182 for selection in &mut selections {
14183 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14184 selection.start = word_range.start;
14185 selection.end = word_range.end;
14186 selection.goal = SelectionGoal::None;
14187 selection.reversed = false;
14188 self.select_match_ranges(
14189 selection.start..selection.end,
14190 selection.reversed,
14191 replace_newest,
14192 autoscroll,
14193 window,
14194 cx,
14195 );
14196 }
14197
14198 if selections.len() == 1 {
14199 let selection = selections
14200 .last()
14201 .expect("ensured that there's only one selection");
14202 let query = buffer
14203 .text_for_range(selection.start..selection.end)
14204 .collect::<String>();
14205 let is_empty = query.is_empty();
14206 let select_state = SelectNextState {
14207 query: AhoCorasick::new(&[query])?,
14208 wordwise: true,
14209 done: is_empty,
14210 };
14211 self.select_next_state = Some(select_state);
14212 } else {
14213 self.select_next_state = None;
14214 }
14215 } else if let Some(selected_text) = selected_text {
14216 self.select_next_state = Some(SelectNextState {
14217 query: AhoCorasick::new(&[selected_text])?,
14218 wordwise: false,
14219 done: false,
14220 });
14221 self.select_next_match_internal(
14222 display_map,
14223 replace_newest,
14224 autoscroll,
14225 window,
14226 cx,
14227 )?;
14228 }
14229 }
14230 Ok(())
14231 }
14232
14233 pub fn select_all_matches(
14234 &mut self,
14235 _action: &SelectAllMatches,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) -> Result<()> {
14239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14240
14241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14242
14243 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14244 let Some(select_next_state) = self.select_next_state.as_mut() else {
14245 return Ok(());
14246 };
14247 if select_next_state.done {
14248 return Ok(());
14249 }
14250
14251 let mut new_selections = Vec::new();
14252
14253 let reversed = self.selections.oldest::<usize>(cx).reversed;
14254 let buffer = &display_map.buffer_snapshot;
14255 let query_matches = select_next_state
14256 .query
14257 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14258
14259 for query_match in query_matches.into_iter() {
14260 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14261 let offset_range = if reversed {
14262 query_match.end()..query_match.start()
14263 } else {
14264 query_match.start()..query_match.end()
14265 };
14266
14267 if !select_next_state.wordwise
14268 || (!buffer.is_inside_word(offset_range.start, false)
14269 && !buffer.is_inside_word(offset_range.end, false))
14270 {
14271 new_selections.push(offset_range.start..offset_range.end);
14272 }
14273 }
14274
14275 select_next_state.done = true;
14276
14277 if new_selections.is_empty() {
14278 log::error!("bug: new_selections is empty in select_all_matches");
14279 return Ok(());
14280 }
14281
14282 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14283 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14284 selections.select_ranges(new_selections)
14285 });
14286
14287 Ok(())
14288 }
14289
14290 pub fn select_next(
14291 &mut self,
14292 action: &SelectNext,
14293 window: &mut Window,
14294 cx: &mut Context<Self>,
14295 ) -> Result<()> {
14296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14298 self.select_next_match_internal(
14299 &display_map,
14300 action.replace_newest,
14301 Some(Autoscroll::newest()),
14302 window,
14303 cx,
14304 )?;
14305 Ok(())
14306 }
14307
14308 pub fn select_previous(
14309 &mut self,
14310 action: &SelectPrevious,
14311 window: &mut Window,
14312 cx: &mut Context<Self>,
14313 ) -> Result<()> {
14314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14316 let buffer = &display_map.buffer_snapshot;
14317 let mut selections = self.selections.all::<usize>(cx);
14318 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14319 let query = &select_prev_state.query;
14320 if !select_prev_state.done {
14321 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14322 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14323 let mut next_selected_range = None;
14324 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14325 let bytes_before_last_selection =
14326 buffer.reversed_bytes_in_range(0..last_selection.start);
14327 let bytes_after_first_selection =
14328 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14329 let query_matches = query
14330 .stream_find_iter(bytes_before_last_selection)
14331 .map(|result| (last_selection.start, result))
14332 .chain(
14333 query
14334 .stream_find_iter(bytes_after_first_selection)
14335 .map(|result| (buffer.len(), result)),
14336 );
14337 for (end_offset, query_match) in query_matches {
14338 let query_match = query_match.unwrap(); // can only fail due to I/O
14339 let offset_range =
14340 end_offset - query_match.end()..end_offset - query_match.start();
14341
14342 if !select_prev_state.wordwise
14343 || (!buffer.is_inside_word(offset_range.start, false)
14344 && !buffer.is_inside_word(offset_range.end, false))
14345 {
14346 next_selected_range = Some(offset_range);
14347 break;
14348 }
14349 }
14350
14351 if let Some(next_selected_range) = next_selected_range {
14352 self.select_match_ranges(
14353 next_selected_range,
14354 last_selection.reversed,
14355 action.replace_newest,
14356 Some(Autoscroll::newest()),
14357 window,
14358 cx,
14359 );
14360 } else {
14361 select_prev_state.done = true;
14362 }
14363 }
14364
14365 self.select_prev_state = Some(select_prev_state);
14366 } else {
14367 let mut only_carets = true;
14368 let mut same_text_selected = true;
14369 let mut selected_text = None;
14370
14371 let mut selections_iter = selections.iter().peekable();
14372 while let Some(selection) = selections_iter.next() {
14373 if selection.start != selection.end {
14374 only_carets = false;
14375 }
14376
14377 if same_text_selected {
14378 if selected_text.is_none() {
14379 selected_text =
14380 Some(buffer.text_for_range(selection.range()).collect::<String>());
14381 }
14382
14383 if let Some(next_selection) = selections_iter.peek() {
14384 if next_selection.range().len() == selection.range().len() {
14385 let next_selected_text = buffer
14386 .text_for_range(next_selection.range())
14387 .collect::<String>();
14388 if Some(next_selected_text) != selected_text {
14389 same_text_selected = false;
14390 selected_text = None;
14391 }
14392 } else {
14393 same_text_selected = false;
14394 selected_text = None;
14395 }
14396 }
14397 }
14398 }
14399
14400 if only_carets {
14401 for selection in &mut selections {
14402 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14403 selection.start = word_range.start;
14404 selection.end = word_range.end;
14405 selection.goal = SelectionGoal::None;
14406 selection.reversed = false;
14407 self.select_match_ranges(
14408 selection.start..selection.end,
14409 selection.reversed,
14410 action.replace_newest,
14411 Some(Autoscroll::newest()),
14412 window,
14413 cx,
14414 );
14415 }
14416 if selections.len() == 1 {
14417 let selection = selections
14418 .last()
14419 .expect("ensured that there's only one selection");
14420 let query = buffer
14421 .text_for_range(selection.start..selection.end)
14422 .collect::<String>();
14423 let is_empty = query.is_empty();
14424 let select_state = SelectNextState {
14425 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14426 wordwise: true,
14427 done: is_empty,
14428 };
14429 self.select_prev_state = Some(select_state);
14430 } else {
14431 self.select_prev_state = None;
14432 }
14433 } else if let Some(selected_text) = selected_text {
14434 self.select_prev_state = Some(SelectNextState {
14435 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14436 wordwise: false,
14437 done: false,
14438 });
14439 self.select_previous(action, window, cx)?;
14440 }
14441 }
14442 Ok(())
14443 }
14444
14445 pub fn find_next_match(
14446 &mut self,
14447 _: &FindNextMatch,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) -> Result<()> {
14451 let selections = self.selections.disjoint_anchors();
14452 match selections.first() {
14453 Some(first) if selections.len() >= 2 => {
14454 self.change_selections(Default::default(), window, cx, |s| {
14455 s.select_ranges([first.range()]);
14456 });
14457 }
14458 _ => self.select_next(
14459 &SelectNext {
14460 replace_newest: true,
14461 },
14462 window,
14463 cx,
14464 )?,
14465 }
14466 Ok(())
14467 }
14468
14469 pub fn find_previous_match(
14470 &mut self,
14471 _: &FindPreviousMatch,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) -> Result<()> {
14475 let selections = self.selections.disjoint_anchors();
14476 match selections.last() {
14477 Some(last) if selections.len() >= 2 => {
14478 self.change_selections(Default::default(), window, cx, |s| {
14479 s.select_ranges([last.range()]);
14480 });
14481 }
14482 _ => self.select_previous(
14483 &SelectPrevious {
14484 replace_newest: true,
14485 },
14486 window,
14487 cx,
14488 )?,
14489 }
14490 Ok(())
14491 }
14492
14493 pub fn toggle_comments(
14494 &mut self,
14495 action: &ToggleComments,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) {
14499 if self.read_only(cx) {
14500 return;
14501 }
14502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14503 let text_layout_details = &self.text_layout_details(window);
14504 self.transact(window, cx, |this, window, cx| {
14505 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14506 let mut edits = Vec::new();
14507 let mut selection_edit_ranges = Vec::new();
14508 let mut last_toggled_row = None;
14509 let snapshot = this.buffer.read(cx).read(cx);
14510 let empty_str: Arc<str> = Arc::default();
14511 let mut suffixes_inserted = Vec::new();
14512 let ignore_indent = action.ignore_indent;
14513
14514 fn comment_prefix_range(
14515 snapshot: &MultiBufferSnapshot,
14516 row: MultiBufferRow,
14517 comment_prefix: &str,
14518 comment_prefix_whitespace: &str,
14519 ignore_indent: bool,
14520 ) -> Range<Point> {
14521 let indent_size = if ignore_indent {
14522 0
14523 } else {
14524 snapshot.indent_size_for_line(row).len
14525 };
14526
14527 let start = Point::new(row.0, indent_size);
14528
14529 let mut line_bytes = snapshot
14530 .bytes_in_range(start..snapshot.max_point())
14531 .flatten()
14532 .copied();
14533
14534 // If this line currently begins with the line comment prefix, then record
14535 // the range containing the prefix.
14536 if line_bytes
14537 .by_ref()
14538 .take(comment_prefix.len())
14539 .eq(comment_prefix.bytes())
14540 {
14541 // Include any whitespace that matches the comment prefix.
14542 let matching_whitespace_len = line_bytes
14543 .zip(comment_prefix_whitespace.bytes())
14544 .take_while(|(a, b)| a == b)
14545 .count() as u32;
14546 let end = Point::new(
14547 start.row,
14548 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14549 );
14550 start..end
14551 } else {
14552 start..start
14553 }
14554 }
14555
14556 fn comment_suffix_range(
14557 snapshot: &MultiBufferSnapshot,
14558 row: MultiBufferRow,
14559 comment_suffix: &str,
14560 comment_suffix_has_leading_space: bool,
14561 ) -> Range<Point> {
14562 let end = Point::new(row.0, snapshot.line_len(row));
14563 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14564
14565 let mut line_end_bytes = snapshot
14566 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14567 .flatten()
14568 .copied();
14569
14570 let leading_space_len = if suffix_start_column > 0
14571 && line_end_bytes.next() == Some(b' ')
14572 && comment_suffix_has_leading_space
14573 {
14574 1
14575 } else {
14576 0
14577 };
14578
14579 // If this line currently begins with the line comment prefix, then record
14580 // the range containing the prefix.
14581 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14582 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14583 start..end
14584 } else {
14585 end..end
14586 }
14587 }
14588
14589 // TODO: Handle selections that cross excerpts
14590 for selection in &mut selections {
14591 let start_column = snapshot
14592 .indent_size_for_line(MultiBufferRow(selection.start.row))
14593 .len;
14594 let language = if let Some(language) =
14595 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14596 {
14597 language
14598 } else {
14599 continue;
14600 };
14601
14602 selection_edit_ranges.clear();
14603
14604 // If multiple selections contain a given row, avoid processing that
14605 // row more than once.
14606 let mut start_row = MultiBufferRow(selection.start.row);
14607 if last_toggled_row == Some(start_row) {
14608 start_row = start_row.next_row();
14609 }
14610 let end_row =
14611 if selection.end.row > selection.start.row && selection.end.column == 0 {
14612 MultiBufferRow(selection.end.row - 1)
14613 } else {
14614 MultiBufferRow(selection.end.row)
14615 };
14616 last_toggled_row = Some(end_row);
14617
14618 if start_row > end_row {
14619 continue;
14620 }
14621
14622 // If the language has line comments, toggle those.
14623 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14624
14625 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14626 if ignore_indent {
14627 full_comment_prefixes = full_comment_prefixes
14628 .into_iter()
14629 .map(|s| Arc::from(s.trim_end()))
14630 .collect();
14631 }
14632
14633 if !full_comment_prefixes.is_empty() {
14634 let first_prefix = full_comment_prefixes
14635 .first()
14636 .expect("prefixes is non-empty");
14637 let prefix_trimmed_lengths = full_comment_prefixes
14638 .iter()
14639 .map(|p| p.trim_end_matches(' ').len())
14640 .collect::<SmallVec<[usize; 4]>>();
14641
14642 let mut all_selection_lines_are_comments = true;
14643
14644 for row in start_row.0..=end_row.0 {
14645 let row = MultiBufferRow(row);
14646 if start_row < end_row && snapshot.is_line_blank(row) {
14647 continue;
14648 }
14649
14650 let prefix_range = full_comment_prefixes
14651 .iter()
14652 .zip(prefix_trimmed_lengths.iter().copied())
14653 .map(|(prefix, trimmed_prefix_len)| {
14654 comment_prefix_range(
14655 snapshot.deref(),
14656 row,
14657 &prefix[..trimmed_prefix_len],
14658 &prefix[trimmed_prefix_len..],
14659 ignore_indent,
14660 )
14661 })
14662 .max_by_key(|range| range.end.column - range.start.column)
14663 .expect("prefixes is non-empty");
14664
14665 if prefix_range.is_empty() {
14666 all_selection_lines_are_comments = false;
14667 }
14668
14669 selection_edit_ranges.push(prefix_range);
14670 }
14671
14672 if all_selection_lines_are_comments {
14673 edits.extend(
14674 selection_edit_ranges
14675 .iter()
14676 .cloned()
14677 .map(|range| (range, empty_str.clone())),
14678 );
14679 } else {
14680 let min_column = selection_edit_ranges
14681 .iter()
14682 .map(|range| range.start.column)
14683 .min()
14684 .unwrap_or(0);
14685 edits.extend(selection_edit_ranges.iter().map(|range| {
14686 let position = Point::new(range.start.row, min_column);
14687 (position..position, first_prefix.clone())
14688 }));
14689 }
14690 } else if let Some(BlockCommentConfig {
14691 start: full_comment_prefix,
14692 end: comment_suffix,
14693 ..
14694 }) = language.block_comment()
14695 {
14696 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14697 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14698 let prefix_range = comment_prefix_range(
14699 snapshot.deref(),
14700 start_row,
14701 comment_prefix,
14702 comment_prefix_whitespace,
14703 ignore_indent,
14704 );
14705 let suffix_range = comment_suffix_range(
14706 snapshot.deref(),
14707 end_row,
14708 comment_suffix.trim_start_matches(' '),
14709 comment_suffix.starts_with(' '),
14710 );
14711
14712 if prefix_range.is_empty() || suffix_range.is_empty() {
14713 edits.push((
14714 prefix_range.start..prefix_range.start,
14715 full_comment_prefix.clone(),
14716 ));
14717 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14718 suffixes_inserted.push((end_row, comment_suffix.len()));
14719 } else {
14720 edits.push((prefix_range, empty_str.clone()));
14721 edits.push((suffix_range, empty_str.clone()));
14722 }
14723 } else {
14724 continue;
14725 }
14726 }
14727
14728 drop(snapshot);
14729 this.buffer.update(cx, |buffer, cx| {
14730 buffer.edit(edits, None, cx);
14731 });
14732
14733 // Adjust selections so that they end before any comment suffixes that
14734 // were inserted.
14735 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14736 let mut selections = this.selections.all::<Point>(cx);
14737 let snapshot = this.buffer.read(cx).read(cx);
14738 for selection in &mut selections {
14739 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14740 match row.cmp(&MultiBufferRow(selection.end.row)) {
14741 Ordering::Less => {
14742 suffixes_inserted.next();
14743 continue;
14744 }
14745 Ordering::Greater => break,
14746 Ordering::Equal => {
14747 if selection.end.column == snapshot.line_len(row) {
14748 if selection.is_empty() {
14749 selection.start.column -= suffix_len as u32;
14750 }
14751 selection.end.column -= suffix_len as u32;
14752 }
14753 break;
14754 }
14755 }
14756 }
14757 }
14758
14759 drop(snapshot);
14760 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14761
14762 let selections = this.selections.all::<Point>(cx);
14763 let selections_on_single_row = selections.windows(2).all(|selections| {
14764 selections[0].start.row == selections[1].start.row
14765 && selections[0].end.row == selections[1].end.row
14766 && selections[0].start.row == selections[0].end.row
14767 });
14768 let selections_selecting = selections
14769 .iter()
14770 .any(|selection| selection.start != selection.end);
14771 let advance_downwards = action.advance_downwards
14772 && selections_on_single_row
14773 && !selections_selecting
14774 && !matches!(this.mode, EditorMode::SingleLine);
14775
14776 if advance_downwards {
14777 let snapshot = this.buffer.read(cx).snapshot(cx);
14778
14779 this.change_selections(Default::default(), window, cx, |s| {
14780 s.move_cursors_with(|display_snapshot, display_point, _| {
14781 let mut point = display_point.to_point(display_snapshot);
14782 point.row += 1;
14783 point = snapshot.clip_point(point, Bias::Left);
14784 let display_point = point.to_display_point(display_snapshot);
14785 let goal = SelectionGoal::HorizontalPosition(
14786 display_snapshot
14787 .x_for_display_point(display_point, text_layout_details)
14788 .into(),
14789 );
14790 (display_point, goal)
14791 })
14792 });
14793 }
14794 });
14795 }
14796
14797 pub fn select_enclosing_symbol(
14798 &mut self,
14799 _: &SelectEnclosingSymbol,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) {
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804
14805 let buffer = self.buffer.read(cx).snapshot(cx);
14806 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14807
14808 fn update_selection(
14809 selection: &Selection<usize>,
14810 buffer_snap: &MultiBufferSnapshot,
14811 ) -> Option<Selection<usize>> {
14812 let cursor = selection.head();
14813 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14814 for symbol in symbols.iter().rev() {
14815 let start = symbol.range.start.to_offset(buffer_snap);
14816 let end = symbol.range.end.to_offset(buffer_snap);
14817 let new_range = start..end;
14818 if start < selection.start || end > selection.end {
14819 return Some(Selection {
14820 id: selection.id,
14821 start: new_range.start,
14822 end: new_range.end,
14823 goal: SelectionGoal::None,
14824 reversed: selection.reversed,
14825 });
14826 }
14827 }
14828 None
14829 }
14830
14831 let mut selected_larger_symbol = false;
14832 let new_selections = old_selections
14833 .iter()
14834 .map(|selection| match update_selection(selection, &buffer) {
14835 Some(new_selection) => {
14836 if new_selection.range() != selection.range() {
14837 selected_larger_symbol = true;
14838 }
14839 new_selection
14840 }
14841 None => selection.clone(),
14842 })
14843 .collect::<Vec<_>>();
14844
14845 if selected_larger_symbol {
14846 self.change_selections(Default::default(), window, cx, |s| {
14847 s.select(new_selections);
14848 });
14849 }
14850 }
14851
14852 pub fn select_larger_syntax_node(
14853 &mut self,
14854 _: &SelectLargerSyntaxNode,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) {
14858 let Some(visible_row_count) = self.visible_row_count() else {
14859 return;
14860 };
14861 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14862 if old_selections.is_empty() {
14863 return;
14864 }
14865
14866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14867
14868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14869 let buffer = self.buffer.read(cx).snapshot(cx);
14870
14871 let mut selected_larger_node = false;
14872 let mut new_selections = old_selections
14873 .iter()
14874 .map(|selection| {
14875 let old_range = selection.start..selection.end;
14876
14877 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14878 // manually select word at selection
14879 if ["string_content", "inline"].contains(&node.kind()) {
14880 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14881 // ignore if word is already selected
14882 if !word_range.is_empty() && old_range != word_range {
14883 let (last_word_range, _) =
14884 buffer.surrounding_word(old_range.end, false);
14885 // only select word if start and end point belongs to same word
14886 if word_range == last_word_range {
14887 selected_larger_node = true;
14888 return Selection {
14889 id: selection.id,
14890 start: word_range.start,
14891 end: word_range.end,
14892 goal: SelectionGoal::None,
14893 reversed: selection.reversed,
14894 };
14895 }
14896 }
14897 }
14898 }
14899
14900 let mut new_range = old_range.clone();
14901 while let Some((_node, containing_range)) =
14902 buffer.syntax_ancestor(new_range.clone())
14903 {
14904 new_range = match containing_range {
14905 MultiOrSingleBufferOffsetRange::Single(_) => break,
14906 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14907 };
14908 if !display_map.intersects_fold(new_range.start)
14909 && !display_map.intersects_fold(new_range.end)
14910 {
14911 break;
14912 }
14913 }
14914
14915 selected_larger_node |= new_range != old_range;
14916 Selection {
14917 id: selection.id,
14918 start: new_range.start,
14919 end: new_range.end,
14920 goal: SelectionGoal::None,
14921 reversed: selection.reversed,
14922 }
14923 })
14924 .collect::<Vec<_>>();
14925
14926 if !selected_larger_node {
14927 return; // don't put this call in the history
14928 }
14929
14930 // scroll based on transformation done to the last selection created by the user
14931 let (last_old, last_new) = old_selections
14932 .last()
14933 .zip(new_selections.last().cloned())
14934 .expect("old_selections isn't empty");
14935
14936 // revert selection
14937 let is_selection_reversed = {
14938 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14939 new_selections.last_mut().expect("checked above").reversed =
14940 should_newest_selection_be_reversed;
14941 should_newest_selection_be_reversed
14942 };
14943
14944 if selected_larger_node {
14945 self.select_syntax_node_history.disable_clearing = true;
14946 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14947 s.select(new_selections.clone());
14948 });
14949 self.select_syntax_node_history.disable_clearing = false;
14950 }
14951
14952 let start_row = last_new.start.to_display_point(&display_map).row().0;
14953 let end_row = last_new.end.to_display_point(&display_map).row().0;
14954 let selection_height = end_row - start_row + 1;
14955 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14956
14957 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14958 let scroll_behavior = if fits_on_the_screen {
14959 self.request_autoscroll(Autoscroll::fit(), cx);
14960 SelectSyntaxNodeScrollBehavior::FitSelection
14961 } else if is_selection_reversed {
14962 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14963 SelectSyntaxNodeScrollBehavior::CursorTop
14964 } else {
14965 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14966 SelectSyntaxNodeScrollBehavior::CursorBottom
14967 };
14968
14969 self.select_syntax_node_history.push((
14970 old_selections,
14971 scroll_behavior,
14972 is_selection_reversed,
14973 ));
14974 }
14975
14976 pub fn select_smaller_syntax_node(
14977 &mut self,
14978 _: &SelectSmallerSyntaxNode,
14979 window: &mut Window,
14980 cx: &mut Context<Self>,
14981 ) {
14982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14983
14984 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14985 self.select_syntax_node_history.pop()
14986 {
14987 if let Some(selection) = selections.last_mut() {
14988 selection.reversed = is_selection_reversed;
14989 }
14990
14991 self.select_syntax_node_history.disable_clearing = true;
14992 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14993 s.select(selections.to_vec());
14994 });
14995 self.select_syntax_node_history.disable_clearing = false;
14996
14997 match scroll_behavior {
14998 SelectSyntaxNodeScrollBehavior::CursorTop => {
14999 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15000 }
15001 SelectSyntaxNodeScrollBehavior::FitSelection => {
15002 self.request_autoscroll(Autoscroll::fit(), cx);
15003 }
15004 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15005 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15006 }
15007 }
15008 }
15009 }
15010
15011 pub fn unwrap_syntax_node(
15012 &mut self,
15013 _: &UnwrapSyntaxNode,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) {
15017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15018
15019 let buffer = self.buffer.read(cx).snapshot(cx);
15020 let selections = self
15021 .selections
15022 .all::<usize>(cx)
15023 .into_iter()
15024 // subtracting the offset requires sorting
15025 .sorted_by_key(|i| i.start);
15026
15027 let full_edits = selections
15028 .into_iter()
15029 .filter_map(|selection| {
15030 // Only requires two branches once if-let-chains stabilize (#53667)
15031 let child = if !selection.is_empty() {
15032 selection.range()
15033 } else if let Some((_, ancestor_range)) =
15034 buffer.syntax_ancestor(selection.start..selection.end)
15035 {
15036 match ancestor_range {
15037 MultiOrSingleBufferOffsetRange::Single(range) => range,
15038 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15039 }
15040 } else {
15041 selection.range()
15042 };
15043
15044 let mut parent = child.clone();
15045 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15046 parent = match ancestor_range {
15047 MultiOrSingleBufferOffsetRange::Single(range) => range,
15048 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15049 };
15050 if parent.start < child.start || parent.end > child.end {
15051 break;
15052 }
15053 }
15054
15055 if parent == child {
15056 return None;
15057 }
15058 let text = buffer.text_for_range(child).collect::<String>();
15059 Some((selection.id, parent, text))
15060 })
15061 .collect::<Vec<_>>();
15062
15063 self.transact(window, cx, |this, window, cx| {
15064 this.buffer.update(cx, |buffer, cx| {
15065 buffer.edit(
15066 full_edits
15067 .iter()
15068 .map(|(_, p, t)| (p.clone(), t.clone()))
15069 .collect::<Vec<_>>(),
15070 None,
15071 cx,
15072 );
15073 });
15074 this.change_selections(Default::default(), window, cx, |s| {
15075 let mut offset = 0;
15076 let mut selections = vec![];
15077 for (id, parent, text) in full_edits {
15078 let start = parent.start - offset;
15079 offset += parent.len() - text.len();
15080 selections.push(Selection {
15081 id,
15082 start,
15083 end: start + text.len(),
15084 reversed: false,
15085 goal: Default::default(),
15086 });
15087 }
15088 s.select(selections);
15089 });
15090 });
15091 }
15092
15093 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15094 if !EditorSettings::get_global(cx).gutter.runnables {
15095 self.clear_tasks();
15096 return Task::ready(());
15097 }
15098 let project = self.project().map(Entity::downgrade);
15099 let task_sources = self.lsp_task_sources(cx);
15100 let multi_buffer = self.buffer.downgrade();
15101 cx.spawn_in(window, async move |editor, cx| {
15102 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15103 let Some(project) = project.and_then(|p| p.upgrade()) else {
15104 return;
15105 };
15106 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15107 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15108 }) else {
15109 return;
15110 };
15111
15112 let hide_runnables = project
15113 .update(cx, |project, _| project.is_via_collab())
15114 .unwrap_or(true);
15115 if hide_runnables {
15116 return;
15117 }
15118 let new_rows =
15119 cx.background_spawn({
15120 let snapshot = display_snapshot.clone();
15121 async move {
15122 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15123 }
15124 })
15125 .await;
15126 let Ok(lsp_tasks) =
15127 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15128 else {
15129 return;
15130 };
15131 let lsp_tasks = lsp_tasks.await;
15132
15133 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15134 lsp_tasks
15135 .into_iter()
15136 .flat_map(|(kind, tasks)| {
15137 tasks.into_iter().filter_map(move |(location, task)| {
15138 Some((kind.clone(), location?, task))
15139 })
15140 })
15141 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15142 let buffer = location.target.buffer;
15143 let buffer_snapshot = buffer.read(cx).snapshot();
15144 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15145 |(excerpt_id, snapshot, _)| {
15146 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15147 display_snapshot
15148 .buffer_snapshot
15149 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15150 } else {
15151 None
15152 }
15153 },
15154 );
15155 if let Some(offset) = offset {
15156 let task_buffer_range =
15157 location.target.range.to_point(&buffer_snapshot);
15158 let context_buffer_range =
15159 task_buffer_range.to_offset(&buffer_snapshot);
15160 let context_range = BufferOffset(context_buffer_range.start)
15161 ..BufferOffset(context_buffer_range.end);
15162
15163 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15164 .or_insert_with(|| RunnableTasks {
15165 templates: Vec::new(),
15166 offset,
15167 column: task_buffer_range.start.column,
15168 extra_variables: HashMap::default(),
15169 context_range,
15170 })
15171 .templates
15172 .push((kind, task.original_task().clone()));
15173 }
15174
15175 acc
15176 })
15177 }) else {
15178 return;
15179 };
15180
15181 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15182 buffer.language_settings(cx).tasks.prefer_lsp
15183 }) else {
15184 return;
15185 };
15186
15187 let rows = Self::runnable_rows(
15188 project,
15189 display_snapshot,
15190 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15191 new_rows,
15192 cx.clone(),
15193 )
15194 .await;
15195 editor
15196 .update(cx, |editor, _| {
15197 editor.clear_tasks();
15198 for (key, mut value) in rows {
15199 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15200 value.templates.extend(lsp_tasks.templates);
15201 }
15202
15203 editor.insert_tasks(key, value);
15204 }
15205 for (key, value) in lsp_tasks_by_rows {
15206 editor.insert_tasks(key, value);
15207 }
15208 })
15209 .ok();
15210 })
15211 }
15212 fn fetch_runnable_ranges(
15213 snapshot: &DisplaySnapshot,
15214 range: Range<Anchor>,
15215 ) -> Vec<language::RunnableRange> {
15216 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15217 }
15218
15219 fn runnable_rows(
15220 project: Entity<Project>,
15221 snapshot: DisplaySnapshot,
15222 prefer_lsp: bool,
15223 runnable_ranges: Vec<RunnableRange>,
15224 cx: AsyncWindowContext,
15225 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15226 cx.spawn(async move |cx| {
15227 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15228 for mut runnable in runnable_ranges {
15229 let Some(tasks) = cx
15230 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15231 .ok()
15232 else {
15233 continue;
15234 };
15235 let mut tasks = tasks.await;
15236
15237 if prefer_lsp {
15238 tasks.retain(|(task_kind, _)| {
15239 !matches!(task_kind, TaskSourceKind::Language { .. })
15240 });
15241 }
15242 if tasks.is_empty() {
15243 continue;
15244 }
15245
15246 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15247 let Some(row) = snapshot
15248 .buffer_snapshot
15249 .buffer_line_for_row(MultiBufferRow(point.row))
15250 .map(|(_, range)| range.start.row)
15251 else {
15252 continue;
15253 };
15254
15255 let context_range =
15256 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15257 runnable_rows.push((
15258 (runnable.buffer_id, row),
15259 RunnableTasks {
15260 templates: tasks,
15261 offset: snapshot
15262 .buffer_snapshot
15263 .anchor_before(runnable.run_range.start),
15264 context_range,
15265 column: point.column,
15266 extra_variables: runnable.extra_captures,
15267 },
15268 ));
15269 }
15270 runnable_rows
15271 })
15272 }
15273
15274 fn templates_with_tags(
15275 project: &Entity<Project>,
15276 runnable: &mut Runnable,
15277 cx: &mut App,
15278 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15279 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15280 let (worktree_id, file) = project
15281 .buffer_for_id(runnable.buffer, cx)
15282 .and_then(|buffer| buffer.read(cx).file())
15283 .map(|file| (file.worktree_id(cx), file.clone()))
15284 .unzip();
15285
15286 (
15287 project.task_store().read(cx).task_inventory().cloned(),
15288 worktree_id,
15289 file,
15290 )
15291 });
15292
15293 let tags = mem::take(&mut runnable.tags);
15294 let language = runnable.language.clone();
15295 cx.spawn(async move |cx| {
15296 let mut templates_with_tags = Vec::new();
15297 if let Some(inventory) = inventory {
15298 for RunnableTag(tag) in tags {
15299 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15300 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15301 }) else {
15302 return templates_with_tags;
15303 };
15304 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15305 move |(_, template)| {
15306 template.tags.iter().any(|source_tag| source_tag == &tag)
15307 },
15308 ));
15309 }
15310 }
15311 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15312
15313 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15314 // Strongest source wins; if we have worktree tag binding, prefer that to
15315 // global and language bindings;
15316 // if we have a global binding, prefer that to language binding.
15317 let first_mismatch = templates_with_tags
15318 .iter()
15319 .position(|(tag_source, _)| tag_source != leading_tag_source);
15320 if let Some(index) = first_mismatch {
15321 templates_with_tags.truncate(index);
15322 }
15323 }
15324
15325 templates_with_tags
15326 })
15327 }
15328
15329 pub fn move_to_enclosing_bracket(
15330 &mut self,
15331 _: &MoveToEnclosingBracket,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15336 self.change_selections(Default::default(), window, cx, |s| {
15337 s.move_offsets_with(|snapshot, selection| {
15338 let Some(enclosing_bracket_ranges) =
15339 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15340 else {
15341 return;
15342 };
15343
15344 let mut best_length = usize::MAX;
15345 let mut best_inside = false;
15346 let mut best_in_bracket_range = false;
15347 let mut best_destination = None;
15348 for (open, close) in enclosing_bracket_ranges {
15349 let close = close.to_inclusive();
15350 let length = close.end() - open.start;
15351 let inside = selection.start >= open.end && selection.end <= *close.start();
15352 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15353 || close.contains(&selection.head());
15354
15355 // If best is next to a bracket and current isn't, skip
15356 if !in_bracket_range && best_in_bracket_range {
15357 continue;
15358 }
15359
15360 // Prefer smaller lengths unless best is inside and current isn't
15361 if length > best_length && (best_inside || !inside) {
15362 continue;
15363 }
15364
15365 best_length = length;
15366 best_inside = inside;
15367 best_in_bracket_range = in_bracket_range;
15368 best_destination = Some(
15369 if close.contains(&selection.start) && close.contains(&selection.end) {
15370 if inside { open.end } else { open.start }
15371 } else if inside {
15372 *close.start()
15373 } else {
15374 *close.end()
15375 },
15376 );
15377 }
15378
15379 if let Some(destination) = best_destination {
15380 selection.collapse_to(destination, SelectionGoal::None);
15381 }
15382 })
15383 });
15384 }
15385
15386 pub fn undo_selection(
15387 &mut self,
15388 _: &UndoSelection,
15389 window: &mut Window,
15390 cx: &mut Context<Self>,
15391 ) {
15392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15393 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15394 self.selection_history.mode = SelectionHistoryMode::Undoing;
15395 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15396 this.end_selection(window, cx);
15397 this.change_selections(
15398 SelectionEffects::scroll(Autoscroll::newest()),
15399 window,
15400 cx,
15401 |s| s.select_anchors(entry.selections.to_vec()),
15402 );
15403 });
15404 self.selection_history.mode = SelectionHistoryMode::Normal;
15405
15406 self.select_next_state = entry.select_next_state;
15407 self.select_prev_state = entry.select_prev_state;
15408 self.add_selections_state = entry.add_selections_state;
15409 }
15410 }
15411
15412 pub fn redo_selection(
15413 &mut self,
15414 _: &RedoSelection,
15415 window: &mut Window,
15416 cx: &mut Context<Self>,
15417 ) {
15418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15419 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15420 self.selection_history.mode = SelectionHistoryMode::Redoing;
15421 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15422 this.end_selection(window, cx);
15423 this.change_selections(
15424 SelectionEffects::scroll(Autoscroll::newest()),
15425 window,
15426 cx,
15427 |s| s.select_anchors(entry.selections.to_vec()),
15428 );
15429 });
15430 self.selection_history.mode = SelectionHistoryMode::Normal;
15431
15432 self.select_next_state = entry.select_next_state;
15433 self.select_prev_state = entry.select_prev_state;
15434 self.add_selections_state = entry.add_selections_state;
15435 }
15436 }
15437
15438 pub fn expand_excerpts(
15439 &mut self,
15440 action: &ExpandExcerpts,
15441 _: &mut Window,
15442 cx: &mut Context<Self>,
15443 ) {
15444 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15445 }
15446
15447 pub fn expand_excerpts_down(
15448 &mut self,
15449 action: &ExpandExcerptsDown,
15450 _: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) {
15453 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15454 }
15455
15456 pub fn expand_excerpts_up(
15457 &mut self,
15458 action: &ExpandExcerptsUp,
15459 _: &mut Window,
15460 cx: &mut Context<Self>,
15461 ) {
15462 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15463 }
15464
15465 pub fn expand_excerpts_for_direction(
15466 &mut self,
15467 lines: u32,
15468 direction: ExpandExcerptDirection,
15469
15470 cx: &mut Context<Self>,
15471 ) {
15472 let selections = self.selections.disjoint_anchors();
15473
15474 let lines = if lines == 0 {
15475 EditorSettings::get_global(cx).expand_excerpt_lines
15476 } else {
15477 lines
15478 };
15479
15480 self.buffer.update(cx, |buffer, cx| {
15481 let snapshot = buffer.snapshot(cx);
15482 let mut excerpt_ids = selections
15483 .iter()
15484 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15485 .collect::<Vec<_>>();
15486 excerpt_ids.sort();
15487 excerpt_ids.dedup();
15488 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15489 })
15490 }
15491
15492 pub fn expand_excerpt(
15493 &mut self,
15494 excerpt: ExcerptId,
15495 direction: ExpandExcerptDirection,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 ) {
15499 let current_scroll_position = self.scroll_position(cx);
15500 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15501 let mut should_scroll_up = false;
15502
15503 if direction == ExpandExcerptDirection::Down {
15504 let multi_buffer = self.buffer.read(cx);
15505 let snapshot = multi_buffer.snapshot(cx);
15506 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15507 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15508 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15509 {
15510 let buffer_snapshot = buffer.read(cx).snapshot();
15511 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15512 let last_row = buffer_snapshot.max_point().row;
15513 let lines_below = last_row.saturating_sub(excerpt_end_row);
15514 should_scroll_up = lines_below >= lines_to_expand;
15515 }
15516 }
15517
15518 self.buffer.update(cx, |buffer, cx| {
15519 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15520 });
15521
15522 if should_scroll_up {
15523 let new_scroll_position =
15524 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15525 self.set_scroll_position(new_scroll_position, window, cx);
15526 }
15527 }
15528
15529 pub fn go_to_singleton_buffer_point(
15530 &mut self,
15531 point: Point,
15532 window: &mut Window,
15533 cx: &mut Context<Self>,
15534 ) {
15535 self.go_to_singleton_buffer_range(point..point, window, cx);
15536 }
15537
15538 pub fn go_to_singleton_buffer_range(
15539 &mut self,
15540 range: Range<Point>,
15541 window: &mut Window,
15542 cx: &mut Context<Self>,
15543 ) {
15544 let multibuffer = self.buffer().read(cx);
15545 let Some(buffer) = multibuffer.as_singleton() else {
15546 return;
15547 };
15548 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15549 return;
15550 };
15551 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15552 return;
15553 };
15554 self.change_selections(
15555 SelectionEffects::default().nav_history(true),
15556 window,
15557 cx,
15558 |s| s.select_anchor_ranges([start..end]),
15559 );
15560 }
15561
15562 pub fn go_to_diagnostic(
15563 &mut self,
15564 action: &GoToDiagnostic,
15565 window: &mut Window,
15566 cx: &mut Context<Self>,
15567 ) {
15568 if !self.diagnostics_enabled() {
15569 return;
15570 }
15571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15572 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15573 }
15574
15575 pub fn go_to_prev_diagnostic(
15576 &mut self,
15577 action: &GoToPreviousDiagnostic,
15578 window: &mut Window,
15579 cx: &mut Context<Self>,
15580 ) {
15581 if !self.diagnostics_enabled() {
15582 return;
15583 }
15584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15585 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15586 }
15587
15588 pub fn go_to_diagnostic_impl(
15589 &mut self,
15590 direction: Direction,
15591 severity: GoToDiagnosticSeverityFilter,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 let buffer = self.buffer.read(cx).snapshot(cx);
15596 let selection = self.selections.newest::<usize>(cx);
15597
15598 let mut active_group_id = None;
15599 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15600 && active_group.active_range.start.to_offset(&buffer) == selection.start
15601 {
15602 active_group_id = Some(active_group.group_id);
15603 }
15604
15605 fn filtered(
15606 snapshot: EditorSnapshot,
15607 severity: GoToDiagnosticSeverityFilter,
15608 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15609 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15610 diagnostics
15611 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15612 .filter(|entry| entry.range.start != entry.range.end)
15613 .filter(|entry| !entry.diagnostic.is_unnecessary)
15614 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15615 }
15616
15617 let snapshot = self.snapshot(window, cx);
15618 let before = filtered(
15619 snapshot.clone(),
15620 severity,
15621 buffer
15622 .diagnostics_in_range(0..selection.start)
15623 .filter(|entry| entry.range.start <= selection.start),
15624 );
15625 let after = filtered(
15626 snapshot,
15627 severity,
15628 buffer
15629 .diagnostics_in_range(selection.start..buffer.len())
15630 .filter(|entry| entry.range.start >= selection.start),
15631 );
15632
15633 let mut found: Option<DiagnosticEntry<usize>> = None;
15634 if direction == Direction::Prev {
15635 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15636 {
15637 for diagnostic in prev_diagnostics.into_iter().rev() {
15638 if diagnostic.range.start != selection.start
15639 || active_group_id
15640 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15641 {
15642 found = Some(diagnostic);
15643 break 'outer;
15644 }
15645 }
15646 }
15647 } else {
15648 for diagnostic in after.chain(before) {
15649 if diagnostic.range.start != selection.start
15650 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15651 {
15652 found = Some(diagnostic);
15653 break;
15654 }
15655 }
15656 }
15657 let Some(next_diagnostic) = found else {
15658 return;
15659 };
15660
15661 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15662 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15663 return;
15664 };
15665 self.change_selections(Default::default(), window, cx, |s| {
15666 s.select_ranges(vec![
15667 next_diagnostic.range.start..next_diagnostic.range.start,
15668 ])
15669 });
15670 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15671 self.refresh_edit_prediction(false, true, window, cx);
15672 }
15673
15674 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15676 let snapshot = self.snapshot(window, cx);
15677 let selection = self.selections.newest::<Point>(cx);
15678 self.go_to_hunk_before_or_after_position(
15679 &snapshot,
15680 selection.head(),
15681 Direction::Next,
15682 window,
15683 cx,
15684 );
15685 }
15686
15687 pub fn go_to_hunk_before_or_after_position(
15688 &mut self,
15689 snapshot: &EditorSnapshot,
15690 position: Point,
15691 direction: Direction,
15692 window: &mut Window,
15693 cx: &mut Context<Editor>,
15694 ) {
15695 let row = if direction == Direction::Next {
15696 self.hunk_after_position(snapshot, position)
15697 .map(|hunk| hunk.row_range.start)
15698 } else {
15699 self.hunk_before_position(snapshot, position)
15700 };
15701
15702 if let Some(row) = row {
15703 let destination = Point::new(row.0, 0);
15704 let autoscroll = Autoscroll::center();
15705
15706 self.unfold_ranges(&[destination..destination], false, false, cx);
15707 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15708 s.select_ranges([destination..destination]);
15709 });
15710 }
15711 }
15712
15713 fn hunk_after_position(
15714 &mut self,
15715 snapshot: &EditorSnapshot,
15716 position: Point,
15717 ) -> Option<MultiBufferDiffHunk> {
15718 snapshot
15719 .buffer_snapshot
15720 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15721 .find(|hunk| hunk.row_range.start.0 > position.row)
15722 .or_else(|| {
15723 snapshot
15724 .buffer_snapshot
15725 .diff_hunks_in_range(Point::zero()..position)
15726 .find(|hunk| hunk.row_range.end.0 < position.row)
15727 })
15728 }
15729
15730 fn go_to_prev_hunk(
15731 &mut self,
15732 _: &GoToPreviousHunk,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15737 let snapshot = self.snapshot(window, cx);
15738 let selection = self.selections.newest::<Point>(cx);
15739 self.go_to_hunk_before_or_after_position(
15740 &snapshot,
15741 selection.head(),
15742 Direction::Prev,
15743 window,
15744 cx,
15745 );
15746 }
15747
15748 fn hunk_before_position(
15749 &mut self,
15750 snapshot: &EditorSnapshot,
15751 position: Point,
15752 ) -> Option<MultiBufferRow> {
15753 snapshot
15754 .buffer_snapshot
15755 .diff_hunk_before(position)
15756 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15757 }
15758
15759 fn go_to_next_change(
15760 &mut self,
15761 _: &GoToNextChange,
15762 window: &mut Window,
15763 cx: &mut Context<Self>,
15764 ) {
15765 if let Some(selections) = self
15766 .change_list
15767 .next_change(1, Direction::Next)
15768 .map(|s| s.to_vec())
15769 {
15770 self.change_selections(Default::default(), window, cx, |s| {
15771 let map = s.display_map();
15772 s.select_display_ranges(selections.iter().map(|a| {
15773 let point = a.to_display_point(&map);
15774 point..point
15775 }))
15776 })
15777 }
15778 }
15779
15780 fn go_to_previous_change(
15781 &mut self,
15782 _: &GoToPreviousChange,
15783 window: &mut Window,
15784 cx: &mut Context<Self>,
15785 ) {
15786 if let Some(selections) = self
15787 .change_list
15788 .next_change(1, Direction::Prev)
15789 .map(|s| s.to_vec())
15790 {
15791 self.change_selections(Default::default(), window, cx, |s| {
15792 let map = s.display_map();
15793 s.select_display_ranges(selections.iter().map(|a| {
15794 let point = a.to_display_point(&map);
15795 point..point
15796 }))
15797 })
15798 }
15799 }
15800
15801 fn go_to_line<T: 'static>(
15802 &mut self,
15803 position: Anchor,
15804 highlight_color: Option<Hsla>,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 let snapshot = self.snapshot(window, cx).display_snapshot;
15809 let position = position.to_point(&snapshot.buffer_snapshot);
15810 let start = snapshot
15811 .buffer_snapshot
15812 .clip_point(Point::new(position.row, 0), Bias::Left);
15813 let end = start + Point::new(1, 0);
15814 let start = snapshot.buffer_snapshot.anchor_before(start);
15815 let end = snapshot.buffer_snapshot.anchor_before(end);
15816
15817 self.highlight_rows::<T>(
15818 start..end,
15819 highlight_color
15820 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15821 Default::default(),
15822 cx,
15823 );
15824
15825 if self.buffer.read(cx).is_singleton() {
15826 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15827 }
15828 }
15829
15830 pub fn go_to_definition(
15831 &mut self,
15832 _: &GoToDefinition,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) -> Task<Result<Navigated>> {
15836 let definition =
15837 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15838 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15839 cx.spawn_in(window, async move |editor, cx| {
15840 if definition.await? == Navigated::Yes {
15841 return Ok(Navigated::Yes);
15842 }
15843 match fallback_strategy {
15844 GoToDefinitionFallback::None => Ok(Navigated::No),
15845 GoToDefinitionFallback::FindAllReferences => {
15846 match editor.update_in(cx, |editor, window, cx| {
15847 editor.find_all_references(&FindAllReferences, window, cx)
15848 })? {
15849 Some(references) => references.await,
15850 None => Ok(Navigated::No),
15851 }
15852 }
15853 }
15854 })
15855 }
15856
15857 pub fn go_to_declaration(
15858 &mut self,
15859 _: &GoToDeclaration,
15860 window: &mut Window,
15861 cx: &mut Context<Self>,
15862 ) -> Task<Result<Navigated>> {
15863 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15864 }
15865
15866 pub fn go_to_declaration_split(
15867 &mut self,
15868 _: &GoToDeclaration,
15869 window: &mut Window,
15870 cx: &mut Context<Self>,
15871 ) -> Task<Result<Navigated>> {
15872 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15873 }
15874
15875 pub fn go_to_implementation(
15876 &mut self,
15877 _: &GoToImplementation,
15878 window: &mut Window,
15879 cx: &mut Context<Self>,
15880 ) -> Task<Result<Navigated>> {
15881 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15882 }
15883
15884 pub fn go_to_implementation_split(
15885 &mut self,
15886 _: &GoToImplementationSplit,
15887 window: &mut Window,
15888 cx: &mut Context<Self>,
15889 ) -> Task<Result<Navigated>> {
15890 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15891 }
15892
15893 pub fn go_to_type_definition(
15894 &mut self,
15895 _: &GoToTypeDefinition,
15896 window: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) -> Task<Result<Navigated>> {
15899 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15900 }
15901
15902 pub fn go_to_definition_split(
15903 &mut self,
15904 _: &GoToDefinitionSplit,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 ) -> Task<Result<Navigated>> {
15908 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15909 }
15910
15911 pub fn go_to_type_definition_split(
15912 &mut self,
15913 _: &GoToTypeDefinitionSplit,
15914 window: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) -> Task<Result<Navigated>> {
15917 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15918 }
15919
15920 fn go_to_definition_of_kind(
15921 &mut self,
15922 kind: GotoDefinitionKind,
15923 split: bool,
15924 window: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) -> Task<Result<Navigated>> {
15927 let Some(provider) = self.semantics_provider.clone() else {
15928 return Task::ready(Ok(Navigated::No));
15929 };
15930 let head = self.selections.newest::<usize>(cx).head();
15931 let buffer = self.buffer.read(cx);
15932 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15933 return Task::ready(Ok(Navigated::No));
15934 };
15935 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15936 return Task::ready(Ok(Navigated::No));
15937 };
15938
15939 cx.spawn_in(window, async move |editor, cx| {
15940 let Some(definitions) = definitions.await? else {
15941 return Ok(Navigated::No);
15942 };
15943 let navigated = editor
15944 .update_in(cx, |editor, window, cx| {
15945 editor.navigate_to_hover_links(
15946 Some(kind),
15947 definitions
15948 .into_iter()
15949 .filter(|location| {
15950 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15951 })
15952 .map(HoverLink::Text)
15953 .collect::<Vec<_>>(),
15954 split,
15955 window,
15956 cx,
15957 )
15958 })?
15959 .await?;
15960 anyhow::Ok(navigated)
15961 })
15962 }
15963
15964 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15965 let selection = self.selections.newest_anchor();
15966 let head = selection.head();
15967 let tail = selection.tail();
15968
15969 let Some((buffer, start_position)) =
15970 self.buffer.read(cx).text_anchor_for_position(head, cx)
15971 else {
15972 return;
15973 };
15974
15975 let end_position = if head != tail {
15976 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15977 return;
15978 };
15979 Some(pos)
15980 } else {
15981 None
15982 };
15983
15984 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15985 let url = if let Some(end_pos) = end_position {
15986 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15987 } else {
15988 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15989 };
15990
15991 if let Some(url) = url {
15992 editor.update(cx, |_, cx| {
15993 cx.open_url(&url);
15994 })
15995 } else {
15996 Ok(())
15997 }
15998 });
15999
16000 url_finder.detach();
16001 }
16002
16003 pub fn open_selected_filename(
16004 &mut self,
16005 _: &OpenSelectedFilename,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 let Some(workspace) = self.workspace() else {
16010 return;
16011 };
16012
16013 let position = self.selections.newest_anchor().head();
16014
16015 let Some((buffer, buffer_position)) =
16016 self.buffer.read(cx).text_anchor_for_position(position, cx)
16017 else {
16018 return;
16019 };
16020
16021 let project = self.project.clone();
16022
16023 cx.spawn_in(window, async move |_, cx| {
16024 let result = find_file(&buffer, project, buffer_position, cx).await;
16025
16026 if let Some((_, path)) = result {
16027 workspace
16028 .update_in(cx, |workspace, window, cx| {
16029 workspace.open_resolved_path(path, window, cx)
16030 })?
16031 .await?;
16032 }
16033 anyhow::Ok(())
16034 })
16035 .detach();
16036 }
16037
16038 pub(crate) fn navigate_to_hover_links(
16039 &mut self,
16040 kind: Option<GotoDefinitionKind>,
16041 definitions: Vec<HoverLink>,
16042 split: bool,
16043 window: &mut Window,
16044 cx: &mut Context<Editor>,
16045 ) -> Task<Result<Navigated>> {
16046 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16047 let mut first_url_or_file = None;
16048 let definitions: Vec<_> = definitions
16049 .into_iter()
16050 .filter_map(|def| match def {
16051 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16052 HoverLink::InlayHint(lsp_location, server_id) => {
16053 let computation =
16054 self.compute_target_location(lsp_location, server_id, window, cx);
16055 Some(cx.background_spawn(computation))
16056 }
16057 HoverLink::Url(url) => {
16058 first_url_or_file = Some(Either::Left(url));
16059 None
16060 }
16061 HoverLink::File(path) => {
16062 first_url_or_file = Some(Either::Right(path));
16063 None
16064 }
16065 })
16066 .collect();
16067
16068 let workspace = self.workspace();
16069
16070 cx.spawn_in(window, async move |editor, acx| {
16071 let mut locations: Vec<Location> = future::join_all(definitions)
16072 .await
16073 .into_iter()
16074 .filter_map(|location| location.transpose())
16075 .collect::<Result<_>>()
16076 .context("location tasks")?;
16077
16078 if locations.len() > 1 {
16079 let Some(workspace) = workspace else {
16080 return Ok(Navigated::No);
16081 };
16082
16083 let tab_kind = match kind {
16084 Some(GotoDefinitionKind::Implementation) => "Implementations",
16085 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16086 Some(GotoDefinitionKind::Declaration) => "Declarations",
16087 Some(GotoDefinitionKind::Type) => "Types",
16088 };
16089 let title = editor
16090 .update_in(acx, |_, _, cx| {
16091 let target = locations
16092 .iter()
16093 .map(|location| {
16094 location
16095 .buffer
16096 .read(cx)
16097 .text_for_range(location.range.clone())
16098 .collect::<String>()
16099 })
16100 .filter(|text| !text.contains('\n'))
16101 .unique()
16102 .take(3)
16103 .join(", ");
16104 if target.is_empty() {
16105 tab_kind.to_owned()
16106 } else {
16107 format!("{tab_kind} for {target}")
16108 }
16109 })
16110 .context("buffer title")?;
16111
16112 let opened = workspace
16113 .update_in(acx, |workspace, window, cx| {
16114 Self::open_locations_in_multibuffer(
16115 workspace,
16116 locations,
16117 title,
16118 split,
16119 MultibufferSelectionMode::First,
16120 window,
16121 cx,
16122 )
16123 })
16124 .is_ok();
16125
16126 anyhow::Ok(Navigated::from_bool(opened))
16127 } else if locations.is_empty() {
16128 // If there is one definition, just open it directly
16129 match first_url_or_file {
16130 Some(Either::Left(url)) => {
16131 acx.update(|_, cx| cx.open_url(&url))?;
16132 Ok(Navigated::Yes)
16133 }
16134 Some(Either::Right(path)) => {
16135 let Some(workspace) = workspace else {
16136 return Ok(Navigated::No);
16137 };
16138
16139 workspace
16140 .update_in(acx, |workspace, window, cx| {
16141 workspace.open_resolved_path(path, window, cx)
16142 })?
16143 .await?;
16144 Ok(Navigated::Yes)
16145 }
16146 None => Ok(Navigated::No),
16147 }
16148 } else {
16149 let Some(workspace) = workspace else {
16150 return Ok(Navigated::No);
16151 };
16152
16153 let target = locations.pop().unwrap();
16154 editor.update_in(acx, |editor, window, cx| {
16155 let pane = workspace.read(cx).active_pane().clone();
16156
16157 let range = target.range.to_point(target.buffer.read(cx));
16158 let range = editor.range_for_match(&range);
16159 let range = collapse_multiline_range(range);
16160
16161 if !split
16162 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16163 {
16164 editor.go_to_singleton_buffer_range(range, window, cx);
16165 } else {
16166 window.defer(cx, move |window, cx| {
16167 let target_editor: Entity<Self> =
16168 workspace.update(cx, |workspace, cx| {
16169 let pane = if split {
16170 workspace.adjacent_pane(window, cx)
16171 } else {
16172 workspace.active_pane().clone()
16173 };
16174
16175 workspace.open_project_item(
16176 pane,
16177 target.buffer.clone(),
16178 true,
16179 true,
16180 window,
16181 cx,
16182 )
16183 });
16184 target_editor.update(cx, |target_editor, cx| {
16185 // When selecting a definition in a different buffer, disable the nav history
16186 // to avoid creating a history entry at the previous cursor location.
16187 pane.update(cx, |pane, _| pane.disable_history());
16188 target_editor.go_to_singleton_buffer_range(range, window, cx);
16189 pane.update(cx, |pane, _| pane.enable_history());
16190 });
16191 });
16192 }
16193 Navigated::Yes
16194 })
16195 }
16196 })
16197 }
16198
16199 fn compute_target_location(
16200 &self,
16201 lsp_location: lsp::Location,
16202 server_id: LanguageServerId,
16203 window: &mut Window,
16204 cx: &mut Context<Self>,
16205 ) -> Task<anyhow::Result<Option<Location>>> {
16206 let Some(project) = self.project.clone() else {
16207 return Task::ready(Ok(None));
16208 };
16209
16210 cx.spawn_in(window, async move |editor, cx| {
16211 let location_task = editor.update(cx, |_, cx| {
16212 project.update(cx, |project, cx| {
16213 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16214 })
16215 })?;
16216 let location = Some({
16217 let target_buffer_handle = location_task.await.context("open local buffer")?;
16218 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16219 let target_start = target_buffer
16220 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16221 let target_end = target_buffer
16222 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16223 target_buffer.anchor_after(target_start)
16224 ..target_buffer.anchor_before(target_end)
16225 })?;
16226 Location {
16227 buffer: target_buffer_handle,
16228 range,
16229 }
16230 });
16231 Ok(location)
16232 })
16233 }
16234
16235 pub fn find_all_references(
16236 &mut self,
16237 _: &FindAllReferences,
16238 window: &mut Window,
16239 cx: &mut Context<Self>,
16240 ) -> Option<Task<Result<Navigated>>> {
16241 let selection = self.selections.newest::<usize>(cx);
16242 let multi_buffer = self.buffer.read(cx);
16243 let head = selection.head();
16244
16245 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16246 let head_anchor = multi_buffer_snapshot.anchor_at(
16247 head,
16248 if head < selection.tail() {
16249 Bias::Right
16250 } else {
16251 Bias::Left
16252 },
16253 );
16254
16255 match self
16256 .find_all_references_task_sources
16257 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16258 {
16259 Ok(_) => {
16260 log::info!(
16261 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16262 );
16263 return None;
16264 }
16265 Err(i) => {
16266 self.find_all_references_task_sources.insert(i, head_anchor);
16267 }
16268 }
16269
16270 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16271 let workspace = self.workspace()?;
16272 let project = workspace.read(cx).project().clone();
16273 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16274 Some(cx.spawn_in(window, async move |editor, cx| {
16275 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16276 if let Ok(i) = editor
16277 .find_all_references_task_sources
16278 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16279 {
16280 editor.find_all_references_task_sources.remove(i);
16281 }
16282 });
16283
16284 let Some(locations) = references.await? else {
16285 return anyhow::Ok(Navigated::No);
16286 };
16287 if locations.is_empty() {
16288 return anyhow::Ok(Navigated::No);
16289 }
16290
16291 workspace.update_in(cx, |workspace, window, cx| {
16292 let target = locations
16293 .iter()
16294 .map(|location| {
16295 location
16296 .buffer
16297 .read(cx)
16298 .text_for_range(location.range.clone())
16299 .collect::<String>()
16300 })
16301 .filter(|text| !text.contains('\n'))
16302 .unique()
16303 .take(3)
16304 .join(", ");
16305 let title = if target.is_empty() {
16306 "References".to_owned()
16307 } else {
16308 format!("References to {target}")
16309 };
16310 Self::open_locations_in_multibuffer(
16311 workspace,
16312 locations,
16313 title,
16314 false,
16315 MultibufferSelectionMode::First,
16316 window,
16317 cx,
16318 );
16319 Navigated::Yes
16320 })
16321 }))
16322 }
16323
16324 /// Opens a multibuffer with the given project locations in it
16325 pub fn open_locations_in_multibuffer(
16326 workspace: &mut Workspace,
16327 mut locations: Vec<Location>,
16328 title: String,
16329 split: bool,
16330 multibuffer_selection_mode: MultibufferSelectionMode,
16331 window: &mut Window,
16332 cx: &mut Context<Workspace>,
16333 ) {
16334 if locations.is_empty() {
16335 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16336 return;
16337 }
16338
16339 // If there are multiple definitions, open them in a multibuffer
16340 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16341 let mut locations = locations.into_iter().peekable();
16342 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16343 let capability = workspace.project().read(cx).capability();
16344
16345 let excerpt_buffer = cx.new(|cx| {
16346 let mut multibuffer = MultiBuffer::new(capability);
16347 while let Some(location) = locations.next() {
16348 let buffer = location.buffer.read(cx);
16349 let mut ranges_for_buffer = Vec::new();
16350 let range = location.range.to_point(buffer);
16351 ranges_for_buffer.push(range.clone());
16352
16353 while let Some(next_location) = locations.peek() {
16354 if next_location.buffer == location.buffer {
16355 ranges_for_buffer.push(next_location.range.to_point(buffer));
16356 locations.next();
16357 } else {
16358 break;
16359 }
16360 }
16361
16362 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16363 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16364 PathKey::for_buffer(&location.buffer, cx),
16365 location.buffer.clone(),
16366 ranges_for_buffer,
16367 multibuffer_context_lines(cx),
16368 cx,
16369 );
16370 ranges.extend(new_ranges)
16371 }
16372
16373 multibuffer.with_title(title)
16374 });
16375
16376 let editor = cx.new(|cx| {
16377 Editor::for_multibuffer(
16378 excerpt_buffer,
16379 Some(workspace.project().clone()),
16380 window,
16381 cx,
16382 )
16383 });
16384 editor.update(cx, |editor, cx| {
16385 match multibuffer_selection_mode {
16386 MultibufferSelectionMode::First => {
16387 if let Some(first_range) = ranges.first() {
16388 editor.change_selections(
16389 SelectionEffects::no_scroll(),
16390 window,
16391 cx,
16392 |selections| {
16393 selections.clear_disjoint();
16394 selections
16395 .select_anchor_ranges(std::iter::once(first_range.clone()));
16396 },
16397 );
16398 }
16399 editor.highlight_background::<Self>(
16400 &ranges,
16401 |theme| theme.colors().editor_highlighted_line_background,
16402 cx,
16403 );
16404 }
16405 MultibufferSelectionMode::All => {
16406 editor.change_selections(
16407 SelectionEffects::no_scroll(),
16408 window,
16409 cx,
16410 |selections| {
16411 selections.clear_disjoint();
16412 selections.select_anchor_ranges(ranges);
16413 },
16414 );
16415 }
16416 }
16417 editor.register_buffers_with_language_servers(cx);
16418 });
16419
16420 let item = Box::new(editor);
16421 let item_id = item.item_id();
16422
16423 if split {
16424 workspace.split_item(SplitDirection::Right, item, window, cx);
16425 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16426 let (preview_item_id, preview_item_idx) =
16427 workspace.active_pane().read_with(cx, |pane, _| {
16428 (pane.preview_item_id(), pane.preview_item_idx())
16429 });
16430
16431 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16432
16433 if let Some(preview_item_id) = preview_item_id {
16434 workspace.active_pane().update(cx, |pane, cx| {
16435 pane.remove_item(preview_item_id, false, false, window, cx);
16436 });
16437 }
16438 } else {
16439 workspace.add_item_to_active_pane(item, None, true, window, cx);
16440 }
16441 workspace.active_pane().update(cx, |pane, cx| {
16442 pane.set_preview_item_id(Some(item_id), cx);
16443 });
16444 }
16445
16446 pub fn rename(
16447 &mut self,
16448 _: &Rename,
16449 window: &mut Window,
16450 cx: &mut Context<Self>,
16451 ) -> Option<Task<Result<()>>> {
16452 use language::ToOffset as _;
16453
16454 let provider = self.semantics_provider.clone()?;
16455 let selection = self.selections.newest_anchor().clone();
16456 let (cursor_buffer, cursor_buffer_position) = self
16457 .buffer
16458 .read(cx)
16459 .text_anchor_for_position(selection.head(), cx)?;
16460 let (tail_buffer, cursor_buffer_position_end) = self
16461 .buffer
16462 .read(cx)
16463 .text_anchor_for_position(selection.tail(), cx)?;
16464 if tail_buffer != cursor_buffer {
16465 return None;
16466 }
16467
16468 let snapshot = cursor_buffer.read(cx).snapshot();
16469 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16470 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16471 let prepare_rename = provider
16472 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16473 .unwrap_or_else(|| Task::ready(Ok(None)));
16474 drop(snapshot);
16475
16476 Some(cx.spawn_in(window, async move |this, cx| {
16477 let rename_range = if let Some(range) = prepare_rename.await? {
16478 Some(range)
16479 } else {
16480 this.update(cx, |this, cx| {
16481 let buffer = this.buffer.read(cx).snapshot(cx);
16482 let mut buffer_highlights = this
16483 .document_highlights_for_position(selection.head(), &buffer)
16484 .filter(|highlight| {
16485 highlight.start.excerpt_id == selection.head().excerpt_id
16486 && highlight.end.excerpt_id == selection.head().excerpt_id
16487 });
16488 buffer_highlights
16489 .next()
16490 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16491 })?
16492 };
16493 if let Some(rename_range) = rename_range {
16494 this.update_in(cx, |this, window, cx| {
16495 let snapshot = cursor_buffer.read(cx).snapshot();
16496 let rename_buffer_range = rename_range.to_offset(&snapshot);
16497 let cursor_offset_in_rename_range =
16498 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16499 let cursor_offset_in_rename_range_end =
16500 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16501
16502 this.take_rename(false, window, cx);
16503 let buffer = this.buffer.read(cx).read(cx);
16504 let cursor_offset = selection.head().to_offset(&buffer);
16505 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16506 let rename_end = rename_start + rename_buffer_range.len();
16507 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16508 let mut old_highlight_id = None;
16509 let old_name: Arc<str> = buffer
16510 .chunks(rename_start..rename_end, true)
16511 .map(|chunk| {
16512 if old_highlight_id.is_none() {
16513 old_highlight_id = chunk.syntax_highlight_id;
16514 }
16515 chunk.text
16516 })
16517 .collect::<String>()
16518 .into();
16519
16520 drop(buffer);
16521
16522 // Position the selection in the rename editor so that it matches the current selection.
16523 this.show_local_selections = false;
16524 let rename_editor = cx.new(|cx| {
16525 let mut editor = Editor::single_line(window, cx);
16526 editor.buffer.update(cx, |buffer, cx| {
16527 buffer.edit([(0..0, old_name.clone())], None, cx)
16528 });
16529 let rename_selection_range = match cursor_offset_in_rename_range
16530 .cmp(&cursor_offset_in_rename_range_end)
16531 {
16532 Ordering::Equal => {
16533 editor.select_all(&SelectAll, window, cx);
16534 return editor;
16535 }
16536 Ordering::Less => {
16537 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16538 }
16539 Ordering::Greater => {
16540 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16541 }
16542 };
16543 if rename_selection_range.end > old_name.len() {
16544 editor.select_all(&SelectAll, window, cx);
16545 } else {
16546 editor.change_selections(Default::default(), window, cx, |s| {
16547 s.select_ranges([rename_selection_range]);
16548 });
16549 }
16550 editor
16551 });
16552 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16553 if e == &EditorEvent::Focused {
16554 cx.emit(EditorEvent::FocusedIn)
16555 }
16556 })
16557 .detach();
16558
16559 let write_highlights =
16560 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16561 let read_highlights =
16562 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16563 let ranges = write_highlights
16564 .iter()
16565 .flat_map(|(_, ranges)| ranges.iter())
16566 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16567 .cloned()
16568 .collect();
16569
16570 this.highlight_text::<Rename>(
16571 ranges,
16572 HighlightStyle {
16573 fade_out: Some(0.6),
16574 ..Default::default()
16575 },
16576 cx,
16577 );
16578 let rename_focus_handle = rename_editor.focus_handle(cx);
16579 window.focus(&rename_focus_handle);
16580 let block_id = this.insert_blocks(
16581 [BlockProperties {
16582 style: BlockStyle::Flex,
16583 placement: BlockPlacement::Below(range.start),
16584 height: Some(1),
16585 render: Arc::new({
16586 let rename_editor = rename_editor.clone();
16587 move |cx: &mut BlockContext| {
16588 let mut text_style = cx.editor_style.text.clone();
16589 if let Some(highlight_style) = old_highlight_id
16590 .and_then(|h| h.style(&cx.editor_style.syntax))
16591 {
16592 text_style = text_style.highlight(highlight_style);
16593 }
16594 div()
16595 .block_mouse_except_scroll()
16596 .pl(cx.anchor_x)
16597 .child(EditorElement::new(
16598 &rename_editor,
16599 EditorStyle {
16600 background: cx.theme().system().transparent,
16601 local_player: cx.editor_style.local_player,
16602 text: text_style,
16603 scrollbar_width: cx.editor_style.scrollbar_width,
16604 syntax: cx.editor_style.syntax.clone(),
16605 status: cx.editor_style.status.clone(),
16606 inlay_hints_style: HighlightStyle {
16607 font_weight: Some(FontWeight::BOLD),
16608 ..make_inlay_hints_style(cx.app)
16609 },
16610 edit_prediction_styles: make_suggestion_styles(
16611 cx.app,
16612 ),
16613 ..EditorStyle::default()
16614 },
16615 ))
16616 .into_any_element()
16617 }
16618 }),
16619 priority: 0,
16620 }],
16621 Some(Autoscroll::fit()),
16622 cx,
16623 )[0];
16624 this.pending_rename = Some(RenameState {
16625 range,
16626 old_name,
16627 editor: rename_editor,
16628 block_id,
16629 });
16630 })?;
16631 }
16632
16633 Ok(())
16634 }))
16635 }
16636
16637 pub fn confirm_rename(
16638 &mut self,
16639 _: &ConfirmRename,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) -> Option<Task<Result<()>>> {
16643 let rename = self.take_rename(false, window, cx)?;
16644 let workspace = self.workspace()?.downgrade();
16645 let (buffer, start) = self
16646 .buffer
16647 .read(cx)
16648 .text_anchor_for_position(rename.range.start, cx)?;
16649 let (end_buffer, _) = self
16650 .buffer
16651 .read(cx)
16652 .text_anchor_for_position(rename.range.end, cx)?;
16653 if buffer != end_buffer {
16654 return None;
16655 }
16656
16657 let old_name = rename.old_name;
16658 let new_name = rename.editor.read(cx).text(cx);
16659
16660 let rename = self.semantics_provider.as_ref()?.perform_rename(
16661 &buffer,
16662 start,
16663 new_name.clone(),
16664 cx,
16665 )?;
16666
16667 Some(cx.spawn_in(window, async move |editor, cx| {
16668 let project_transaction = rename.await?;
16669 Self::open_project_transaction(
16670 &editor,
16671 workspace,
16672 project_transaction,
16673 format!("Rename: {} → {}", old_name, new_name),
16674 cx,
16675 )
16676 .await?;
16677
16678 editor.update(cx, |editor, cx| {
16679 editor.refresh_document_highlights(cx);
16680 })?;
16681 Ok(())
16682 }))
16683 }
16684
16685 fn take_rename(
16686 &mut self,
16687 moving_cursor: bool,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) -> Option<RenameState> {
16691 let rename = self.pending_rename.take()?;
16692 if rename.editor.focus_handle(cx).is_focused(window) {
16693 window.focus(&self.focus_handle);
16694 }
16695
16696 self.remove_blocks(
16697 [rename.block_id].into_iter().collect(),
16698 Some(Autoscroll::fit()),
16699 cx,
16700 );
16701 self.clear_highlights::<Rename>(cx);
16702 self.show_local_selections = true;
16703
16704 if moving_cursor {
16705 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16706 editor.selections.newest::<usize>(cx).head()
16707 });
16708
16709 // Update the selection to match the position of the selection inside
16710 // the rename editor.
16711 let snapshot = self.buffer.read(cx).read(cx);
16712 let rename_range = rename.range.to_offset(&snapshot);
16713 let cursor_in_editor = snapshot
16714 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16715 .min(rename_range.end);
16716 drop(snapshot);
16717
16718 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16719 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16720 });
16721 } else {
16722 self.refresh_document_highlights(cx);
16723 }
16724
16725 Some(rename)
16726 }
16727
16728 pub fn pending_rename(&self) -> Option<&RenameState> {
16729 self.pending_rename.as_ref()
16730 }
16731
16732 fn format(
16733 &mut self,
16734 _: &Format,
16735 window: &mut Window,
16736 cx: &mut Context<Self>,
16737 ) -> Option<Task<Result<()>>> {
16738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16739
16740 let project = match &self.project {
16741 Some(project) => project.clone(),
16742 None => return None,
16743 };
16744
16745 Some(self.perform_format(
16746 project,
16747 FormatTrigger::Manual,
16748 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16749 window,
16750 cx,
16751 ))
16752 }
16753
16754 fn format_selections(
16755 &mut self,
16756 _: &FormatSelections,
16757 window: &mut Window,
16758 cx: &mut Context<Self>,
16759 ) -> Option<Task<Result<()>>> {
16760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16761
16762 let project = match &self.project {
16763 Some(project) => project.clone(),
16764 None => return None,
16765 };
16766
16767 let ranges = self
16768 .selections
16769 .all_adjusted(cx)
16770 .into_iter()
16771 .map(|selection| selection.range())
16772 .collect_vec();
16773
16774 Some(self.perform_format(
16775 project,
16776 FormatTrigger::Manual,
16777 FormatTarget::Ranges(ranges),
16778 window,
16779 cx,
16780 ))
16781 }
16782
16783 fn perform_format(
16784 &mut self,
16785 project: Entity<Project>,
16786 trigger: FormatTrigger,
16787 target: FormatTarget,
16788 window: &mut Window,
16789 cx: &mut Context<Self>,
16790 ) -> Task<Result<()>> {
16791 let buffer = self.buffer.clone();
16792 let (buffers, target) = match target {
16793 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16794 FormatTarget::Ranges(selection_ranges) => {
16795 let multi_buffer = buffer.read(cx);
16796 let snapshot = multi_buffer.read(cx);
16797 let mut buffers = HashSet::default();
16798 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16799 BTreeMap::new();
16800 for selection_range in selection_ranges {
16801 for (buffer, buffer_range, _) in
16802 snapshot.range_to_buffer_ranges(selection_range)
16803 {
16804 let buffer_id = buffer.remote_id();
16805 let start = buffer.anchor_before(buffer_range.start);
16806 let end = buffer.anchor_after(buffer_range.end);
16807 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16808 buffer_id_to_ranges
16809 .entry(buffer_id)
16810 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16811 .or_insert_with(|| vec![start..end]);
16812 }
16813 }
16814 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16815 }
16816 };
16817
16818 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16819 let selections_prev = transaction_id_prev
16820 .and_then(|transaction_id_prev| {
16821 // default to selections as they were after the last edit, if we have them,
16822 // instead of how they are now.
16823 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16824 // will take you back to where you made the last edit, instead of staying where you scrolled
16825 self.selection_history
16826 .transaction(transaction_id_prev)
16827 .map(|t| t.0.clone())
16828 })
16829 .unwrap_or_else(|| self.selections.disjoint_anchors());
16830
16831 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16832 let format = project.update(cx, |project, cx| {
16833 project.format(buffers, target, true, trigger, cx)
16834 });
16835
16836 cx.spawn_in(window, async move |editor, cx| {
16837 let transaction = futures::select_biased! {
16838 transaction = format.log_err().fuse() => transaction,
16839 () = timeout => {
16840 log::warn!("timed out waiting for formatting");
16841 None
16842 }
16843 };
16844
16845 buffer
16846 .update(cx, |buffer, cx| {
16847 if let Some(transaction) = transaction
16848 && !buffer.is_singleton()
16849 {
16850 buffer.push_transaction(&transaction.0, cx);
16851 }
16852 cx.notify();
16853 })
16854 .ok();
16855
16856 if let Some(transaction_id_now) =
16857 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16858 {
16859 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16860 if has_new_transaction {
16861 _ = editor.update(cx, |editor, _| {
16862 editor
16863 .selection_history
16864 .insert_transaction(transaction_id_now, selections_prev);
16865 });
16866 }
16867 }
16868
16869 Ok(())
16870 })
16871 }
16872
16873 fn organize_imports(
16874 &mut self,
16875 _: &OrganizeImports,
16876 window: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) -> Option<Task<Result<()>>> {
16879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16880 let project = match &self.project {
16881 Some(project) => project.clone(),
16882 None => return None,
16883 };
16884 Some(self.perform_code_action_kind(
16885 project,
16886 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16887 window,
16888 cx,
16889 ))
16890 }
16891
16892 fn perform_code_action_kind(
16893 &mut self,
16894 project: Entity<Project>,
16895 kind: CodeActionKind,
16896 window: &mut Window,
16897 cx: &mut Context<Self>,
16898 ) -> Task<Result<()>> {
16899 let buffer = self.buffer.clone();
16900 let buffers = buffer.read(cx).all_buffers();
16901 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16902 let apply_action = project.update(cx, |project, cx| {
16903 project.apply_code_action_kind(buffers, kind, true, cx)
16904 });
16905 cx.spawn_in(window, async move |_, cx| {
16906 let transaction = futures::select_biased! {
16907 () = timeout => {
16908 log::warn!("timed out waiting for executing code action");
16909 None
16910 }
16911 transaction = apply_action.log_err().fuse() => transaction,
16912 };
16913 buffer
16914 .update(cx, |buffer, cx| {
16915 // check if we need this
16916 if let Some(transaction) = transaction
16917 && !buffer.is_singleton()
16918 {
16919 buffer.push_transaction(&transaction.0, cx);
16920 }
16921 cx.notify();
16922 })
16923 .ok();
16924 Ok(())
16925 })
16926 }
16927
16928 pub fn restart_language_server(
16929 &mut self,
16930 _: &RestartLanguageServer,
16931 _: &mut Window,
16932 cx: &mut Context<Self>,
16933 ) {
16934 if let Some(project) = self.project.clone() {
16935 self.buffer.update(cx, |multi_buffer, cx| {
16936 project.update(cx, |project, cx| {
16937 project.restart_language_servers_for_buffers(
16938 multi_buffer.all_buffers().into_iter().collect(),
16939 HashSet::default(),
16940 cx,
16941 );
16942 });
16943 })
16944 }
16945 }
16946
16947 pub fn stop_language_server(
16948 &mut self,
16949 _: &StopLanguageServer,
16950 _: &mut Window,
16951 cx: &mut Context<Self>,
16952 ) {
16953 if let Some(project) = self.project.clone() {
16954 self.buffer.update(cx, |multi_buffer, cx| {
16955 project.update(cx, |project, cx| {
16956 project.stop_language_servers_for_buffers(
16957 multi_buffer.all_buffers().into_iter().collect(),
16958 HashSet::default(),
16959 cx,
16960 );
16961 cx.emit(project::Event::RefreshInlayHints);
16962 });
16963 });
16964 }
16965 }
16966
16967 fn cancel_language_server_work(
16968 workspace: &mut Workspace,
16969 _: &actions::CancelLanguageServerWork,
16970 _: &mut Window,
16971 cx: &mut Context<Workspace>,
16972 ) {
16973 let project = workspace.project();
16974 let buffers = workspace
16975 .active_item(cx)
16976 .and_then(|item| item.act_as::<Editor>(cx))
16977 .map_or(HashSet::default(), |editor| {
16978 editor.read(cx).buffer.read(cx).all_buffers()
16979 });
16980 project.update(cx, |project, cx| {
16981 project.cancel_language_server_work_for_buffers(buffers, cx);
16982 });
16983 }
16984
16985 fn show_character_palette(
16986 &mut self,
16987 _: &ShowCharacterPalette,
16988 window: &mut Window,
16989 _: &mut Context<Self>,
16990 ) {
16991 window.show_character_palette();
16992 }
16993
16994 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16995 if !self.diagnostics_enabled() {
16996 return;
16997 }
16998
16999 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17000 let buffer = self.buffer.read(cx).snapshot(cx);
17001 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17002 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17003 let is_valid = buffer
17004 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17005 .any(|entry| {
17006 entry.diagnostic.is_primary
17007 && !entry.range.is_empty()
17008 && entry.range.start == primary_range_start
17009 && entry.diagnostic.message == active_diagnostics.active_message
17010 });
17011
17012 if !is_valid {
17013 self.dismiss_diagnostics(cx);
17014 }
17015 }
17016 }
17017
17018 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17019 match &self.active_diagnostics {
17020 ActiveDiagnostic::Group(group) => Some(group),
17021 _ => None,
17022 }
17023 }
17024
17025 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17026 if !self.diagnostics_enabled() {
17027 return;
17028 }
17029 self.dismiss_diagnostics(cx);
17030 self.active_diagnostics = ActiveDiagnostic::All;
17031 }
17032
17033 fn activate_diagnostics(
17034 &mut self,
17035 buffer_id: BufferId,
17036 diagnostic: DiagnosticEntry<usize>,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17041 return;
17042 }
17043 self.dismiss_diagnostics(cx);
17044 let snapshot = self.snapshot(window, cx);
17045 let buffer = self.buffer.read(cx).snapshot(cx);
17046 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17047 return;
17048 };
17049
17050 let diagnostic_group = buffer
17051 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17052 .collect::<Vec<_>>();
17053
17054 let blocks =
17055 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17056
17057 let blocks = self.display_map.update(cx, |display_map, cx| {
17058 display_map.insert_blocks(blocks, cx).into_iter().collect()
17059 });
17060 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17061 active_range: buffer.anchor_before(diagnostic.range.start)
17062 ..buffer.anchor_after(diagnostic.range.end),
17063 active_message: diagnostic.diagnostic.message.clone(),
17064 group_id: diagnostic.diagnostic.group_id,
17065 blocks,
17066 });
17067 cx.notify();
17068 }
17069
17070 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17071 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17072 return;
17073 };
17074
17075 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17076 if let ActiveDiagnostic::Group(group) = prev {
17077 self.display_map.update(cx, |display_map, cx| {
17078 display_map.remove_blocks(group.blocks, cx);
17079 });
17080 cx.notify();
17081 }
17082 }
17083
17084 /// Disable inline diagnostics rendering for this editor.
17085 pub fn disable_inline_diagnostics(&mut self) {
17086 self.inline_diagnostics_enabled = false;
17087 self.inline_diagnostics_update = Task::ready(());
17088 self.inline_diagnostics.clear();
17089 }
17090
17091 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17092 self.diagnostics_enabled = false;
17093 self.dismiss_diagnostics(cx);
17094 self.inline_diagnostics_update = Task::ready(());
17095 self.inline_diagnostics.clear();
17096 }
17097
17098 pub fn diagnostics_enabled(&self) -> bool {
17099 self.diagnostics_enabled && self.mode.is_full()
17100 }
17101
17102 pub fn inline_diagnostics_enabled(&self) -> bool {
17103 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17104 }
17105
17106 pub fn show_inline_diagnostics(&self) -> bool {
17107 self.show_inline_diagnostics
17108 }
17109
17110 pub fn toggle_inline_diagnostics(
17111 &mut self,
17112 _: &ToggleInlineDiagnostics,
17113 window: &mut Window,
17114 cx: &mut Context<Editor>,
17115 ) {
17116 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17117 self.refresh_inline_diagnostics(false, window, cx);
17118 }
17119
17120 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17121 self.diagnostics_max_severity = severity;
17122 self.display_map.update(cx, |display_map, _| {
17123 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17124 });
17125 }
17126
17127 pub fn toggle_diagnostics(
17128 &mut self,
17129 _: &ToggleDiagnostics,
17130 window: &mut Window,
17131 cx: &mut Context<Editor>,
17132 ) {
17133 if !self.diagnostics_enabled() {
17134 return;
17135 }
17136
17137 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17138 EditorSettings::get_global(cx)
17139 .diagnostics_max_severity
17140 .filter(|severity| severity != &DiagnosticSeverity::Off)
17141 .unwrap_or(DiagnosticSeverity::Hint)
17142 } else {
17143 DiagnosticSeverity::Off
17144 };
17145 self.set_max_diagnostics_severity(new_severity, cx);
17146 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17147 self.active_diagnostics = ActiveDiagnostic::None;
17148 self.inline_diagnostics_update = Task::ready(());
17149 self.inline_diagnostics.clear();
17150 } else {
17151 self.refresh_inline_diagnostics(false, window, cx);
17152 }
17153
17154 cx.notify();
17155 }
17156
17157 pub fn toggle_minimap(
17158 &mut self,
17159 _: &ToggleMinimap,
17160 window: &mut Window,
17161 cx: &mut Context<Editor>,
17162 ) {
17163 if self.supports_minimap(cx) {
17164 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17165 }
17166 }
17167
17168 fn refresh_inline_diagnostics(
17169 &mut self,
17170 debounce: bool,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) {
17174 let max_severity = ProjectSettings::get_global(cx)
17175 .diagnostics
17176 .inline
17177 .max_severity
17178 .unwrap_or(self.diagnostics_max_severity);
17179
17180 if !self.inline_diagnostics_enabled()
17181 || !self.show_inline_diagnostics
17182 || max_severity == DiagnosticSeverity::Off
17183 {
17184 self.inline_diagnostics_update = Task::ready(());
17185 self.inline_diagnostics.clear();
17186 return;
17187 }
17188
17189 let debounce_ms = ProjectSettings::get_global(cx)
17190 .diagnostics
17191 .inline
17192 .update_debounce_ms;
17193 let debounce = if debounce && debounce_ms > 0 {
17194 Some(Duration::from_millis(debounce_ms))
17195 } else {
17196 None
17197 };
17198 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17199 if let Some(debounce) = debounce {
17200 cx.background_executor().timer(debounce).await;
17201 }
17202 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17203 editor
17204 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17205 .ok()
17206 }) else {
17207 return;
17208 };
17209
17210 let new_inline_diagnostics = cx
17211 .background_spawn(async move {
17212 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17213 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17214 let message = diagnostic_entry
17215 .diagnostic
17216 .message
17217 .split_once('\n')
17218 .map(|(line, _)| line)
17219 .map(SharedString::new)
17220 .unwrap_or_else(|| {
17221 SharedString::from(diagnostic_entry.diagnostic.message)
17222 });
17223 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17224 let (Ok(i) | Err(i)) = inline_diagnostics
17225 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17226 inline_diagnostics.insert(
17227 i,
17228 (
17229 start_anchor,
17230 InlineDiagnostic {
17231 message,
17232 group_id: diagnostic_entry.diagnostic.group_id,
17233 start: diagnostic_entry.range.start.to_point(&snapshot),
17234 is_primary: diagnostic_entry.diagnostic.is_primary,
17235 severity: diagnostic_entry.diagnostic.severity,
17236 },
17237 ),
17238 );
17239 }
17240 inline_diagnostics
17241 })
17242 .await;
17243
17244 editor
17245 .update(cx, |editor, cx| {
17246 editor.inline_diagnostics = new_inline_diagnostics;
17247 cx.notify();
17248 })
17249 .ok();
17250 });
17251 }
17252
17253 fn pull_diagnostics(
17254 &mut self,
17255 buffer_id: Option<BufferId>,
17256 window: &Window,
17257 cx: &mut Context<Self>,
17258 ) -> Option<()> {
17259 if !self.mode().is_full() {
17260 return None;
17261 }
17262 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17263 .diagnostics
17264 .lsp_pull_diagnostics;
17265 if !pull_diagnostics_settings.enabled {
17266 return None;
17267 }
17268 let project = self.project()?.downgrade();
17269 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17270 let mut buffers = self.buffer.read(cx).all_buffers();
17271 if let Some(buffer_id) = buffer_id {
17272 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17273 }
17274
17275 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17276 cx.background_executor().timer(debounce).await;
17277
17278 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17279 buffers
17280 .into_iter()
17281 .filter_map(|buffer| {
17282 project
17283 .update(cx, |project, cx| {
17284 project.lsp_store().update(cx, |lsp_store, cx| {
17285 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17286 })
17287 })
17288 .ok()
17289 })
17290 .collect::<FuturesUnordered<_>>()
17291 }) else {
17292 return;
17293 };
17294
17295 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17296 match pull_task {
17297 Ok(()) => {
17298 if editor
17299 .update_in(cx, |editor, window, cx| {
17300 editor.update_diagnostics_state(window, cx);
17301 })
17302 .is_err()
17303 {
17304 return;
17305 }
17306 }
17307 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17308 }
17309 }
17310 });
17311
17312 Some(())
17313 }
17314
17315 pub fn set_selections_from_remote(
17316 &mut self,
17317 selections: Vec<Selection<Anchor>>,
17318 pending_selection: Option<Selection<Anchor>>,
17319 window: &mut Window,
17320 cx: &mut Context<Self>,
17321 ) {
17322 let old_cursor_position = self.selections.newest_anchor().head();
17323 self.selections.change_with(cx, |s| {
17324 s.select_anchors(selections);
17325 if let Some(pending_selection) = pending_selection {
17326 s.set_pending(pending_selection, SelectMode::Character);
17327 } else {
17328 s.clear_pending();
17329 }
17330 });
17331 self.selections_did_change(
17332 false,
17333 &old_cursor_position,
17334 SelectionEffects::default(),
17335 window,
17336 cx,
17337 );
17338 }
17339
17340 pub fn transact(
17341 &mut self,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17345 ) -> Option<TransactionId> {
17346 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17347 this.start_transaction_at(Instant::now(), window, cx);
17348 update(this, window, cx);
17349 this.end_transaction_at(Instant::now(), cx)
17350 })
17351 }
17352
17353 pub fn start_transaction_at(
17354 &mut self,
17355 now: Instant,
17356 window: &mut Window,
17357 cx: &mut Context<Self>,
17358 ) -> Option<TransactionId> {
17359 self.end_selection(window, cx);
17360 if let Some(tx_id) = self
17361 .buffer
17362 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17363 {
17364 self.selection_history
17365 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17366 cx.emit(EditorEvent::TransactionBegun {
17367 transaction_id: tx_id,
17368 });
17369 Some(tx_id)
17370 } else {
17371 None
17372 }
17373 }
17374
17375 pub fn end_transaction_at(
17376 &mut self,
17377 now: Instant,
17378 cx: &mut Context<Self>,
17379 ) -> Option<TransactionId> {
17380 if let Some(transaction_id) = self
17381 .buffer
17382 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17383 {
17384 if let Some((_, end_selections)) =
17385 self.selection_history.transaction_mut(transaction_id)
17386 {
17387 *end_selections = Some(self.selections.disjoint_anchors());
17388 } else {
17389 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17390 }
17391
17392 cx.emit(EditorEvent::Edited { transaction_id });
17393 Some(transaction_id)
17394 } else {
17395 None
17396 }
17397 }
17398
17399 pub fn modify_transaction_selection_history(
17400 &mut self,
17401 transaction_id: TransactionId,
17402 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17403 ) -> bool {
17404 self.selection_history
17405 .transaction_mut(transaction_id)
17406 .map(modify)
17407 .is_some()
17408 }
17409
17410 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17411 if self.selection_mark_mode {
17412 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17413 s.move_with(|_, sel| {
17414 sel.collapse_to(sel.head(), SelectionGoal::None);
17415 });
17416 })
17417 }
17418 self.selection_mark_mode = true;
17419 cx.notify();
17420 }
17421
17422 pub fn swap_selection_ends(
17423 &mut self,
17424 _: &actions::SwapSelectionEnds,
17425 window: &mut Window,
17426 cx: &mut Context<Self>,
17427 ) {
17428 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17429 s.move_with(|_, sel| {
17430 if sel.start != sel.end {
17431 sel.reversed = !sel.reversed
17432 }
17433 });
17434 });
17435 self.request_autoscroll(Autoscroll::newest(), cx);
17436 cx.notify();
17437 }
17438
17439 pub fn toggle_focus(
17440 workspace: &mut Workspace,
17441 _: &actions::ToggleFocus,
17442 window: &mut Window,
17443 cx: &mut Context<Workspace>,
17444 ) {
17445 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17446 return;
17447 };
17448 workspace.activate_item(&item, true, true, window, cx);
17449 }
17450
17451 pub fn toggle_fold(
17452 &mut self,
17453 _: &actions::ToggleFold,
17454 window: &mut Window,
17455 cx: &mut Context<Self>,
17456 ) {
17457 if self.is_singleton(cx) {
17458 let selection = self.selections.newest::<Point>(cx);
17459
17460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17461 let range = if selection.is_empty() {
17462 let point = selection.head().to_display_point(&display_map);
17463 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17464 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17465 .to_point(&display_map);
17466 start..end
17467 } else {
17468 selection.range()
17469 };
17470 if display_map.folds_in_range(range).next().is_some() {
17471 self.unfold_lines(&Default::default(), window, cx)
17472 } else {
17473 self.fold(&Default::default(), window, cx)
17474 }
17475 } else {
17476 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17477 let buffer_ids: HashSet<_> = self
17478 .selections
17479 .disjoint_anchor_ranges()
17480 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17481 .collect();
17482
17483 let should_unfold = buffer_ids
17484 .iter()
17485 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17486
17487 for buffer_id in buffer_ids {
17488 if should_unfold {
17489 self.unfold_buffer(buffer_id, cx);
17490 } else {
17491 self.fold_buffer(buffer_id, cx);
17492 }
17493 }
17494 }
17495 }
17496
17497 pub fn toggle_fold_recursive(
17498 &mut self,
17499 _: &actions::ToggleFoldRecursive,
17500 window: &mut Window,
17501 cx: &mut Context<Self>,
17502 ) {
17503 let selection = self.selections.newest::<Point>(cx);
17504
17505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17506 let range = if selection.is_empty() {
17507 let point = selection.head().to_display_point(&display_map);
17508 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17509 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17510 .to_point(&display_map);
17511 start..end
17512 } else {
17513 selection.range()
17514 };
17515 if display_map.folds_in_range(range).next().is_some() {
17516 self.unfold_recursive(&Default::default(), window, cx)
17517 } else {
17518 self.fold_recursive(&Default::default(), window, cx)
17519 }
17520 }
17521
17522 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17523 if self.is_singleton(cx) {
17524 let mut to_fold = Vec::new();
17525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17526 let selections = self.selections.all_adjusted(cx);
17527
17528 for selection in selections {
17529 let range = selection.range().sorted();
17530 let buffer_start_row = range.start.row;
17531
17532 if range.start.row != range.end.row {
17533 let mut found = false;
17534 let mut row = range.start.row;
17535 while row <= range.end.row {
17536 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17537 {
17538 found = true;
17539 row = crease.range().end.row + 1;
17540 to_fold.push(crease);
17541 } else {
17542 row += 1
17543 }
17544 }
17545 if found {
17546 continue;
17547 }
17548 }
17549
17550 for row in (0..=range.start.row).rev() {
17551 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17552 && crease.range().end.row >= buffer_start_row
17553 {
17554 to_fold.push(crease);
17555 if row <= range.start.row {
17556 break;
17557 }
17558 }
17559 }
17560 }
17561
17562 self.fold_creases(to_fold, true, window, cx);
17563 } else {
17564 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17565 let buffer_ids = self
17566 .selections
17567 .disjoint_anchor_ranges()
17568 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17569 .collect::<HashSet<_>>();
17570 for buffer_id in buffer_ids {
17571 self.fold_buffer(buffer_id, cx);
17572 }
17573 }
17574 }
17575
17576 pub fn toggle_fold_all(
17577 &mut self,
17578 _: &actions::ToggleFoldAll,
17579 window: &mut Window,
17580 cx: &mut Context<Self>,
17581 ) {
17582 if self.buffer.read(cx).is_singleton() {
17583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17584 let has_folds = display_map
17585 .folds_in_range(0..display_map.buffer_snapshot.len())
17586 .next()
17587 .is_some();
17588
17589 if has_folds {
17590 self.unfold_all(&actions::UnfoldAll, window, cx);
17591 } else {
17592 self.fold_all(&actions::FoldAll, window, cx);
17593 }
17594 } else {
17595 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17596 let should_unfold = buffer_ids
17597 .iter()
17598 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17599
17600 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17601 editor
17602 .update_in(cx, |editor, _, cx| {
17603 for buffer_id in buffer_ids {
17604 if should_unfold {
17605 editor.unfold_buffer(buffer_id, cx);
17606 } else {
17607 editor.fold_buffer(buffer_id, cx);
17608 }
17609 }
17610 })
17611 .ok();
17612 });
17613 }
17614 }
17615
17616 fn fold_at_level(
17617 &mut self,
17618 fold_at: &FoldAtLevel,
17619 window: &mut Window,
17620 cx: &mut Context<Self>,
17621 ) {
17622 if !self.buffer.read(cx).is_singleton() {
17623 return;
17624 }
17625
17626 let fold_at_level = fold_at.0;
17627 let snapshot = self.buffer.read(cx).snapshot(cx);
17628 let mut to_fold = Vec::new();
17629 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17630
17631 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17632 while start_row < end_row {
17633 match self
17634 .snapshot(window, cx)
17635 .crease_for_buffer_row(MultiBufferRow(start_row))
17636 {
17637 Some(crease) => {
17638 let nested_start_row = crease.range().start.row + 1;
17639 let nested_end_row = crease.range().end.row;
17640
17641 if current_level < fold_at_level {
17642 stack.push((nested_start_row, nested_end_row, current_level + 1));
17643 } else if current_level == fold_at_level {
17644 to_fold.push(crease);
17645 }
17646
17647 start_row = nested_end_row + 1;
17648 }
17649 None => start_row += 1,
17650 }
17651 }
17652 }
17653
17654 self.fold_creases(to_fold, true, window, cx);
17655 }
17656
17657 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17658 if self.buffer.read(cx).is_singleton() {
17659 let mut fold_ranges = Vec::new();
17660 let snapshot = self.buffer.read(cx).snapshot(cx);
17661
17662 for row in 0..snapshot.max_row().0 {
17663 if let Some(foldable_range) = self
17664 .snapshot(window, cx)
17665 .crease_for_buffer_row(MultiBufferRow(row))
17666 {
17667 fold_ranges.push(foldable_range);
17668 }
17669 }
17670
17671 self.fold_creases(fold_ranges, true, window, cx);
17672 } else {
17673 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17674 editor
17675 .update_in(cx, |editor, _, cx| {
17676 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17677 editor.fold_buffer(buffer_id, cx);
17678 }
17679 })
17680 .ok();
17681 });
17682 }
17683 }
17684
17685 pub fn fold_function_bodies(
17686 &mut self,
17687 _: &actions::FoldFunctionBodies,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 let snapshot = self.buffer.read(cx).snapshot(cx);
17692
17693 let ranges = snapshot
17694 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17695 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17696 .collect::<Vec<_>>();
17697
17698 let creases = ranges
17699 .into_iter()
17700 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17701 .collect();
17702
17703 self.fold_creases(creases, true, window, cx);
17704 }
17705
17706 pub fn fold_recursive(
17707 &mut self,
17708 _: &actions::FoldRecursive,
17709 window: &mut Window,
17710 cx: &mut Context<Self>,
17711 ) {
17712 let mut to_fold = Vec::new();
17713 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17714 let selections = self.selections.all_adjusted(cx);
17715
17716 for selection in selections {
17717 let range = selection.range().sorted();
17718 let buffer_start_row = range.start.row;
17719
17720 if range.start.row != range.end.row {
17721 let mut found = false;
17722 for row in range.start.row..=range.end.row {
17723 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17724 found = true;
17725 to_fold.push(crease);
17726 }
17727 }
17728 if found {
17729 continue;
17730 }
17731 }
17732
17733 for row in (0..=range.start.row).rev() {
17734 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17735 if crease.range().end.row >= buffer_start_row {
17736 to_fold.push(crease);
17737 } else {
17738 break;
17739 }
17740 }
17741 }
17742 }
17743
17744 self.fold_creases(to_fold, true, window, cx);
17745 }
17746
17747 pub fn fold_at(
17748 &mut self,
17749 buffer_row: MultiBufferRow,
17750 window: &mut Window,
17751 cx: &mut Context<Self>,
17752 ) {
17753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17754
17755 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17756 let autoscroll = self
17757 .selections
17758 .all::<Point>(cx)
17759 .iter()
17760 .any(|selection| crease.range().overlaps(&selection.range()));
17761
17762 self.fold_creases(vec![crease], autoscroll, window, cx);
17763 }
17764 }
17765
17766 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17767 if self.is_singleton(cx) {
17768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17769 let buffer = &display_map.buffer_snapshot;
17770 let selections = self.selections.all::<Point>(cx);
17771 let ranges = selections
17772 .iter()
17773 .map(|s| {
17774 let range = s.display_range(&display_map).sorted();
17775 let mut start = range.start.to_point(&display_map);
17776 let mut end = range.end.to_point(&display_map);
17777 start.column = 0;
17778 end.column = buffer.line_len(MultiBufferRow(end.row));
17779 start..end
17780 })
17781 .collect::<Vec<_>>();
17782
17783 self.unfold_ranges(&ranges, true, true, cx);
17784 } else {
17785 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17786 let buffer_ids = self
17787 .selections
17788 .disjoint_anchor_ranges()
17789 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17790 .collect::<HashSet<_>>();
17791 for buffer_id in buffer_ids {
17792 self.unfold_buffer(buffer_id, cx);
17793 }
17794 }
17795 }
17796
17797 pub fn unfold_recursive(
17798 &mut self,
17799 _: &UnfoldRecursive,
17800 _window: &mut Window,
17801 cx: &mut Context<Self>,
17802 ) {
17803 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17804 let selections = self.selections.all::<Point>(cx);
17805 let ranges = selections
17806 .iter()
17807 .map(|s| {
17808 let mut range = s.display_range(&display_map).sorted();
17809 *range.start.column_mut() = 0;
17810 *range.end.column_mut() = display_map.line_len(range.end.row());
17811 let start = range.start.to_point(&display_map);
17812 let end = range.end.to_point(&display_map);
17813 start..end
17814 })
17815 .collect::<Vec<_>>();
17816
17817 self.unfold_ranges(&ranges, true, true, cx);
17818 }
17819
17820 pub fn unfold_at(
17821 &mut self,
17822 buffer_row: MultiBufferRow,
17823 _window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17827
17828 let intersection_range = Point::new(buffer_row.0, 0)
17829 ..Point::new(
17830 buffer_row.0,
17831 display_map.buffer_snapshot.line_len(buffer_row),
17832 );
17833
17834 let autoscroll = self
17835 .selections
17836 .all::<Point>(cx)
17837 .iter()
17838 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17839
17840 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17841 }
17842
17843 pub fn unfold_all(
17844 &mut self,
17845 _: &actions::UnfoldAll,
17846 _window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) {
17849 if self.buffer.read(cx).is_singleton() {
17850 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17851 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17852 } else {
17853 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17854 editor
17855 .update(cx, |editor, cx| {
17856 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17857 editor.unfold_buffer(buffer_id, cx);
17858 }
17859 })
17860 .ok();
17861 });
17862 }
17863 }
17864
17865 pub fn fold_selected_ranges(
17866 &mut self,
17867 _: &FoldSelectedRanges,
17868 window: &mut Window,
17869 cx: &mut Context<Self>,
17870 ) {
17871 let selections = self.selections.all_adjusted(cx);
17872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17873 let ranges = selections
17874 .into_iter()
17875 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17876 .collect::<Vec<_>>();
17877 self.fold_creases(ranges, true, window, cx);
17878 }
17879
17880 pub fn fold_ranges<T: ToOffset + Clone>(
17881 &mut self,
17882 ranges: Vec<Range<T>>,
17883 auto_scroll: bool,
17884 window: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17888 let ranges = ranges
17889 .into_iter()
17890 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17891 .collect::<Vec<_>>();
17892 self.fold_creases(ranges, auto_scroll, window, cx);
17893 }
17894
17895 pub fn fold_creases<T: ToOffset + Clone>(
17896 &mut self,
17897 creases: Vec<Crease<T>>,
17898 auto_scroll: bool,
17899 _window: &mut Window,
17900 cx: &mut Context<Self>,
17901 ) {
17902 if creases.is_empty() {
17903 return;
17904 }
17905
17906 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17907
17908 if auto_scroll {
17909 self.request_autoscroll(Autoscroll::fit(), cx);
17910 }
17911
17912 cx.notify();
17913
17914 self.scrollbar_marker_state.dirty = true;
17915 self.folds_did_change(cx);
17916 }
17917
17918 /// Removes any folds whose ranges intersect any of the given ranges.
17919 pub fn unfold_ranges<T: ToOffset + Clone>(
17920 &mut self,
17921 ranges: &[Range<T>],
17922 inclusive: bool,
17923 auto_scroll: bool,
17924 cx: &mut Context<Self>,
17925 ) {
17926 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17927 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17928 });
17929 self.folds_did_change(cx);
17930 }
17931
17932 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17933 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17934 return;
17935 }
17936 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17937 self.display_map.update(cx, |display_map, cx| {
17938 display_map.fold_buffers([buffer_id], cx)
17939 });
17940 cx.emit(EditorEvent::BufferFoldToggled {
17941 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17942 folded: true,
17943 });
17944 cx.notify();
17945 }
17946
17947 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17948 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17949 return;
17950 }
17951 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17952 self.display_map.update(cx, |display_map, cx| {
17953 display_map.unfold_buffers([buffer_id], cx);
17954 });
17955 cx.emit(EditorEvent::BufferFoldToggled {
17956 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17957 folded: false,
17958 });
17959 cx.notify();
17960 }
17961
17962 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17963 self.display_map.read(cx).is_buffer_folded(buffer)
17964 }
17965
17966 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17967 self.display_map.read(cx).folded_buffers()
17968 }
17969
17970 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17971 self.display_map.update(cx, |display_map, cx| {
17972 display_map.disable_header_for_buffer(buffer_id, cx);
17973 });
17974 cx.notify();
17975 }
17976
17977 /// Removes any folds with the given ranges.
17978 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17979 &mut self,
17980 ranges: &[Range<T>],
17981 type_id: TypeId,
17982 auto_scroll: bool,
17983 cx: &mut Context<Self>,
17984 ) {
17985 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17986 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17987 });
17988 self.folds_did_change(cx);
17989 }
17990
17991 fn remove_folds_with<T: ToOffset + Clone>(
17992 &mut self,
17993 ranges: &[Range<T>],
17994 auto_scroll: bool,
17995 cx: &mut Context<Self>,
17996 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17997 ) {
17998 if ranges.is_empty() {
17999 return;
18000 }
18001
18002 let mut buffers_affected = HashSet::default();
18003 let multi_buffer = self.buffer().read(cx);
18004 for range in ranges {
18005 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18006 buffers_affected.insert(buffer.read(cx).remote_id());
18007 };
18008 }
18009
18010 self.display_map.update(cx, update);
18011
18012 if auto_scroll {
18013 self.request_autoscroll(Autoscroll::fit(), cx);
18014 }
18015
18016 cx.notify();
18017 self.scrollbar_marker_state.dirty = true;
18018 self.active_indent_guides_state.dirty = true;
18019 }
18020
18021 pub fn update_renderer_widths(
18022 &mut self,
18023 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18024 cx: &mut Context<Self>,
18025 ) -> bool {
18026 self.display_map
18027 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18028 }
18029
18030 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18031 self.display_map.read(cx).fold_placeholder.clone()
18032 }
18033
18034 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18035 self.buffer.update(cx, |buffer, cx| {
18036 buffer.set_all_diff_hunks_expanded(cx);
18037 });
18038 }
18039
18040 pub fn expand_all_diff_hunks(
18041 &mut self,
18042 _: &ExpandAllDiffHunks,
18043 _window: &mut Window,
18044 cx: &mut Context<Self>,
18045 ) {
18046 self.buffer.update(cx, |buffer, cx| {
18047 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18048 });
18049 }
18050
18051 pub fn toggle_selected_diff_hunks(
18052 &mut self,
18053 _: &ToggleSelectedDiffHunks,
18054 _window: &mut Window,
18055 cx: &mut Context<Self>,
18056 ) {
18057 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18058 self.toggle_diff_hunks_in_ranges(ranges, cx);
18059 }
18060
18061 pub fn diff_hunks_in_ranges<'a>(
18062 &'a self,
18063 ranges: &'a [Range<Anchor>],
18064 buffer: &'a MultiBufferSnapshot,
18065 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18066 ranges.iter().flat_map(move |range| {
18067 let end_excerpt_id = range.end.excerpt_id;
18068 let range = range.to_point(buffer);
18069 let mut peek_end = range.end;
18070 if range.end.row < buffer.max_row().0 {
18071 peek_end = Point::new(range.end.row + 1, 0);
18072 }
18073 buffer
18074 .diff_hunks_in_range(range.start..peek_end)
18075 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18076 })
18077 }
18078
18079 pub fn has_stageable_diff_hunks_in_ranges(
18080 &self,
18081 ranges: &[Range<Anchor>],
18082 snapshot: &MultiBufferSnapshot,
18083 ) -> bool {
18084 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18085 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18086 }
18087
18088 pub fn toggle_staged_selected_diff_hunks(
18089 &mut self,
18090 _: &::git::ToggleStaged,
18091 _: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 let snapshot = self.buffer.read(cx).snapshot(cx);
18095 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18096 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18097 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18098 }
18099
18100 pub fn set_render_diff_hunk_controls(
18101 &mut self,
18102 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18103 cx: &mut Context<Self>,
18104 ) {
18105 self.render_diff_hunk_controls = render_diff_hunk_controls;
18106 cx.notify();
18107 }
18108
18109 pub fn stage_and_next(
18110 &mut self,
18111 _: &::git::StageAndNext,
18112 window: &mut Window,
18113 cx: &mut Context<Self>,
18114 ) {
18115 self.do_stage_or_unstage_and_next(true, window, cx);
18116 }
18117
18118 pub fn unstage_and_next(
18119 &mut self,
18120 _: &::git::UnstageAndNext,
18121 window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) {
18124 self.do_stage_or_unstage_and_next(false, window, cx);
18125 }
18126
18127 pub fn stage_or_unstage_diff_hunks(
18128 &mut self,
18129 stage: bool,
18130 ranges: Vec<Range<Anchor>>,
18131 cx: &mut Context<Self>,
18132 ) {
18133 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18134 cx.spawn(async move |this, cx| {
18135 task.await?;
18136 this.update(cx, |this, cx| {
18137 let snapshot = this.buffer.read(cx).snapshot(cx);
18138 let chunk_by = this
18139 .diff_hunks_in_ranges(&ranges, &snapshot)
18140 .chunk_by(|hunk| hunk.buffer_id);
18141 for (buffer_id, hunks) in &chunk_by {
18142 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18143 }
18144 })
18145 })
18146 .detach_and_log_err(cx);
18147 }
18148
18149 fn save_buffers_for_ranges_if_needed(
18150 &mut self,
18151 ranges: &[Range<Anchor>],
18152 cx: &mut Context<Editor>,
18153 ) -> Task<Result<()>> {
18154 let multibuffer = self.buffer.read(cx);
18155 let snapshot = multibuffer.read(cx);
18156 let buffer_ids: HashSet<_> = ranges
18157 .iter()
18158 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18159 .collect();
18160 drop(snapshot);
18161
18162 let mut buffers = HashSet::default();
18163 for buffer_id in buffer_ids {
18164 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18165 let buffer = buffer_entity.read(cx);
18166 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18167 {
18168 buffers.insert(buffer_entity);
18169 }
18170 }
18171 }
18172
18173 if let Some(project) = &self.project {
18174 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18175 } else {
18176 Task::ready(Ok(()))
18177 }
18178 }
18179
18180 fn do_stage_or_unstage_and_next(
18181 &mut self,
18182 stage: bool,
18183 window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18187
18188 if ranges.iter().any(|range| range.start != range.end) {
18189 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18190 return;
18191 }
18192
18193 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18194 let snapshot = self.snapshot(window, cx);
18195 let position = self.selections.newest::<Point>(cx).head();
18196 let mut row = snapshot
18197 .buffer_snapshot
18198 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18199 .find(|hunk| hunk.row_range.start.0 > position.row)
18200 .map(|hunk| hunk.row_range.start);
18201
18202 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18203 // Outside of the project diff editor, wrap around to the beginning.
18204 if !all_diff_hunks_expanded {
18205 row = row.or_else(|| {
18206 snapshot
18207 .buffer_snapshot
18208 .diff_hunks_in_range(Point::zero()..position)
18209 .find(|hunk| hunk.row_range.end.0 < position.row)
18210 .map(|hunk| hunk.row_range.start)
18211 });
18212 }
18213
18214 if let Some(row) = row {
18215 let destination = Point::new(row.0, 0);
18216 let autoscroll = Autoscroll::center();
18217
18218 self.unfold_ranges(&[destination..destination], false, false, cx);
18219 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18220 s.select_ranges([destination..destination]);
18221 });
18222 }
18223 }
18224
18225 fn do_stage_or_unstage(
18226 &self,
18227 stage: bool,
18228 buffer_id: BufferId,
18229 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18230 cx: &mut App,
18231 ) -> Option<()> {
18232 let project = self.project()?;
18233 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18234 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18235 let buffer_snapshot = buffer.read(cx).snapshot();
18236 let file_exists = buffer_snapshot
18237 .file()
18238 .is_some_and(|file| file.disk_state().exists());
18239 diff.update(cx, |diff, cx| {
18240 diff.stage_or_unstage_hunks(
18241 stage,
18242 &hunks
18243 .map(|hunk| buffer_diff::DiffHunk {
18244 buffer_range: hunk.buffer_range,
18245 diff_base_byte_range: hunk.diff_base_byte_range,
18246 secondary_status: hunk.secondary_status,
18247 range: Point::zero()..Point::zero(), // unused
18248 })
18249 .collect::<Vec<_>>(),
18250 &buffer_snapshot,
18251 file_exists,
18252 cx,
18253 )
18254 });
18255 None
18256 }
18257
18258 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18259 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18260 self.buffer
18261 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18262 }
18263
18264 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18265 self.buffer.update(cx, |buffer, cx| {
18266 let ranges = vec![Anchor::min()..Anchor::max()];
18267 if !buffer.all_diff_hunks_expanded()
18268 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18269 {
18270 buffer.collapse_diff_hunks(ranges, cx);
18271 true
18272 } else {
18273 false
18274 }
18275 })
18276 }
18277
18278 fn toggle_diff_hunks_in_ranges(
18279 &mut self,
18280 ranges: Vec<Range<Anchor>>,
18281 cx: &mut Context<Editor>,
18282 ) {
18283 self.buffer.update(cx, |buffer, cx| {
18284 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18285 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18286 })
18287 }
18288
18289 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18290 self.buffer.update(cx, |buffer, cx| {
18291 let snapshot = buffer.snapshot(cx);
18292 let excerpt_id = range.end.excerpt_id;
18293 let point_range = range.to_point(&snapshot);
18294 let expand = !buffer.single_hunk_is_expanded(range, cx);
18295 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18296 })
18297 }
18298
18299 pub(crate) fn apply_all_diff_hunks(
18300 &mut self,
18301 _: &ApplyAllDiffHunks,
18302 window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) {
18305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18306
18307 let buffers = self.buffer.read(cx).all_buffers();
18308 for branch_buffer in buffers {
18309 branch_buffer.update(cx, |branch_buffer, cx| {
18310 branch_buffer.merge_into_base(Vec::new(), cx);
18311 });
18312 }
18313
18314 if let Some(project) = self.project.clone() {
18315 self.save(
18316 SaveOptions {
18317 format: true,
18318 autosave: false,
18319 },
18320 project,
18321 window,
18322 cx,
18323 )
18324 .detach_and_log_err(cx);
18325 }
18326 }
18327
18328 pub(crate) fn apply_selected_diff_hunks(
18329 &mut self,
18330 _: &ApplyDiffHunk,
18331 window: &mut Window,
18332 cx: &mut Context<Self>,
18333 ) {
18334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18335 let snapshot = self.snapshot(window, cx);
18336 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18337 let mut ranges_by_buffer = HashMap::default();
18338 self.transact(window, cx, |editor, _window, cx| {
18339 for hunk in hunks {
18340 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18341 ranges_by_buffer
18342 .entry(buffer.clone())
18343 .or_insert_with(Vec::new)
18344 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18345 }
18346 }
18347
18348 for (buffer, ranges) in ranges_by_buffer {
18349 buffer.update(cx, |buffer, cx| {
18350 buffer.merge_into_base(ranges, cx);
18351 });
18352 }
18353 });
18354
18355 if let Some(project) = self.project.clone() {
18356 self.save(
18357 SaveOptions {
18358 format: true,
18359 autosave: false,
18360 },
18361 project,
18362 window,
18363 cx,
18364 )
18365 .detach_and_log_err(cx);
18366 }
18367 }
18368
18369 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18370 if hovered != self.gutter_hovered {
18371 self.gutter_hovered = hovered;
18372 cx.notify();
18373 }
18374 }
18375
18376 pub fn insert_blocks(
18377 &mut self,
18378 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18379 autoscroll: Option<Autoscroll>,
18380 cx: &mut Context<Self>,
18381 ) -> Vec<CustomBlockId> {
18382 let blocks = self
18383 .display_map
18384 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18385 if let Some(autoscroll) = autoscroll {
18386 self.request_autoscroll(autoscroll, cx);
18387 }
18388 cx.notify();
18389 blocks
18390 }
18391
18392 pub fn resize_blocks(
18393 &mut self,
18394 heights: HashMap<CustomBlockId, u32>,
18395 autoscroll: Option<Autoscroll>,
18396 cx: &mut Context<Self>,
18397 ) {
18398 self.display_map
18399 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18400 if let Some(autoscroll) = autoscroll {
18401 self.request_autoscroll(autoscroll, cx);
18402 }
18403 cx.notify();
18404 }
18405
18406 pub fn replace_blocks(
18407 &mut self,
18408 renderers: HashMap<CustomBlockId, RenderBlock>,
18409 autoscroll: Option<Autoscroll>,
18410 cx: &mut Context<Self>,
18411 ) {
18412 self.display_map
18413 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18414 if let Some(autoscroll) = autoscroll {
18415 self.request_autoscroll(autoscroll, cx);
18416 }
18417 cx.notify();
18418 }
18419
18420 pub fn remove_blocks(
18421 &mut self,
18422 block_ids: HashSet<CustomBlockId>,
18423 autoscroll: Option<Autoscroll>,
18424 cx: &mut Context<Self>,
18425 ) {
18426 self.display_map.update(cx, |display_map, cx| {
18427 display_map.remove_blocks(block_ids, cx)
18428 });
18429 if let Some(autoscroll) = autoscroll {
18430 self.request_autoscroll(autoscroll, cx);
18431 }
18432 cx.notify();
18433 }
18434
18435 pub fn row_for_block(
18436 &self,
18437 block_id: CustomBlockId,
18438 cx: &mut Context<Self>,
18439 ) -> Option<DisplayRow> {
18440 self.display_map
18441 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18442 }
18443
18444 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18445 self.focused_block = Some(focused_block);
18446 }
18447
18448 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18449 self.focused_block.take()
18450 }
18451
18452 pub fn insert_creases(
18453 &mut self,
18454 creases: impl IntoIterator<Item = Crease<Anchor>>,
18455 cx: &mut Context<Self>,
18456 ) -> Vec<CreaseId> {
18457 self.display_map
18458 .update(cx, |map, cx| map.insert_creases(creases, cx))
18459 }
18460
18461 pub fn remove_creases(
18462 &mut self,
18463 ids: impl IntoIterator<Item = CreaseId>,
18464 cx: &mut Context<Self>,
18465 ) -> Vec<(CreaseId, Range<Anchor>)> {
18466 self.display_map
18467 .update(cx, |map, cx| map.remove_creases(ids, cx))
18468 }
18469
18470 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18471 self.display_map
18472 .update(cx, |map, cx| map.snapshot(cx))
18473 .longest_row()
18474 }
18475
18476 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18477 self.display_map
18478 .update(cx, |map, cx| map.snapshot(cx))
18479 .max_point()
18480 }
18481
18482 pub fn text(&self, cx: &App) -> String {
18483 self.buffer.read(cx).read(cx).text()
18484 }
18485
18486 pub fn is_empty(&self, cx: &App) -> bool {
18487 self.buffer.read(cx).read(cx).is_empty()
18488 }
18489
18490 pub fn text_option(&self, cx: &App) -> Option<String> {
18491 let text = self.text(cx);
18492 let text = text.trim();
18493
18494 if text.is_empty() {
18495 return None;
18496 }
18497
18498 Some(text.to_string())
18499 }
18500
18501 pub fn set_text(
18502 &mut self,
18503 text: impl Into<Arc<str>>,
18504 window: &mut Window,
18505 cx: &mut Context<Self>,
18506 ) {
18507 self.transact(window, cx, |this, _, cx| {
18508 this.buffer
18509 .read(cx)
18510 .as_singleton()
18511 .expect("you can only call set_text on editors for singleton buffers")
18512 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18513 });
18514 }
18515
18516 pub fn display_text(&self, cx: &mut App) -> String {
18517 self.display_map
18518 .update(cx, |map, cx| map.snapshot(cx))
18519 .text()
18520 }
18521
18522 fn create_minimap(
18523 &self,
18524 minimap_settings: MinimapSettings,
18525 window: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) -> Option<Entity<Self>> {
18528 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18529 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18530 }
18531
18532 fn initialize_new_minimap(
18533 &self,
18534 minimap_settings: MinimapSettings,
18535 window: &mut Window,
18536 cx: &mut Context<Self>,
18537 ) -> Entity<Self> {
18538 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18539
18540 let mut minimap = Editor::new_internal(
18541 EditorMode::Minimap {
18542 parent: cx.weak_entity(),
18543 },
18544 self.buffer.clone(),
18545 None,
18546 Some(self.display_map.clone()),
18547 window,
18548 cx,
18549 );
18550 minimap.scroll_manager.clone_state(&self.scroll_manager);
18551 minimap.set_text_style_refinement(TextStyleRefinement {
18552 font_size: Some(MINIMAP_FONT_SIZE),
18553 font_weight: Some(MINIMAP_FONT_WEIGHT),
18554 ..Default::default()
18555 });
18556 minimap.update_minimap_configuration(minimap_settings, cx);
18557 cx.new(|_| minimap)
18558 }
18559
18560 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18561 let current_line_highlight = minimap_settings
18562 .current_line_highlight
18563 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18564 self.set_current_line_highlight(Some(current_line_highlight));
18565 }
18566
18567 pub fn minimap(&self) -> Option<&Entity<Self>> {
18568 self.minimap
18569 .as_ref()
18570 .filter(|_| self.minimap_visibility.visible())
18571 }
18572
18573 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18574 let mut wrap_guides = smallvec![];
18575
18576 if self.show_wrap_guides == Some(false) {
18577 return wrap_guides;
18578 }
18579
18580 let settings = self.buffer.read(cx).language_settings(cx);
18581 if settings.show_wrap_guides {
18582 match self.soft_wrap_mode(cx) {
18583 SoftWrap::Column(soft_wrap) => {
18584 wrap_guides.push((soft_wrap as usize, true));
18585 }
18586 SoftWrap::Bounded(soft_wrap) => {
18587 wrap_guides.push((soft_wrap as usize, true));
18588 }
18589 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18590 }
18591 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18592 }
18593
18594 wrap_guides
18595 }
18596
18597 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18598 let settings = self.buffer.read(cx).language_settings(cx);
18599 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18600 match mode {
18601 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18602 SoftWrap::None
18603 }
18604 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18605 language_settings::SoftWrap::PreferredLineLength => {
18606 SoftWrap::Column(settings.preferred_line_length)
18607 }
18608 language_settings::SoftWrap::Bounded => {
18609 SoftWrap::Bounded(settings.preferred_line_length)
18610 }
18611 }
18612 }
18613
18614 pub fn set_soft_wrap_mode(
18615 &mut self,
18616 mode: language_settings::SoftWrap,
18617
18618 cx: &mut Context<Self>,
18619 ) {
18620 self.soft_wrap_mode_override = Some(mode);
18621 cx.notify();
18622 }
18623
18624 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18625 self.hard_wrap = hard_wrap;
18626 cx.notify();
18627 }
18628
18629 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18630 self.text_style_refinement = Some(style);
18631 }
18632
18633 /// called by the Element so we know what style we were most recently rendered with.
18634 pub(crate) fn set_style(
18635 &mut self,
18636 style: EditorStyle,
18637 window: &mut Window,
18638 cx: &mut Context<Self>,
18639 ) {
18640 // We intentionally do not inform the display map about the minimap style
18641 // so that wrapping is not recalculated and stays consistent for the editor
18642 // and its linked minimap.
18643 if !self.mode.is_minimap() {
18644 let rem_size = window.rem_size();
18645 self.display_map.update(cx, |map, cx| {
18646 map.set_font(
18647 style.text.font(),
18648 style.text.font_size.to_pixels(rem_size),
18649 cx,
18650 )
18651 });
18652 }
18653 self.style = Some(style);
18654 }
18655
18656 pub fn style(&self) -> Option<&EditorStyle> {
18657 self.style.as_ref()
18658 }
18659
18660 // Called by the element. This method is not designed to be called outside of the editor
18661 // element's layout code because it does not notify when rewrapping is computed synchronously.
18662 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18663 self.display_map
18664 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18665 }
18666
18667 pub fn set_soft_wrap(&mut self) {
18668 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18669 }
18670
18671 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18672 if self.soft_wrap_mode_override.is_some() {
18673 self.soft_wrap_mode_override.take();
18674 } else {
18675 let soft_wrap = match self.soft_wrap_mode(cx) {
18676 SoftWrap::GitDiff => return,
18677 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18678 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18679 language_settings::SoftWrap::None
18680 }
18681 };
18682 self.soft_wrap_mode_override = Some(soft_wrap);
18683 }
18684 cx.notify();
18685 }
18686
18687 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18688 let Some(workspace) = self.workspace() else {
18689 return;
18690 };
18691 let fs = workspace.read(cx).app_state().fs.clone();
18692 let current_show = TabBarSettings::get_global(cx).show;
18693 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18694 setting.show = Some(!current_show);
18695 });
18696 }
18697
18698 pub fn toggle_indent_guides(
18699 &mut self,
18700 _: &ToggleIndentGuides,
18701 _: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18705 self.buffer
18706 .read(cx)
18707 .language_settings(cx)
18708 .indent_guides
18709 .enabled
18710 });
18711 self.show_indent_guides = Some(!currently_enabled);
18712 cx.notify();
18713 }
18714
18715 fn should_show_indent_guides(&self) -> Option<bool> {
18716 self.show_indent_guides
18717 }
18718
18719 pub fn toggle_line_numbers(
18720 &mut self,
18721 _: &ToggleLineNumbers,
18722 _: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 let mut editor_settings = EditorSettings::get_global(cx).clone();
18726 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18727 EditorSettings::override_global(editor_settings, cx);
18728 }
18729
18730 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18731 if let Some(show_line_numbers) = self.show_line_numbers {
18732 return show_line_numbers;
18733 }
18734 EditorSettings::get_global(cx).gutter.line_numbers
18735 }
18736
18737 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18738 self.use_relative_line_numbers
18739 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18740 }
18741
18742 pub fn toggle_relative_line_numbers(
18743 &mut self,
18744 _: &ToggleRelativeLineNumbers,
18745 _: &mut Window,
18746 cx: &mut Context<Self>,
18747 ) {
18748 let is_relative = self.should_use_relative_line_numbers(cx);
18749 self.set_relative_line_number(Some(!is_relative), cx)
18750 }
18751
18752 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18753 self.use_relative_line_numbers = is_relative;
18754 cx.notify();
18755 }
18756
18757 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18758 self.show_gutter = show_gutter;
18759 cx.notify();
18760 }
18761
18762 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18763 self.show_scrollbars = ScrollbarAxes {
18764 horizontal: show,
18765 vertical: show,
18766 };
18767 cx.notify();
18768 }
18769
18770 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18771 self.show_scrollbars.vertical = show;
18772 cx.notify();
18773 }
18774
18775 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18776 self.show_scrollbars.horizontal = show;
18777 cx.notify();
18778 }
18779
18780 pub fn set_minimap_visibility(
18781 &mut self,
18782 minimap_visibility: MinimapVisibility,
18783 window: &mut Window,
18784 cx: &mut Context<Self>,
18785 ) {
18786 if self.minimap_visibility != minimap_visibility {
18787 if minimap_visibility.visible() && self.minimap.is_none() {
18788 let minimap_settings = EditorSettings::get_global(cx).minimap;
18789 self.minimap =
18790 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18791 }
18792 self.minimap_visibility = minimap_visibility;
18793 cx.notify();
18794 }
18795 }
18796
18797 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18798 self.set_show_scrollbars(false, cx);
18799 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18800 }
18801
18802 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18803 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18804 }
18805
18806 /// Normally the text in full mode and auto height editors is padded on the
18807 /// left side by roughly half a character width for improved hit testing.
18808 ///
18809 /// Use this method to disable this for cases where this is not wanted (e.g.
18810 /// if you want to align the editor text with some other text above or below)
18811 /// or if you want to add this padding to single-line editors.
18812 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18813 self.offset_content = offset_content;
18814 cx.notify();
18815 }
18816
18817 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18818 self.show_line_numbers = Some(show_line_numbers);
18819 cx.notify();
18820 }
18821
18822 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18823 self.disable_expand_excerpt_buttons = true;
18824 cx.notify();
18825 }
18826
18827 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18828 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18829 cx.notify();
18830 }
18831
18832 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18833 self.show_code_actions = Some(show_code_actions);
18834 cx.notify();
18835 }
18836
18837 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18838 self.show_runnables = Some(show_runnables);
18839 cx.notify();
18840 }
18841
18842 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18843 self.show_breakpoints = Some(show_breakpoints);
18844 cx.notify();
18845 }
18846
18847 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18848 if self.display_map.read(cx).masked != masked {
18849 self.display_map.update(cx, |map, _| map.masked = masked);
18850 }
18851 cx.notify()
18852 }
18853
18854 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18855 self.show_wrap_guides = Some(show_wrap_guides);
18856 cx.notify();
18857 }
18858
18859 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18860 self.show_indent_guides = Some(show_indent_guides);
18861 cx.notify();
18862 }
18863
18864 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18865 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18866 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18867 && let Some(dir) = file.abs_path(cx).parent()
18868 {
18869 return Some(dir.to_owned());
18870 }
18871
18872 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18873 return Some(project_path.path.to_path_buf());
18874 }
18875 }
18876
18877 None
18878 }
18879
18880 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18881 self.active_excerpt(cx)?
18882 .1
18883 .read(cx)
18884 .file()
18885 .and_then(|f| f.as_local())
18886 }
18887
18888 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18889 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18890 let buffer = buffer.read(cx);
18891 if let Some(project_path) = buffer.project_path(cx) {
18892 let project = self.project()?.read(cx);
18893 project.absolute_path(&project_path, cx)
18894 } else {
18895 buffer
18896 .file()
18897 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18898 }
18899 })
18900 }
18901
18902 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18903 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18904 let project_path = buffer.read(cx).project_path(cx)?;
18905 let project = self.project()?.read(cx);
18906 let entry = project.entry_for_path(&project_path, cx)?;
18907 let path = entry.path.to_path_buf();
18908 Some(path)
18909 })
18910 }
18911
18912 pub fn reveal_in_finder(
18913 &mut self,
18914 _: &RevealInFileManager,
18915 _window: &mut Window,
18916 cx: &mut Context<Self>,
18917 ) {
18918 if let Some(target) = self.target_file(cx) {
18919 cx.reveal_path(&target.abs_path(cx));
18920 }
18921 }
18922
18923 pub fn copy_path(
18924 &mut self,
18925 _: &zed_actions::workspace::CopyPath,
18926 _window: &mut Window,
18927 cx: &mut Context<Self>,
18928 ) {
18929 if let Some(path) = self.target_file_abs_path(cx)
18930 && let Some(path) = path.to_str()
18931 {
18932 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18933 }
18934 }
18935
18936 pub fn copy_relative_path(
18937 &mut self,
18938 _: &zed_actions::workspace::CopyRelativePath,
18939 _window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 if let Some(path) = self.target_file_path(cx)
18943 && let Some(path) = path.to_str()
18944 {
18945 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18946 }
18947 }
18948
18949 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18950 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18951 buffer.read(cx).project_path(cx)
18952 } else {
18953 None
18954 }
18955 }
18956
18957 // Returns true if the editor handled a go-to-line request
18958 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18959 maybe!({
18960 let breakpoint_store = self.breakpoint_store.as_ref()?;
18961
18962 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18963 else {
18964 self.clear_row_highlights::<ActiveDebugLine>();
18965 return None;
18966 };
18967
18968 let position = active_stack_frame.position;
18969 let buffer_id = position.buffer_id?;
18970 let snapshot = self
18971 .project
18972 .as_ref()?
18973 .read(cx)
18974 .buffer_for_id(buffer_id, cx)?
18975 .read(cx)
18976 .snapshot();
18977
18978 let mut handled = false;
18979 for (id, ExcerptRange { context, .. }) in
18980 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18981 {
18982 if context.start.cmp(&position, &snapshot).is_ge()
18983 || context.end.cmp(&position, &snapshot).is_lt()
18984 {
18985 continue;
18986 }
18987 let snapshot = self.buffer.read(cx).snapshot(cx);
18988 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18989
18990 handled = true;
18991 self.clear_row_highlights::<ActiveDebugLine>();
18992
18993 self.go_to_line::<ActiveDebugLine>(
18994 multibuffer_anchor,
18995 Some(cx.theme().colors().editor_debugger_active_line_background),
18996 window,
18997 cx,
18998 );
18999
19000 cx.notify();
19001 }
19002
19003 handled.then_some(())
19004 })
19005 .is_some()
19006 }
19007
19008 pub fn copy_file_name_without_extension(
19009 &mut self,
19010 _: &CopyFileNameWithoutExtension,
19011 _: &mut Window,
19012 cx: &mut Context<Self>,
19013 ) {
19014 if let Some(file) = self.target_file(cx)
19015 && let Some(file_stem) = file.path().file_stem()
19016 && let Some(name) = file_stem.to_str()
19017 {
19018 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19019 }
19020 }
19021
19022 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19023 if let Some(file) = self.target_file(cx)
19024 && let Some(file_name) = file.path().file_name()
19025 && let Some(name) = file_name.to_str()
19026 {
19027 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19028 }
19029 }
19030
19031 pub fn toggle_git_blame(
19032 &mut self,
19033 _: &::git::Blame,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19038
19039 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19040 self.start_git_blame(true, window, cx);
19041 }
19042
19043 cx.notify();
19044 }
19045
19046 pub fn toggle_git_blame_inline(
19047 &mut self,
19048 _: &ToggleGitBlameInline,
19049 window: &mut Window,
19050 cx: &mut Context<Self>,
19051 ) {
19052 self.toggle_git_blame_inline_internal(true, window, cx);
19053 cx.notify();
19054 }
19055
19056 pub fn open_git_blame_commit(
19057 &mut self,
19058 _: &OpenGitBlameCommit,
19059 window: &mut Window,
19060 cx: &mut Context<Self>,
19061 ) {
19062 self.open_git_blame_commit_internal(window, cx);
19063 }
19064
19065 fn open_git_blame_commit_internal(
19066 &mut self,
19067 window: &mut Window,
19068 cx: &mut Context<Self>,
19069 ) -> Option<()> {
19070 let blame = self.blame.as_ref()?;
19071 let snapshot = self.snapshot(window, cx);
19072 let cursor = self.selections.newest::<Point>(cx).head();
19073 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19074 let blame_entry = blame
19075 .update(cx, |blame, cx| {
19076 blame
19077 .blame_for_rows(
19078 &[RowInfo {
19079 buffer_id: Some(buffer.remote_id()),
19080 buffer_row: Some(point.row),
19081 ..Default::default()
19082 }],
19083 cx,
19084 )
19085 .next()
19086 })
19087 .flatten()?;
19088 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19089 let repo = blame.read(cx).repository(cx)?;
19090 let workspace = self.workspace()?.downgrade();
19091 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19092 None
19093 }
19094
19095 pub fn git_blame_inline_enabled(&self) -> bool {
19096 self.git_blame_inline_enabled
19097 }
19098
19099 pub fn toggle_selection_menu(
19100 &mut self,
19101 _: &ToggleSelectionMenu,
19102 _: &mut Window,
19103 cx: &mut Context<Self>,
19104 ) {
19105 self.show_selection_menu = self
19106 .show_selection_menu
19107 .map(|show_selections_menu| !show_selections_menu)
19108 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19109
19110 cx.notify();
19111 }
19112
19113 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19114 self.show_selection_menu
19115 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19116 }
19117
19118 fn start_git_blame(
19119 &mut self,
19120 user_triggered: bool,
19121 window: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) {
19124 if let Some(project) = self.project() {
19125 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
19126 return;
19127 };
19128
19129 if buffer.read(cx).file().is_none() {
19130 return;
19131 }
19132
19133 let focused = self.focus_handle(cx).contains_focused(window, cx);
19134
19135 let project = project.clone();
19136 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
19137 self.blame_subscription =
19138 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19139 self.blame = Some(blame);
19140 }
19141 }
19142
19143 fn toggle_git_blame_inline_internal(
19144 &mut self,
19145 user_triggered: bool,
19146 window: &mut Window,
19147 cx: &mut Context<Self>,
19148 ) {
19149 if self.git_blame_inline_enabled {
19150 self.git_blame_inline_enabled = false;
19151 self.show_git_blame_inline = false;
19152 self.show_git_blame_inline_delay_task.take();
19153 } else {
19154 self.git_blame_inline_enabled = true;
19155 self.start_git_blame_inline(user_triggered, window, cx);
19156 }
19157
19158 cx.notify();
19159 }
19160
19161 fn start_git_blame_inline(
19162 &mut self,
19163 user_triggered: bool,
19164 window: &mut Window,
19165 cx: &mut Context<Self>,
19166 ) {
19167 self.start_git_blame(user_triggered, window, cx);
19168
19169 if ProjectSettings::get_global(cx)
19170 .git
19171 .inline_blame_delay()
19172 .is_some()
19173 {
19174 self.start_inline_blame_timer(window, cx);
19175 } else {
19176 self.show_git_blame_inline = true
19177 }
19178 }
19179
19180 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19181 self.blame.as_ref()
19182 }
19183
19184 pub fn show_git_blame_gutter(&self) -> bool {
19185 self.show_git_blame_gutter
19186 }
19187
19188 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19189 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19190 }
19191
19192 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19193 self.show_git_blame_inline
19194 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19195 && !self.newest_selection_head_on_empty_line(cx)
19196 && self.has_blame_entries(cx)
19197 }
19198
19199 fn has_blame_entries(&self, cx: &App) -> bool {
19200 self.blame()
19201 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19202 }
19203
19204 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19205 let cursor_anchor = self.selections.newest_anchor().head();
19206
19207 let snapshot = self.buffer.read(cx).snapshot(cx);
19208 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19209
19210 snapshot.line_len(buffer_row) == 0
19211 }
19212
19213 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19214 let buffer_and_selection = maybe!({
19215 let selection = self.selections.newest::<Point>(cx);
19216 let selection_range = selection.range();
19217
19218 let multi_buffer = self.buffer().read(cx);
19219 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19220 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19221
19222 let (buffer, range, _) = if selection.reversed {
19223 buffer_ranges.first()
19224 } else {
19225 buffer_ranges.last()
19226 }?;
19227
19228 let selection = text::ToPoint::to_point(&range.start, buffer).row
19229 ..text::ToPoint::to_point(&range.end, buffer).row;
19230 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19231 });
19232
19233 let Some((buffer, selection)) = buffer_and_selection else {
19234 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19235 };
19236
19237 let Some(project) = self.project() else {
19238 return Task::ready(Err(anyhow!("editor does not have project")));
19239 };
19240
19241 project.update(cx, |project, cx| {
19242 project.get_permalink_to_line(&buffer, selection, cx)
19243 })
19244 }
19245
19246 pub fn copy_permalink_to_line(
19247 &mut self,
19248 _: &CopyPermalinkToLine,
19249 window: &mut Window,
19250 cx: &mut Context<Self>,
19251 ) {
19252 let permalink_task = self.get_permalink_to_line(cx);
19253 let workspace = self.workspace();
19254
19255 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19256 Ok(permalink) => {
19257 cx.update(|_, cx| {
19258 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19259 })
19260 .ok();
19261 }
19262 Err(err) => {
19263 let message = format!("Failed to copy permalink: {err}");
19264
19265 anyhow::Result::<()>::Err(err).log_err();
19266
19267 if let Some(workspace) = workspace {
19268 workspace
19269 .update_in(cx, |workspace, _, cx| {
19270 struct CopyPermalinkToLine;
19271
19272 workspace.show_toast(
19273 Toast::new(
19274 NotificationId::unique::<CopyPermalinkToLine>(),
19275 message,
19276 ),
19277 cx,
19278 )
19279 })
19280 .ok();
19281 }
19282 }
19283 })
19284 .detach();
19285 }
19286
19287 pub fn copy_file_location(
19288 &mut self,
19289 _: &CopyFileLocation,
19290 _: &mut Window,
19291 cx: &mut Context<Self>,
19292 ) {
19293 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19294 if let Some(file) = self.target_file(cx)
19295 && let Some(path) = file.path().to_str()
19296 {
19297 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19298 }
19299 }
19300
19301 pub fn open_permalink_to_line(
19302 &mut self,
19303 _: &OpenPermalinkToLine,
19304 window: &mut Window,
19305 cx: &mut Context<Self>,
19306 ) {
19307 let permalink_task = self.get_permalink_to_line(cx);
19308 let workspace = self.workspace();
19309
19310 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19311 Ok(permalink) => {
19312 cx.update(|_, cx| {
19313 cx.open_url(permalink.as_ref());
19314 })
19315 .ok();
19316 }
19317 Err(err) => {
19318 let message = format!("Failed to open permalink: {err}");
19319
19320 anyhow::Result::<()>::Err(err).log_err();
19321
19322 if let Some(workspace) = workspace {
19323 workspace
19324 .update(cx, |workspace, cx| {
19325 struct OpenPermalinkToLine;
19326
19327 workspace.show_toast(
19328 Toast::new(
19329 NotificationId::unique::<OpenPermalinkToLine>(),
19330 message,
19331 ),
19332 cx,
19333 )
19334 })
19335 .ok();
19336 }
19337 }
19338 })
19339 .detach();
19340 }
19341
19342 pub fn insert_uuid_v4(
19343 &mut self,
19344 _: &InsertUuidV4,
19345 window: &mut Window,
19346 cx: &mut Context<Self>,
19347 ) {
19348 self.insert_uuid(UuidVersion::V4, window, cx);
19349 }
19350
19351 pub fn insert_uuid_v7(
19352 &mut self,
19353 _: &InsertUuidV7,
19354 window: &mut Window,
19355 cx: &mut Context<Self>,
19356 ) {
19357 self.insert_uuid(UuidVersion::V7, window, cx);
19358 }
19359
19360 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19362 self.transact(window, cx, |this, window, cx| {
19363 let edits = this
19364 .selections
19365 .all::<Point>(cx)
19366 .into_iter()
19367 .map(|selection| {
19368 let uuid = match version {
19369 UuidVersion::V4 => uuid::Uuid::new_v4(),
19370 UuidVersion::V7 => uuid::Uuid::now_v7(),
19371 };
19372
19373 (selection.range(), uuid.to_string())
19374 });
19375 this.edit(edits, cx);
19376 this.refresh_edit_prediction(true, false, window, cx);
19377 });
19378 }
19379
19380 pub fn open_selections_in_multibuffer(
19381 &mut self,
19382 _: &OpenSelectionsInMultibuffer,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 let multibuffer = self.buffer.read(cx);
19387
19388 let Some(buffer) = multibuffer.as_singleton() else {
19389 return;
19390 };
19391
19392 let Some(workspace) = self.workspace() else {
19393 return;
19394 };
19395
19396 let title = multibuffer.title(cx).to_string();
19397
19398 let locations = self
19399 .selections
19400 .all_anchors(cx)
19401 .iter()
19402 .map(|selection| Location {
19403 buffer: buffer.clone(),
19404 range: selection.start.text_anchor..selection.end.text_anchor,
19405 })
19406 .collect::<Vec<_>>();
19407
19408 cx.spawn_in(window, async move |_, cx| {
19409 workspace.update_in(cx, |workspace, window, cx| {
19410 Self::open_locations_in_multibuffer(
19411 workspace,
19412 locations,
19413 format!("Selections for '{title}'"),
19414 false,
19415 MultibufferSelectionMode::All,
19416 window,
19417 cx,
19418 );
19419 })
19420 })
19421 .detach();
19422 }
19423
19424 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19425 /// last highlight added will be used.
19426 ///
19427 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19428 pub fn highlight_rows<T: 'static>(
19429 &mut self,
19430 range: Range<Anchor>,
19431 color: Hsla,
19432 options: RowHighlightOptions,
19433 cx: &mut Context<Self>,
19434 ) {
19435 let snapshot = self.buffer().read(cx).snapshot(cx);
19436 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19437 let ix = row_highlights.binary_search_by(|highlight| {
19438 Ordering::Equal
19439 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19440 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19441 });
19442
19443 if let Err(mut ix) = ix {
19444 let index = post_inc(&mut self.highlight_order);
19445
19446 // If this range intersects with the preceding highlight, then merge it with
19447 // the preceding highlight. Otherwise insert a new highlight.
19448 let mut merged = false;
19449 if ix > 0 {
19450 let prev_highlight = &mut row_highlights[ix - 1];
19451 if prev_highlight
19452 .range
19453 .end
19454 .cmp(&range.start, &snapshot)
19455 .is_ge()
19456 {
19457 ix -= 1;
19458 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19459 prev_highlight.range.end = range.end;
19460 }
19461 merged = true;
19462 prev_highlight.index = index;
19463 prev_highlight.color = color;
19464 prev_highlight.options = options;
19465 }
19466 }
19467
19468 if !merged {
19469 row_highlights.insert(
19470 ix,
19471 RowHighlight {
19472 range,
19473 index,
19474 color,
19475 options,
19476 type_id: TypeId::of::<T>(),
19477 },
19478 );
19479 }
19480
19481 // If any of the following highlights intersect with this one, merge them.
19482 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19483 let highlight = &row_highlights[ix];
19484 if next_highlight
19485 .range
19486 .start
19487 .cmp(&highlight.range.end, &snapshot)
19488 .is_le()
19489 {
19490 if next_highlight
19491 .range
19492 .end
19493 .cmp(&highlight.range.end, &snapshot)
19494 .is_gt()
19495 {
19496 row_highlights[ix].range.end = next_highlight.range.end;
19497 }
19498 row_highlights.remove(ix + 1);
19499 } else {
19500 break;
19501 }
19502 }
19503 }
19504 }
19505
19506 /// Remove any highlighted row ranges of the given type that intersect the
19507 /// given ranges.
19508 pub fn remove_highlighted_rows<T: 'static>(
19509 &mut self,
19510 ranges_to_remove: Vec<Range<Anchor>>,
19511 cx: &mut Context<Self>,
19512 ) {
19513 let snapshot = self.buffer().read(cx).snapshot(cx);
19514 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19515 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19516 row_highlights.retain(|highlight| {
19517 while let Some(range_to_remove) = ranges_to_remove.peek() {
19518 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19519 Ordering::Less | Ordering::Equal => {
19520 ranges_to_remove.next();
19521 }
19522 Ordering::Greater => {
19523 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19524 Ordering::Less | Ordering::Equal => {
19525 return false;
19526 }
19527 Ordering::Greater => break,
19528 }
19529 }
19530 }
19531 }
19532
19533 true
19534 })
19535 }
19536
19537 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19538 pub fn clear_row_highlights<T: 'static>(&mut self) {
19539 self.highlighted_rows.remove(&TypeId::of::<T>());
19540 }
19541
19542 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19543 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19544 self.highlighted_rows
19545 .get(&TypeId::of::<T>())
19546 .map_or(&[] as &[_], |vec| vec.as_slice())
19547 .iter()
19548 .map(|highlight| (highlight.range.clone(), highlight.color))
19549 }
19550
19551 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19552 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19553 /// Allows to ignore certain kinds of highlights.
19554 pub fn highlighted_display_rows(
19555 &self,
19556 window: &mut Window,
19557 cx: &mut App,
19558 ) -> BTreeMap<DisplayRow, LineHighlight> {
19559 let snapshot = self.snapshot(window, cx);
19560 let mut used_highlight_orders = HashMap::default();
19561 self.highlighted_rows
19562 .iter()
19563 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19564 .fold(
19565 BTreeMap::<DisplayRow, LineHighlight>::new(),
19566 |mut unique_rows, highlight| {
19567 let start = highlight.range.start.to_display_point(&snapshot);
19568 let end = highlight.range.end.to_display_point(&snapshot);
19569 let start_row = start.row().0;
19570 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19571 && end.column() == 0
19572 {
19573 end.row().0.saturating_sub(1)
19574 } else {
19575 end.row().0
19576 };
19577 for row in start_row..=end_row {
19578 let used_index =
19579 used_highlight_orders.entry(row).or_insert(highlight.index);
19580 if highlight.index >= *used_index {
19581 *used_index = highlight.index;
19582 unique_rows.insert(
19583 DisplayRow(row),
19584 LineHighlight {
19585 include_gutter: highlight.options.include_gutter,
19586 border: None,
19587 background: highlight.color.into(),
19588 type_id: Some(highlight.type_id),
19589 },
19590 );
19591 }
19592 }
19593 unique_rows
19594 },
19595 )
19596 }
19597
19598 pub fn highlighted_display_row_for_autoscroll(
19599 &self,
19600 snapshot: &DisplaySnapshot,
19601 ) -> Option<DisplayRow> {
19602 self.highlighted_rows
19603 .values()
19604 .flat_map(|highlighted_rows| highlighted_rows.iter())
19605 .filter_map(|highlight| {
19606 if highlight.options.autoscroll {
19607 Some(highlight.range.start.to_display_point(snapshot).row())
19608 } else {
19609 None
19610 }
19611 })
19612 .min()
19613 }
19614
19615 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19616 self.highlight_background::<SearchWithinRange>(
19617 ranges,
19618 |colors| colors.colors().editor_document_highlight_read_background,
19619 cx,
19620 )
19621 }
19622
19623 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19624 self.breadcrumb_header = Some(new_header);
19625 }
19626
19627 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19628 self.clear_background_highlights::<SearchWithinRange>(cx);
19629 }
19630
19631 pub fn highlight_background<T: 'static>(
19632 &mut self,
19633 ranges: &[Range<Anchor>],
19634 color_fetcher: fn(&Theme) -> Hsla,
19635 cx: &mut Context<Self>,
19636 ) {
19637 self.background_highlights.insert(
19638 HighlightKey::Type(TypeId::of::<T>()),
19639 (color_fetcher, Arc::from(ranges)),
19640 );
19641 self.scrollbar_marker_state.dirty = true;
19642 cx.notify();
19643 }
19644
19645 pub fn highlight_background_key<T: 'static>(
19646 &mut self,
19647 key: usize,
19648 ranges: &[Range<Anchor>],
19649 color_fetcher: fn(&Theme) -> Hsla,
19650 cx: &mut Context<Self>,
19651 ) {
19652 self.background_highlights.insert(
19653 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19654 (color_fetcher, Arc::from(ranges)),
19655 );
19656 self.scrollbar_marker_state.dirty = true;
19657 cx.notify();
19658 }
19659
19660 pub fn clear_background_highlights<T: 'static>(
19661 &mut self,
19662 cx: &mut Context<Self>,
19663 ) -> Option<BackgroundHighlight> {
19664 let text_highlights = self
19665 .background_highlights
19666 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19667 if !text_highlights.1.is_empty() {
19668 self.scrollbar_marker_state.dirty = true;
19669 cx.notify();
19670 }
19671 Some(text_highlights)
19672 }
19673
19674 pub fn highlight_gutter<T: 'static>(
19675 &mut self,
19676 ranges: impl Into<Vec<Range<Anchor>>>,
19677 color_fetcher: fn(&App) -> Hsla,
19678 cx: &mut Context<Self>,
19679 ) {
19680 self.gutter_highlights
19681 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19682 cx.notify();
19683 }
19684
19685 pub fn clear_gutter_highlights<T: 'static>(
19686 &mut self,
19687 cx: &mut Context<Self>,
19688 ) -> Option<GutterHighlight> {
19689 cx.notify();
19690 self.gutter_highlights.remove(&TypeId::of::<T>())
19691 }
19692
19693 pub fn insert_gutter_highlight<T: 'static>(
19694 &mut self,
19695 range: Range<Anchor>,
19696 color_fetcher: fn(&App) -> Hsla,
19697 cx: &mut Context<Self>,
19698 ) {
19699 let snapshot = self.buffer().read(cx).snapshot(cx);
19700 let mut highlights = self
19701 .gutter_highlights
19702 .remove(&TypeId::of::<T>())
19703 .map(|(_, highlights)| highlights)
19704 .unwrap_or_default();
19705 let ix = highlights.binary_search_by(|highlight| {
19706 Ordering::Equal
19707 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19708 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19709 });
19710 if let Err(ix) = ix {
19711 highlights.insert(ix, range);
19712 }
19713 self.gutter_highlights
19714 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19715 }
19716
19717 pub fn remove_gutter_highlights<T: 'static>(
19718 &mut self,
19719 ranges_to_remove: Vec<Range<Anchor>>,
19720 cx: &mut Context<Self>,
19721 ) {
19722 let snapshot = self.buffer().read(cx).snapshot(cx);
19723 let Some((color_fetcher, mut gutter_highlights)) =
19724 self.gutter_highlights.remove(&TypeId::of::<T>())
19725 else {
19726 return;
19727 };
19728 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19729 gutter_highlights.retain(|highlight| {
19730 while let Some(range_to_remove) = ranges_to_remove.peek() {
19731 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19732 Ordering::Less | Ordering::Equal => {
19733 ranges_to_remove.next();
19734 }
19735 Ordering::Greater => {
19736 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19737 Ordering::Less | Ordering::Equal => {
19738 return false;
19739 }
19740 Ordering::Greater => break,
19741 }
19742 }
19743 }
19744 }
19745
19746 true
19747 });
19748 self.gutter_highlights
19749 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19750 }
19751
19752 #[cfg(feature = "test-support")]
19753 pub fn all_text_highlights(
19754 &self,
19755 window: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19758 let snapshot = self.snapshot(window, cx);
19759 self.display_map.update(cx, |display_map, _| {
19760 display_map
19761 .all_text_highlights()
19762 .map(|highlight| {
19763 let (style, ranges) = highlight.as_ref();
19764 (
19765 *style,
19766 ranges
19767 .iter()
19768 .map(|range| range.clone().to_display_points(&snapshot))
19769 .collect(),
19770 )
19771 })
19772 .collect()
19773 })
19774 }
19775
19776 #[cfg(feature = "test-support")]
19777 pub fn all_text_background_highlights(
19778 &self,
19779 window: &mut Window,
19780 cx: &mut Context<Self>,
19781 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19782 let snapshot = self.snapshot(window, cx);
19783 let buffer = &snapshot.buffer_snapshot;
19784 let start = buffer.anchor_before(0);
19785 let end = buffer.anchor_after(buffer.len());
19786 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19787 }
19788
19789 #[cfg(feature = "test-support")]
19790 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19791 let snapshot = self.buffer().read(cx).snapshot(cx);
19792
19793 let highlights = self
19794 .background_highlights
19795 .get(&HighlightKey::Type(TypeId::of::<
19796 items::BufferSearchHighlights,
19797 >()));
19798
19799 if let Some((_color, ranges)) = highlights {
19800 ranges
19801 .iter()
19802 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19803 .collect_vec()
19804 } else {
19805 vec![]
19806 }
19807 }
19808
19809 fn document_highlights_for_position<'a>(
19810 &'a self,
19811 position: Anchor,
19812 buffer: &'a MultiBufferSnapshot,
19813 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19814 let read_highlights = self
19815 .background_highlights
19816 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19817 .map(|h| &h.1);
19818 let write_highlights = self
19819 .background_highlights
19820 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19821 .map(|h| &h.1);
19822 let left_position = position.bias_left(buffer);
19823 let right_position = position.bias_right(buffer);
19824 read_highlights
19825 .into_iter()
19826 .chain(write_highlights)
19827 .flat_map(move |ranges| {
19828 let start_ix = match ranges.binary_search_by(|probe| {
19829 let cmp = probe.end.cmp(&left_position, buffer);
19830 if cmp.is_ge() {
19831 Ordering::Greater
19832 } else {
19833 Ordering::Less
19834 }
19835 }) {
19836 Ok(i) | Err(i) => i,
19837 };
19838
19839 ranges[start_ix..]
19840 .iter()
19841 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19842 })
19843 }
19844
19845 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19846 self.background_highlights
19847 .get(&HighlightKey::Type(TypeId::of::<T>()))
19848 .is_some_and(|(_, highlights)| !highlights.is_empty())
19849 }
19850
19851 pub fn background_highlights_in_range(
19852 &self,
19853 search_range: Range<Anchor>,
19854 display_snapshot: &DisplaySnapshot,
19855 theme: &Theme,
19856 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19857 let mut results = Vec::new();
19858 for (color_fetcher, ranges) in self.background_highlights.values() {
19859 let color = color_fetcher(theme);
19860 let start_ix = match ranges.binary_search_by(|probe| {
19861 let cmp = probe
19862 .end
19863 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19864 if cmp.is_gt() {
19865 Ordering::Greater
19866 } else {
19867 Ordering::Less
19868 }
19869 }) {
19870 Ok(i) | Err(i) => i,
19871 };
19872 for range in &ranges[start_ix..] {
19873 if range
19874 .start
19875 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19876 .is_ge()
19877 {
19878 break;
19879 }
19880
19881 let start = range.start.to_display_point(display_snapshot);
19882 let end = range.end.to_display_point(display_snapshot);
19883 results.push((start..end, color))
19884 }
19885 }
19886 results
19887 }
19888
19889 pub fn background_highlight_row_ranges<T: 'static>(
19890 &self,
19891 search_range: Range<Anchor>,
19892 display_snapshot: &DisplaySnapshot,
19893 count: usize,
19894 ) -> Vec<RangeInclusive<DisplayPoint>> {
19895 let mut results = Vec::new();
19896 let Some((_, ranges)) = self
19897 .background_highlights
19898 .get(&HighlightKey::Type(TypeId::of::<T>()))
19899 else {
19900 return vec![];
19901 };
19902
19903 let start_ix = match ranges.binary_search_by(|probe| {
19904 let cmp = probe
19905 .end
19906 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19907 if cmp.is_gt() {
19908 Ordering::Greater
19909 } else {
19910 Ordering::Less
19911 }
19912 }) {
19913 Ok(i) | Err(i) => i,
19914 };
19915 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19916 if let (Some(start_display), Some(end_display)) = (start, end) {
19917 results.push(
19918 start_display.to_display_point(display_snapshot)
19919 ..=end_display.to_display_point(display_snapshot),
19920 );
19921 }
19922 };
19923 let mut start_row: Option<Point> = None;
19924 let mut end_row: Option<Point> = None;
19925 if ranges.len() > count {
19926 return Vec::new();
19927 }
19928 for range in &ranges[start_ix..] {
19929 if range
19930 .start
19931 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19932 .is_ge()
19933 {
19934 break;
19935 }
19936 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19937 if let Some(current_row) = &end_row
19938 && end.row == current_row.row
19939 {
19940 continue;
19941 }
19942 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19943 if start_row.is_none() {
19944 assert_eq!(end_row, None);
19945 start_row = Some(start);
19946 end_row = Some(end);
19947 continue;
19948 }
19949 if let Some(current_end) = end_row.as_mut() {
19950 if start.row > current_end.row + 1 {
19951 push_region(start_row, end_row);
19952 start_row = Some(start);
19953 end_row = Some(end);
19954 } else {
19955 // Merge two hunks.
19956 *current_end = end;
19957 }
19958 } else {
19959 unreachable!();
19960 }
19961 }
19962 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19963 push_region(start_row, end_row);
19964 results
19965 }
19966
19967 pub fn gutter_highlights_in_range(
19968 &self,
19969 search_range: Range<Anchor>,
19970 display_snapshot: &DisplaySnapshot,
19971 cx: &App,
19972 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19973 let mut results = Vec::new();
19974 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19975 let color = color_fetcher(cx);
19976 let start_ix = match ranges.binary_search_by(|probe| {
19977 let cmp = probe
19978 .end
19979 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19980 if cmp.is_gt() {
19981 Ordering::Greater
19982 } else {
19983 Ordering::Less
19984 }
19985 }) {
19986 Ok(i) | Err(i) => i,
19987 };
19988 for range in &ranges[start_ix..] {
19989 if range
19990 .start
19991 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19992 .is_ge()
19993 {
19994 break;
19995 }
19996
19997 let start = range.start.to_display_point(display_snapshot);
19998 let end = range.end.to_display_point(display_snapshot);
19999 results.push((start..end, color))
20000 }
20001 }
20002 results
20003 }
20004
20005 /// Get the text ranges corresponding to the redaction query
20006 pub fn redacted_ranges(
20007 &self,
20008 search_range: Range<Anchor>,
20009 display_snapshot: &DisplaySnapshot,
20010 cx: &App,
20011 ) -> Vec<Range<DisplayPoint>> {
20012 display_snapshot
20013 .buffer_snapshot
20014 .redacted_ranges(search_range, |file| {
20015 if let Some(file) = file {
20016 file.is_private()
20017 && EditorSettings::get(
20018 Some(SettingsLocation {
20019 worktree_id: file.worktree_id(cx),
20020 path: file.path().as_ref(),
20021 }),
20022 cx,
20023 )
20024 .redact_private_values
20025 } else {
20026 false
20027 }
20028 })
20029 .map(|range| {
20030 range.start.to_display_point(display_snapshot)
20031 ..range.end.to_display_point(display_snapshot)
20032 })
20033 .collect()
20034 }
20035
20036 pub fn highlight_text_key<T: 'static>(
20037 &mut self,
20038 key: usize,
20039 ranges: Vec<Range<Anchor>>,
20040 style: HighlightStyle,
20041 cx: &mut Context<Self>,
20042 ) {
20043 self.display_map.update(cx, |map, _| {
20044 map.highlight_text(
20045 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20046 ranges,
20047 style,
20048 );
20049 });
20050 cx.notify();
20051 }
20052
20053 pub fn highlight_text<T: 'static>(
20054 &mut self,
20055 ranges: Vec<Range<Anchor>>,
20056 style: HighlightStyle,
20057 cx: &mut Context<Self>,
20058 ) {
20059 self.display_map.update(cx, |map, _| {
20060 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20061 });
20062 cx.notify();
20063 }
20064
20065 pub(crate) fn highlight_inlays<T: 'static>(
20066 &mut self,
20067 highlights: Vec<InlayHighlight>,
20068 style: HighlightStyle,
20069 cx: &mut Context<Self>,
20070 ) {
20071 self.display_map.update(cx, |map, _| {
20072 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20073 });
20074 cx.notify();
20075 }
20076
20077 pub fn text_highlights<'a, T: 'static>(
20078 &'a self,
20079 cx: &'a App,
20080 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20081 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20082 }
20083
20084 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20085 let cleared = self
20086 .display_map
20087 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20088 if cleared {
20089 cx.notify();
20090 }
20091 }
20092
20093 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20094 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20095 && self.focus_handle.is_focused(window)
20096 }
20097
20098 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20099 self.show_cursor_when_unfocused = is_enabled;
20100 cx.notify();
20101 }
20102
20103 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20104 cx.notify();
20105 }
20106
20107 fn on_debug_session_event(
20108 &mut self,
20109 _session: Entity<Session>,
20110 event: &SessionEvent,
20111 cx: &mut Context<Self>,
20112 ) {
20113 if let SessionEvent::InvalidateInlineValue = event {
20114 self.refresh_inline_values(cx);
20115 }
20116 }
20117
20118 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20119 let Some(project) = self.project.clone() else {
20120 return;
20121 };
20122
20123 if !self.inline_value_cache.enabled {
20124 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20125 self.splice_inlays(&inlays, Vec::new(), cx);
20126 return;
20127 }
20128
20129 let current_execution_position = self
20130 .highlighted_rows
20131 .get(&TypeId::of::<ActiveDebugLine>())
20132 .and_then(|lines| lines.last().map(|line| line.range.end));
20133
20134 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20135 let inline_values = editor
20136 .update(cx, |editor, cx| {
20137 let Some(current_execution_position) = current_execution_position else {
20138 return Some(Task::ready(Ok(Vec::new())));
20139 };
20140
20141 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20142 let snapshot = buffer.snapshot(cx);
20143
20144 let excerpt = snapshot.excerpt_containing(
20145 current_execution_position..current_execution_position,
20146 )?;
20147
20148 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20149 })?;
20150
20151 let range =
20152 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20153
20154 project.inline_values(buffer, range, cx)
20155 })
20156 .ok()
20157 .flatten()?
20158 .await
20159 .context("refreshing debugger inlays")
20160 .log_err()?;
20161
20162 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20163
20164 for (buffer_id, inline_value) in inline_values
20165 .into_iter()
20166 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20167 {
20168 buffer_inline_values
20169 .entry(buffer_id)
20170 .or_default()
20171 .push(inline_value);
20172 }
20173
20174 editor
20175 .update(cx, |editor, cx| {
20176 let snapshot = editor.buffer.read(cx).snapshot(cx);
20177 let mut new_inlays = Vec::default();
20178
20179 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20180 let buffer_id = buffer_snapshot.remote_id();
20181 buffer_inline_values
20182 .get(&buffer_id)
20183 .into_iter()
20184 .flatten()
20185 .for_each(|hint| {
20186 let inlay = Inlay::debugger(
20187 post_inc(&mut editor.next_inlay_id),
20188 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20189 hint.text(),
20190 );
20191 if !inlay.text.chars().contains(&'\n') {
20192 new_inlays.push(inlay);
20193 }
20194 });
20195 }
20196
20197 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20198 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20199
20200 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20201 })
20202 .ok()?;
20203 Some(())
20204 });
20205 }
20206
20207 fn on_buffer_event(
20208 &mut self,
20209 multibuffer: &Entity<MultiBuffer>,
20210 event: &multi_buffer::Event,
20211 window: &mut Window,
20212 cx: &mut Context<Self>,
20213 ) {
20214 match event {
20215 multi_buffer::Event::Edited {
20216 singleton_buffer_edited,
20217 edited_buffer,
20218 } => {
20219 self.scrollbar_marker_state.dirty = true;
20220 self.active_indent_guides_state.dirty = true;
20221 self.refresh_active_diagnostics(cx);
20222 self.refresh_code_actions(window, cx);
20223 self.refresh_selected_text_highlights(true, window, cx);
20224 self.refresh_single_line_folds(window, cx);
20225 refresh_matching_bracket_highlights(self, window, cx);
20226 if self.has_active_edit_prediction() {
20227 self.update_visible_edit_prediction(window, cx);
20228 }
20229 if let Some(project) = self.project.as_ref()
20230 && let Some(edited_buffer) = edited_buffer
20231 {
20232 project.update(cx, |project, cx| {
20233 self.registered_buffers
20234 .entry(edited_buffer.read(cx).remote_id())
20235 .or_insert_with(|| {
20236 project.register_buffer_with_language_servers(edited_buffer, cx)
20237 });
20238 });
20239 }
20240 cx.emit(EditorEvent::BufferEdited);
20241 cx.emit(SearchEvent::MatchesInvalidated);
20242
20243 if let Some(buffer) = edited_buffer {
20244 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20245 }
20246
20247 if *singleton_buffer_edited {
20248 if let Some(buffer) = edited_buffer
20249 && buffer.read(cx).file().is_none()
20250 {
20251 cx.emit(EditorEvent::TitleChanged);
20252 }
20253 if let Some(project) = &self.project {
20254 #[allow(clippy::mutable_key_type)]
20255 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20256 multibuffer
20257 .all_buffers()
20258 .into_iter()
20259 .filter_map(|buffer| {
20260 buffer.update(cx, |buffer, cx| {
20261 let language = buffer.language()?;
20262 let should_discard = project.update(cx, |project, cx| {
20263 project.is_local()
20264 && !project.has_language_servers_for(buffer, cx)
20265 });
20266 should_discard.not().then_some(language.clone())
20267 })
20268 })
20269 .collect::<HashSet<_>>()
20270 });
20271 if !languages_affected.is_empty() {
20272 self.refresh_inlay_hints(
20273 InlayHintRefreshReason::BufferEdited(languages_affected),
20274 cx,
20275 );
20276 }
20277 }
20278 }
20279
20280 let Some(project) = &self.project else { return };
20281 let (telemetry, is_via_ssh) = {
20282 let project = project.read(cx);
20283 let telemetry = project.client().telemetry().clone();
20284 let is_via_ssh = project.is_via_remote_server();
20285 (telemetry, is_via_ssh)
20286 };
20287 refresh_linked_ranges(self, window, cx);
20288 telemetry.log_edit_event("editor", is_via_ssh);
20289 }
20290 multi_buffer::Event::ExcerptsAdded {
20291 buffer,
20292 predecessor,
20293 excerpts,
20294 } => {
20295 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20296 let buffer_id = buffer.read(cx).remote_id();
20297 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20298 && let Some(project) = &self.project
20299 {
20300 update_uncommitted_diff_for_buffer(
20301 cx.entity(),
20302 project,
20303 [buffer.clone()],
20304 self.buffer.clone(),
20305 cx,
20306 )
20307 .detach();
20308 }
20309 self.update_lsp_data(false, Some(buffer_id), window, cx);
20310 cx.emit(EditorEvent::ExcerptsAdded {
20311 buffer: buffer.clone(),
20312 predecessor: *predecessor,
20313 excerpts: excerpts.clone(),
20314 });
20315 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20316 }
20317 multi_buffer::Event::ExcerptsRemoved {
20318 ids,
20319 removed_buffer_ids,
20320 } => {
20321 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20322 let buffer = self.buffer.read(cx);
20323 self.registered_buffers
20324 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20325 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20326 cx.emit(EditorEvent::ExcerptsRemoved {
20327 ids: ids.clone(),
20328 removed_buffer_ids: removed_buffer_ids.clone(),
20329 });
20330 }
20331 multi_buffer::Event::ExcerptsEdited {
20332 excerpt_ids,
20333 buffer_ids,
20334 } => {
20335 self.display_map.update(cx, |map, cx| {
20336 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20337 });
20338 cx.emit(EditorEvent::ExcerptsEdited {
20339 ids: excerpt_ids.clone(),
20340 });
20341 }
20342 multi_buffer::Event::ExcerptsExpanded { ids } => {
20343 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20344 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20345 }
20346 multi_buffer::Event::Reparsed(buffer_id) => {
20347 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20348 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20349
20350 cx.emit(EditorEvent::Reparsed(*buffer_id));
20351 }
20352 multi_buffer::Event::DiffHunksToggled => {
20353 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20354 }
20355 multi_buffer::Event::LanguageChanged(buffer_id) => {
20356 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20357 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20358 cx.emit(EditorEvent::Reparsed(*buffer_id));
20359 cx.notify();
20360 }
20361 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20362 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20363 multi_buffer::Event::FileHandleChanged
20364 | multi_buffer::Event::Reloaded
20365 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20366 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20367 multi_buffer::Event::DiagnosticsUpdated => {
20368 self.update_diagnostics_state(window, cx);
20369 }
20370 _ => {}
20371 };
20372 }
20373
20374 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20375 if !self.diagnostics_enabled() {
20376 return;
20377 }
20378 self.refresh_active_diagnostics(cx);
20379 self.refresh_inline_diagnostics(true, window, cx);
20380 self.scrollbar_marker_state.dirty = true;
20381 cx.notify();
20382 }
20383
20384 pub fn start_temporary_diff_override(&mut self) {
20385 self.load_diff_task.take();
20386 self.temporary_diff_override = true;
20387 }
20388
20389 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20390 self.temporary_diff_override = false;
20391 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20392 self.buffer.update(cx, |buffer, cx| {
20393 buffer.set_all_diff_hunks_collapsed(cx);
20394 });
20395
20396 if let Some(project) = self.project.clone() {
20397 self.load_diff_task = Some(
20398 update_uncommitted_diff_for_buffer(
20399 cx.entity(),
20400 &project,
20401 self.buffer.read(cx).all_buffers(),
20402 self.buffer.clone(),
20403 cx,
20404 )
20405 .shared(),
20406 );
20407 }
20408 }
20409
20410 fn on_display_map_changed(
20411 &mut self,
20412 _: Entity<DisplayMap>,
20413 _: &mut Window,
20414 cx: &mut Context<Self>,
20415 ) {
20416 cx.notify();
20417 }
20418
20419 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20420 if self.diagnostics_enabled() {
20421 let new_severity = EditorSettings::get_global(cx)
20422 .diagnostics_max_severity
20423 .unwrap_or(DiagnosticSeverity::Hint);
20424 self.set_max_diagnostics_severity(new_severity, cx);
20425 }
20426 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20427 self.update_edit_prediction_settings(cx);
20428 self.refresh_edit_prediction(true, false, window, cx);
20429 self.refresh_inline_values(cx);
20430 self.refresh_inlay_hints(
20431 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20432 self.selections.newest_anchor().head(),
20433 &self.buffer.read(cx).snapshot(cx),
20434 cx,
20435 )),
20436 cx,
20437 );
20438
20439 let old_cursor_shape = self.cursor_shape;
20440 let old_show_breadcrumbs = self.show_breadcrumbs;
20441
20442 {
20443 let editor_settings = EditorSettings::get_global(cx);
20444 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20445 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20446 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20447 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20448 }
20449
20450 if old_cursor_shape != self.cursor_shape {
20451 cx.emit(EditorEvent::CursorShapeChanged);
20452 }
20453
20454 if old_show_breadcrumbs != self.show_breadcrumbs {
20455 cx.emit(EditorEvent::BreadcrumbsChanged);
20456 }
20457
20458 let project_settings = ProjectSettings::get_global(cx);
20459 self.serialize_dirty_buffers =
20460 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20461
20462 if self.mode.is_full() {
20463 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20464 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20465 if self.show_inline_diagnostics != show_inline_diagnostics {
20466 self.show_inline_diagnostics = show_inline_diagnostics;
20467 self.refresh_inline_diagnostics(false, window, cx);
20468 }
20469
20470 if self.git_blame_inline_enabled != inline_blame_enabled {
20471 self.toggle_git_blame_inline_internal(false, window, cx);
20472 }
20473
20474 let minimap_settings = EditorSettings::get_global(cx).minimap;
20475 if self.minimap_visibility != MinimapVisibility::Disabled {
20476 if self.minimap_visibility.settings_visibility()
20477 != minimap_settings.minimap_enabled()
20478 {
20479 self.set_minimap_visibility(
20480 MinimapVisibility::for_mode(self.mode(), cx),
20481 window,
20482 cx,
20483 );
20484 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20485 minimap_entity.update(cx, |minimap_editor, cx| {
20486 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20487 })
20488 }
20489 }
20490 }
20491
20492 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20493 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20494 }) {
20495 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20496 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20497 }
20498 self.refresh_colors(false, None, window, cx);
20499 }
20500
20501 cx.notify();
20502 }
20503
20504 pub fn set_searchable(&mut self, searchable: bool) {
20505 self.searchable = searchable;
20506 }
20507
20508 pub fn searchable(&self) -> bool {
20509 self.searchable
20510 }
20511
20512 fn open_proposed_changes_editor(
20513 &mut self,
20514 _: &OpenProposedChangesEditor,
20515 window: &mut Window,
20516 cx: &mut Context<Self>,
20517 ) {
20518 let Some(workspace) = self.workspace() else {
20519 cx.propagate();
20520 return;
20521 };
20522
20523 let selections = self.selections.all::<usize>(cx);
20524 let multi_buffer = self.buffer.read(cx);
20525 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20526 let mut new_selections_by_buffer = HashMap::default();
20527 for selection in selections {
20528 for (buffer, range, _) in
20529 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20530 {
20531 let mut range = range.to_point(buffer);
20532 range.start.column = 0;
20533 range.end.column = buffer.line_len(range.end.row);
20534 new_selections_by_buffer
20535 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20536 .or_insert(Vec::new())
20537 .push(range)
20538 }
20539 }
20540
20541 let proposed_changes_buffers = new_selections_by_buffer
20542 .into_iter()
20543 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20544 .collect::<Vec<_>>();
20545 let proposed_changes_editor = cx.new(|cx| {
20546 ProposedChangesEditor::new(
20547 "Proposed changes",
20548 proposed_changes_buffers,
20549 self.project.clone(),
20550 window,
20551 cx,
20552 )
20553 });
20554
20555 window.defer(cx, move |window, cx| {
20556 workspace.update(cx, |workspace, cx| {
20557 workspace.active_pane().update(cx, |pane, cx| {
20558 pane.add_item(
20559 Box::new(proposed_changes_editor),
20560 true,
20561 true,
20562 None,
20563 window,
20564 cx,
20565 );
20566 });
20567 });
20568 });
20569 }
20570
20571 pub fn open_excerpts_in_split(
20572 &mut self,
20573 _: &OpenExcerptsSplit,
20574 window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) {
20577 self.open_excerpts_common(None, true, window, cx)
20578 }
20579
20580 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20581 self.open_excerpts_common(None, false, window, cx)
20582 }
20583
20584 fn open_excerpts_common(
20585 &mut self,
20586 jump_data: Option<JumpData>,
20587 split: bool,
20588 window: &mut Window,
20589 cx: &mut Context<Self>,
20590 ) {
20591 let Some(workspace) = self.workspace() else {
20592 cx.propagate();
20593 return;
20594 };
20595
20596 if self.buffer.read(cx).is_singleton() {
20597 cx.propagate();
20598 return;
20599 }
20600
20601 let mut new_selections_by_buffer = HashMap::default();
20602 match &jump_data {
20603 Some(JumpData::MultiBufferPoint {
20604 excerpt_id,
20605 position,
20606 anchor,
20607 line_offset_from_top,
20608 }) => {
20609 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20610 if let Some(buffer) = multi_buffer_snapshot
20611 .buffer_id_for_excerpt(*excerpt_id)
20612 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20613 {
20614 let buffer_snapshot = buffer.read(cx).snapshot();
20615 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20616 language::ToPoint::to_point(anchor, &buffer_snapshot)
20617 } else {
20618 buffer_snapshot.clip_point(*position, Bias::Left)
20619 };
20620 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20621 new_selections_by_buffer.insert(
20622 buffer,
20623 (
20624 vec![jump_to_offset..jump_to_offset],
20625 Some(*line_offset_from_top),
20626 ),
20627 );
20628 }
20629 }
20630 Some(JumpData::MultiBufferRow {
20631 row,
20632 line_offset_from_top,
20633 }) => {
20634 let point = MultiBufferPoint::new(row.0, 0);
20635 if let Some((buffer, buffer_point, _)) =
20636 self.buffer.read(cx).point_to_buffer_point(point, cx)
20637 {
20638 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20639 new_selections_by_buffer
20640 .entry(buffer)
20641 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20642 .0
20643 .push(buffer_offset..buffer_offset)
20644 }
20645 }
20646 None => {
20647 let selections = self.selections.all::<usize>(cx);
20648 let multi_buffer = self.buffer.read(cx);
20649 for selection in selections {
20650 for (snapshot, range, _, anchor) in multi_buffer
20651 .snapshot(cx)
20652 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20653 {
20654 if let Some(anchor) = anchor {
20655 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20656 else {
20657 continue;
20658 };
20659 let offset = text::ToOffset::to_offset(
20660 &anchor.text_anchor,
20661 &buffer_handle.read(cx).snapshot(),
20662 );
20663 let range = offset..offset;
20664 new_selections_by_buffer
20665 .entry(buffer_handle)
20666 .or_insert((Vec::new(), None))
20667 .0
20668 .push(range)
20669 } else {
20670 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20671 else {
20672 continue;
20673 };
20674 new_selections_by_buffer
20675 .entry(buffer_handle)
20676 .or_insert((Vec::new(), None))
20677 .0
20678 .push(range)
20679 }
20680 }
20681 }
20682 }
20683 }
20684
20685 new_selections_by_buffer
20686 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20687
20688 if new_selections_by_buffer.is_empty() {
20689 return;
20690 }
20691
20692 // We defer the pane interaction because we ourselves are a workspace item
20693 // and activating a new item causes the pane to call a method on us reentrantly,
20694 // which panics if we're on the stack.
20695 window.defer(cx, move |window, cx| {
20696 workspace.update(cx, |workspace, cx| {
20697 let pane = if split {
20698 workspace.adjacent_pane(window, cx)
20699 } else {
20700 workspace.active_pane().clone()
20701 };
20702
20703 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20704 let editor = buffer
20705 .read(cx)
20706 .file()
20707 .is_none()
20708 .then(|| {
20709 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20710 // so `workspace.open_project_item` will never find them, always opening a new editor.
20711 // Instead, we try to activate the existing editor in the pane first.
20712 let (editor, pane_item_index) =
20713 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20714 let editor = item.downcast::<Editor>()?;
20715 let singleton_buffer =
20716 editor.read(cx).buffer().read(cx).as_singleton()?;
20717 if singleton_buffer == buffer {
20718 Some((editor, i))
20719 } else {
20720 None
20721 }
20722 })?;
20723 pane.update(cx, |pane, cx| {
20724 pane.activate_item(pane_item_index, true, true, window, cx)
20725 });
20726 Some(editor)
20727 })
20728 .flatten()
20729 .unwrap_or_else(|| {
20730 workspace.open_project_item::<Self>(
20731 pane.clone(),
20732 buffer,
20733 true,
20734 true,
20735 window,
20736 cx,
20737 )
20738 });
20739
20740 editor.update(cx, |editor, cx| {
20741 let autoscroll = match scroll_offset {
20742 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20743 None => Autoscroll::newest(),
20744 };
20745 let nav_history = editor.nav_history.take();
20746 editor.change_selections(
20747 SelectionEffects::scroll(autoscroll),
20748 window,
20749 cx,
20750 |s| {
20751 s.select_ranges(ranges);
20752 },
20753 );
20754 editor.nav_history = nav_history;
20755 });
20756 }
20757 })
20758 });
20759 }
20760
20761 // For now, don't allow opening excerpts in buffers that aren't backed by
20762 // regular project files.
20763 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20764 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20765 }
20766
20767 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20768 let snapshot = self.buffer.read(cx).read(cx);
20769 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20770 Some(
20771 ranges
20772 .iter()
20773 .map(move |range| {
20774 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20775 })
20776 .collect(),
20777 )
20778 }
20779
20780 fn selection_replacement_ranges(
20781 &self,
20782 range: Range<OffsetUtf16>,
20783 cx: &mut App,
20784 ) -> Vec<Range<OffsetUtf16>> {
20785 let selections = self.selections.all::<OffsetUtf16>(cx);
20786 let newest_selection = selections
20787 .iter()
20788 .max_by_key(|selection| selection.id)
20789 .unwrap();
20790 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20791 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20792 let snapshot = self.buffer.read(cx).read(cx);
20793 selections
20794 .into_iter()
20795 .map(|mut selection| {
20796 selection.start.0 =
20797 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20798 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20799 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20800 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20801 })
20802 .collect()
20803 }
20804
20805 fn report_editor_event(
20806 &self,
20807 reported_event: ReportEditorEvent,
20808 file_extension: Option<String>,
20809 cx: &App,
20810 ) {
20811 if cfg!(any(test, feature = "test-support")) {
20812 return;
20813 }
20814
20815 let Some(project) = &self.project else { return };
20816
20817 // If None, we are in a file without an extension
20818 let file = self
20819 .buffer
20820 .read(cx)
20821 .as_singleton()
20822 .and_then(|b| b.read(cx).file());
20823 let file_extension = file_extension.or(file
20824 .as_ref()
20825 .and_then(|file| Path::new(file.file_name(cx)).extension())
20826 .and_then(|e| e.to_str())
20827 .map(|a| a.to_string()));
20828
20829 let vim_mode = vim_enabled(cx);
20830
20831 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20832 let copilot_enabled = edit_predictions_provider
20833 == language::language_settings::EditPredictionProvider::Copilot;
20834 let copilot_enabled_for_language = self
20835 .buffer
20836 .read(cx)
20837 .language_settings(cx)
20838 .show_edit_predictions;
20839
20840 let project = project.read(cx);
20841 let event_type = reported_event.event_type();
20842
20843 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20844 telemetry::event!(
20845 event_type,
20846 type = if auto_saved {"autosave"} else {"manual"},
20847 file_extension,
20848 vim_mode,
20849 copilot_enabled,
20850 copilot_enabled_for_language,
20851 edit_predictions_provider,
20852 is_via_ssh = project.is_via_remote_server(),
20853 );
20854 } else {
20855 telemetry::event!(
20856 event_type,
20857 file_extension,
20858 vim_mode,
20859 copilot_enabled,
20860 copilot_enabled_for_language,
20861 edit_predictions_provider,
20862 is_via_ssh = project.is_via_remote_server(),
20863 );
20864 };
20865 }
20866
20867 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20868 /// with each line being an array of {text, highlight} objects.
20869 fn copy_highlight_json(
20870 &mut self,
20871 _: &CopyHighlightJson,
20872 window: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) {
20875 #[derive(Serialize)]
20876 struct Chunk<'a> {
20877 text: String,
20878 highlight: Option<&'a str>,
20879 }
20880
20881 let snapshot = self.buffer.read(cx).snapshot(cx);
20882 let range = self
20883 .selected_text_range(false, window, cx)
20884 .and_then(|selection| {
20885 if selection.range.is_empty() {
20886 None
20887 } else {
20888 Some(selection.range)
20889 }
20890 })
20891 .unwrap_or_else(|| 0..snapshot.len());
20892
20893 let chunks = snapshot.chunks(range, true);
20894 let mut lines = Vec::new();
20895 let mut line: VecDeque<Chunk> = VecDeque::new();
20896
20897 let Some(style) = self.style.as_ref() else {
20898 return;
20899 };
20900
20901 for chunk in chunks {
20902 let highlight = chunk
20903 .syntax_highlight_id
20904 .and_then(|id| id.name(&style.syntax));
20905 let mut chunk_lines = chunk.text.split('\n').peekable();
20906 while let Some(text) = chunk_lines.next() {
20907 let mut merged_with_last_token = false;
20908 if let Some(last_token) = line.back_mut()
20909 && last_token.highlight == highlight
20910 {
20911 last_token.text.push_str(text);
20912 merged_with_last_token = true;
20913 }
20914
20915 if !merged_with_last_token {
20916 line.push_back(Chunk {
20917 text: text.into(),
20918 highlight,
20919 });
20920 }
20921
20922 if chunk_lines.peek().is_some() {
20923 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20924 line.pop_front();
20925 }
20926 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20927 line.pop_back();
20928 }
20929
20930 lines.push(mem::take(&mut line));
20931 }
20932 }
20933 }
20934
20935 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20936 return;
20937 };
20938 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20939 }
20940
20941 pub fn open_context_menu(
20942 &mut self,
20943 _: &OpenContextMenu,
20944 window: &mut Window,
20945 cx: &mut Context<Self>,
20946 ) {
20947 self.request_autoscroll(Autoscroll::newest(), cx);
20948 let position = self.selections.newest_display(cx).start;
20949 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20950 }
20951
20952 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20953 &self.inlay_hint_cache
20954 }
20955
20956 pub fn replay_insert_event(
20957 &mut self,
20958 text: &str,
20959 relative_utf16_range: Option<Range<isize>>,
20960 window: &mut Window,
20961 cx: &mut Context<Self>,
20962 ) {
20963 if !self.input_enabled {
20964 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20965 return;
20966 }
20967 if let Some(relative_utf16_range) = relative_utf16_range {
20968 let selections = self.selections.all::<OffsetUtf16>(cx);
20969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20970 let new_ranges = selections.into_iter().map(|range| {
20971 let start = OffsetUtf16(
20972 range
20973 .head()
20974 .0
20975 .saturating_add_signed(relative_utf16_range.start),
20976 );
20977 let end = OffsetUtf16(
20978 range
20979 .head()
20980 .0
20981 .saturating_add_signed(relative_utf16_range.end),
20982 );
20983 start..end
20984 });
20985 s.select_ranges(new_ranges);
20986 });
20987 }
20988
20989 self.handle_input(text, window, cx);
20990 }
20991
20992 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20993 let Some(provider) = self.semantics_provider.as_ref() else {
20994 return false;
20995 };
20996
20997 let mut supports = false;
20998 self.buffer().update(cx, |this, cx| {
20999 this.for_each_buffer(|buffer| {
21000 supports |= provider.supports_inlay_hints(buffer, cx);
21001 });
21002 });
21003
21004 supports
21005 }
21006
21007 pub fn is_focused(&self, window: &Window) -> bool {
21008 self.focus_handle.is_focused(window)
21009 }
21010
21011 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21012 cx.emit(EditorEvent::Focused);
21013
21014 if let Some(descendant) = self
21015 .last_focused_descendant
21016 .take()
21017 .and_then(|descendant| descendant.upgrade())
21018 {
21019 window.focus(&descendant);
21020 } else {
21021 if let Some(blame) = self.blame.as_ref() {
21022 blame.update(cx, GitBlame::focus)
21023 }
21024
21025 self.blink_manager.update(cx, BlinkManager::enable);
21026 self.show_cursor_names(window, cx);
21027 self.buffer.update(cx, |buffer, cx| {
21028 buffer.finalize_last_transaction(cx);
21029 if self.leader_id.is_none() {
21030 buffer.set_active_selections(
21031 &self.selections.disjoint_anchors(),
21032 self.selections.line_mode,
21033 self.cursor_shape,
21034 cx,
21035 );
21036 }
21037 });
21038 }
21039 }
21040
21041 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21042 cx.emit(EditorEvent::FocusedIn)
21043 }
21044
21045 fn handle_focus_out(
21046 &mut self,
21047 event: FocusOutEvent,
21048 _window: &mut Window,
21049 cx: &mut Context<Self>,
21050 ) {
21051 if event.blurred != self.focus_handle {
21052 self.last_focused_descendant = Some(event.blurred);
21053 }
21054 self.selection_drag_state = SelectionDragState::None;
21055 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21056 }
21057
21058 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21059 self.blink_manager.update(cx, BlinkManager::disable);
21060 self.buffer
21061 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21062
21063 if let Some(blame) = self.blame.as_ref() {
21064 blame.update(cx, GitBlame::blur)
21065 }
21066 if !self.hover_state.focused(window, cx) {
21067 hide_hover(self, cx);
21068 }
21069 if !self
21070 .context_menu
21071 .borrow()
21072 .as_ref()
21073 .is_some_and(|context_menu| context_menu.focused(window, cx))
21074 {
21075 self.hide_context_menu(window, cx);
21076 }
21077 self.discard_edit_prediction(false, cx);
21078 cx.emit(EditorEvent::Blurred);
21079 cx.notify();
21080 }
21081
21082 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21083 let mut pending: String = window
21084 .pending_input_keystrokes()
21085 .into_iter()
21086 .flatten()
21087 .filter_map(|keystroke| {
21088 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21089 keystroke.key_char.clone()
21090 } else {
21091 None
21092 }
21093 })
21094 .collect();
21095
21096 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21097 pending = "".to_string();
21098 }
21099
21100 let existing_pending = self
21101 .text_highlights::<PendingInput>(cx)
21102 .map(|(_, ranges)| ranges.to_vec());
21103 if existing_pending.is_none() && pending.is_empty() {
21104 return;
21105 }
21106 let transaction =
21107 self.transact(window, cx, |this, window, cx| {
21108 let selections = this.selections.all::<usize>(cx);
21109 let edits = selections
21110 .iter()
21111 .map(|selection| (selection.end..selection.end, pending.clone()));
21112 this.edit(edits, cx);
21113 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21114 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21115 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21116 }));
21117 });
21118 if let Some(existing_ranges) = existing_pending {
21119 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21120 this.edit(edits, cx);
21121 }
21122 });
21123
21124 let snapshot = self.snapshot(window, cx);
21125 let ranges = self
21126 .selections
21127 .all::<usize>(cx)
21128 .into_iter()
21129 .map(|selection| {
21130 snapshot.buffer_snapshot.anchor_after(selection.end)
21131 ..snapshot
21132 .buffer_snapshot
21133 .anchor_before(selection.end + pending.len())
21134 })
21135 .collect();
21136
21137 if pending.is_empty() {
21138 self.clear_highlights::<PendingInput>(cx);
21139 } else {
21140 self.highlight_text::<PendingInput>(
21141 ranges,
21142 HighlightStyle {
21143 underline: Some(UnderlineStyle {
21144 thickness: px(1.),
21145 color: None,
21146 wavy: false,
21147 }),
21148 ..Default::default()
21149 },
21150 cx,
21151 );
21152 }
21153
21154 self.ime_transaction = self.ime_transaction.or(transaction);
21155 if let Some(transaction) = self.ime_transaction {
21156 self.buffer.update(cx, |buffer, cx| {
21157 buffer.group_until_transaction(transaction, cx);
21158 });
21159 }
21160
21161 if self.text_highlights::<PendingInput>(cx).is_none() {
21162 self.ime_transaction.take();
21163 }
21164 }
21165
21166 pub fn register_action_renderer(
21167 &mut self,
21168 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21169 ) -> Subscription {
21170 let id = self.next_editor_action_id.post_inc();
21171 self.editor_actions
21172 .borrow_mut()
21173 .insert(id, Box::new(listener));
21174
21175 let editor_actions = self.editor_actions.clone();
21176 Subscription::new(move || {
21177 editor_actions.borrow_mut().remove(&id);
21178 })
21179 }
21180
21181 pub fn register_action<A: Action>(
21182 &mut self,
21183 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21184 ) -> Subscription {
21185 let id = self.next_editor_action_id.post_inc();
21186 let listener = Arc::new(listener);
21187 self.editor_actions.borrow_mut().insert(
21188 id,
21189 Box::new(move |_, window, _| {
21190 let listener = listener.clone();
21191 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21192 let action = action.downcast_ref().unwrap();
21193 if phase == DispatchPhase::Bubble {
21194 listener(action, window, cx)
21195 }
21196 })
21197 }),
21198 );
21199
21200 let editor_actions = self.editor_actions.clone();
21201 Subscription::new(move || {
21202 editor_actions.borrow_mut().remove(&id);
21203 })
21204 }
21205
21206 pub fn file_header_size(&self) -> u32 {
21207 FILE_HEADER_HEIGHT
21208 }
21209
21210 pub fn restore(
21211 &mut self,
21212 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21213 window: &mut Window,
21214 cx: &mut Context<Self>,
21215 ) {
21216 let workspace = self.workspace();
21217 let project = self.project();
21218 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21219 let mut tasks = Vec::new();
21220 for (buffer_id, changes) in revert_changes {
21221 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21222 buffer.update(cx, |buffer, cx| {
21223 buffer.edit(
21224 changes
21225 .into_iter()
21226 .map(|(range, text)| (range, text.to_string())),
21227 None,
21228 cx,
21229 );
21230 });
21231
21232 if let Some(project) =
21233 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21234 {
21235 project.update(cx, |project, cx| {
21236 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21237 })
21238 }
21239 }
21240 }
21241 tasks
21242 });
21243 cx.spawn_in(window, async move |_, cx| {
21244 for (buffer, task) in save_tasks {
21245 let result = task.await;
21246 if result.is_err() {
21247 let Some(path) = buffer
21248 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21249 .ok()
21250 else {
21251 continue;
21252 };
21253 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21254 let Some(task) = cx
21255 .update_window_entity(workspace, |workspace, window, cx| {
21256 workspace
21257 .open_path_preview(path, None, false, false, false, window, cx)
21258 })
21259 .ok()
21260 else {
21261 continue;
21262 };
21263 task.await.log_err();
21264 }
21265 }
21266 }
21267 })
21268 .detach();
21269 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21270 selections.refresh()
21271 });
21272 }
21273
21274 pub fn to_pixel_point(
21275 &self,
21276 source: multi_buffer::Anchor,
21277 editor_snapshot: &EditorSnapshot,
21278 window: &mut Window,
21279 ) -> Option<gpui::Point<Pixels>> {
21280 let source_point = source.to_display_point(editor_snapshot);
21281 self.display_to_pixel_point(source_point, editor_snapshot, window)
21282 }
21283
21284 pub fn display_to_pixel_point(
21285 &self,
21286 source: DisplayPoint,
21287 editor_snapshot: &EditorSnapshot,
21288 window: &mut Window,
21289 ) -> Option<gpui::Point<Pixels>> {
21290 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21291 let text_layout_details = self.text_layout_details(window);
21292 let scroll_top = text_layout_details
21293 .scroll_anchor
21294 .scroll_position(editor_snapshot)
21295 .y;
21296
21297 if source.row().as_f32() < scroll_top.floor() {
21298 return None;
21299 }
21300 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21301 let source_y = line_height * (source.row().as_f32() - scroll_top);
21302 Some(gpui::Point::new(source_x, source_y))
21303 }
21304
21305 pub fn has_visible_completions_menu(&self) -> bool {
21306 !self.edit_prediction_preview_is_active()
21307 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21308 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21309 })
21310 }
21311
21312 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21313 if self.mode.is_minimap() {
21314 return;
21315 }
21316 self.addons
21317 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21318 }
21319
21320 pub fn unregister_addon<T: Addon>(&mut self) {
21321 self.addons.remove(&std::any::TypeId::of::<T>());
21322 }
21323
21324 pub fn addon<T: Addon>(&self) -> Option<&T> {
21325 let type_id = std::any::TypeId::of::<T>();
21326 self.addons
21327 .get(&type_id)
21328 .and_then(|item| item.to_any().downcast_ref::<T>())
21329 }
21330
21331 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21332 let type_id = std::any::TypeId::of::<T>();
21333 self.addons
21334 .get_mut(&type_id)
21335 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21336 }
21337
21338 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21339 let text_layout_details = self.text_layout_details(window);
21340 let style = &text_layout_details.editor_style;
21341 let font_id = window.text_system().resolve_font(&style.text.font());
21342 let font_size = style.text.font_size.to_pixels(window.rem_size());
21343 let line_height = style.text.line_height_in_pixels(window.rem_size());
21344 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21345 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21346
21347 CharacterDimensions {
21348 em_width,
21349 em_advance,
21350 line_height,
21351 }
21352 }
21353
21354 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21355 self.load_diff_task.clone()
21356 }
21357
21358 fn read_metadata_from_db(
21359 &mut self,
21360 item_id: u64,
21361 workspace_id: WorkspaceId,
21362 window: &mut Window,
21363 cx: &mut Context<Editor>,
21364 ) {
21365 if self.is_singleton(cx)
21366 && !self.mode.is_minimap()
21367 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21368 {
21369 let buffer_snapshot = OnceCell::new();
21370
21371 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21372 && !folds.is_empty()
21373 {
21374 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21375 self.fold_ranges(
21376 folds
21377 .into_iter()
21378 .map(|(start, end)| {
21379 snapshot.clip_offset(start, Bias::Left)
21380 ..snapshot.clip_offset(end, Bias::Right)
21381 })
21382 .collect(),
21383 false,
21384 window,
21385 cx,
21386 );
21387 }
21388
21389 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21390 && !selections.is_empty()
21391 {
21392 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21393 // skip adding the initial selection to selection history
21394 self.selection_history.mode = SelectionHistoryMode::Skipping;
21395 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21396 s.select_ranges(selections.into_iter().map(|(start, end)| {
21397 snapshot.clip_offset(start, Bias::Left)
21398 ..snapshot.clip_offset(end, Bias::Right)
21399 }));
21400 });
21401 self.selection_history.mode = SelectionHistoryMode::Normal;
21402 };
21403 }
21404
21405 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21406 }
21407
21408 fn update_lsp_data(
21409 &mut self,
21410 ignore_cache: bool,
21411 for_buffer: Option<BufferId>,
21412 window: &mut Window,
21413 cx: &mut Context<'_, Self>,
21414 ) {
21415 self.pull_diagnostics(for_buffer, window, cx);
21416 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21417 }
21418}
21419
21420fn vim_enabled(cx: &App) -> bool {
21421 cx.global::<SettingsStore>()
21422 .raw_user_settings()
21423 .get("vim_mode")
21424 == Some(&serde_json::Value::Bool(true))
21425}
21426
21427fn process_completion_for_edit(
21428 completion: &Completion,
21429 intent: CompletionIntent,
21430 buffer: &Entity<Buffer>,
21431 cursor_position: &text::Anchor,
21432 cx: &mut Context<Editor>,
21433) -> CompletionEdit {
21434 let buffer = buffer.read(cx);
21435 let buffer_snapshot = buffer.snapshot();
21436 let (snippet, new_text) = if completion.is_snippet() {
21437 // Workaround for typescript language server issues so that methods don't expand within
21438 // strings and functions with type expressions. The previous point is used because the query
21439 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21440 let mut snippet_source = completion.new_text.clone();
21441 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21442 previous_point.column = previous_point.column.saturating_sub(1);
21443 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21444 && scope.prefers_label_for_snippet_in_completion()
21445 && let Some(label) = completion.label()
21446 && matches!(
21447 completion.kind(),
21448 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21449 )
21450 {
21451 snippet_source = label;
21452 }
21453 match Snippet::parse(&snippet_source).log_err() {
21454 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21455 None => (None, completion.new_text.clone()),
21456 }
21457 } else {
21458 (None, completion.new_text.clone())
21459 };
21460
21461 let mut range_to_replace = {
21462 let replace_range = &completion.replace_range;
21463 if let CompletionSource::Lsp {
21464 insert_range: Some(insert_range),
21465 ..
21466 } = &completion.source
21467 {
21468 debug_assert_eq!(
21469 insert_range.start, replace_range.start,
21470 "insert_range and replace_range should start at the same position"
21471 );
21472 debug_assert!(
21473 insert_range
21474 .start
21475 .cmp(cursor_position, &buffer_snapshot)
21476 .is_le(),
21477 "insert_range should start before or at cursor position"
21478 );
21479 debug_assert!(
21480 replace_range
21481 .start
21482 .cmp(cursor_position, &buffer_snapshot)
21483 .is_le(),
21484 "replace_range should start before or at cursor position"
21485 );
21486
21487 let should_replace = match intent {
21488 CompletionIntent::CompleteWithInsert => false,
21489 CompletionIntent::CompleteWithReplace => true,
21490 CompletionIntent::Complete | CompletionIntent::Compose => {
21491 let insert_mode =
21492 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21493 .completions
21494 .lsp_insert_mode;
21495 match insert_mode {
21496 LspInsertMode::Insert => false,
21497 LspInsertMode::Replace => true,
21498 LspInsertMode::ReplaceSubsequence => {
21499 let mut text_to_replace = buffer.chars_for_range(
21500 buffer.anchor_before(replace_range.start)
21501 ..buffer.anchor_after(replace_range.end),
21502 );
21503 let mut current_needle = text_to_replace.next();
21504 for haystack_ch in completion.label.text.chars() {
21505 if let Some(needle_ch) = current_needle
21506 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21507 {
21508 current_needle = text_to_replace.next();
21509 }
21510 }
21511 current_needle.is_none()
21512 }
21513 LspInsertMode::ReplaceSuffix => {
21514 if replace_range
21515 .end
21516 .cmp(cursor_position, &buffer_snapshot)
21517 .is_gt()
21518 {
21519 let range_after_cursor = *cursor_position..replace_range.end;
21520 let text_after_cursor = buffer
21521 .text_for_range(
21522 buffer.anchor_before(range_after_cursor.start)
21523 ..buffer.anchor_after(range_after_cursor.end),
21524 )
21525 .collect::<String>()
21526 .to_ascii_lowercase();
21527 completion
21528 .label
21529 .text
21530 .to_ascii_lowercase()
21531 .ends_with(&text_after_cursor)
21532 } else {
21533 true
21534 }
21535 }
21536 }
21537 }
21538 };
21539
21540 if should_replace {
21541 replace_range.clone()
21542 } else {
21543 insert_range.clone()
21544 }
21545 } else {
21546 replace_range.clone()
21547 }
21548 };
21549
21550 if range_to_replace
21551 .end
21552 .cmp(cursor_position, &buffer_snapshot)
21553 .is_lt()
21554 {
21555 range_to_replace.end = *cursor_position;
21556 }
21557
21558 CompletionEdit {
21559 new_text,
21560 replace_range: range_to_replace.to_offset(buffer),
21561 snippet,
21562 }
21563}
21564
21565struct CompletionEdit {
21566 new_text: String,
21567 replace_range: Range<usize>,
21568 snippet: Option<Snippet>,
21569}
21570
21571fn insert_extra_newline_brackets(
21572 buffer: &MultiBufferSnapshot,
21573 range: Range<usize>,
21574 language: &language::LanguageScope,
21575) -> bool {
21576 let leading_whitespace_len = buffer
21577 .reversed_chars_at(range.start)
21578 .take_while(|c| c.is_whitespace() && *c != '\n')
21579 .map(|c| c.len_utf8())
21580 .sum::<usize>();
21581 let trailing_whitespace_len = buffer
21582 .chars_at(range.end)
21583 .take_while(|c| c.is_whitespace() && *c != '\n')
21584 .map(|c| c.len_utf8())
21585 .sum::<usize>();
21586 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21587
21588 language.brackets().any(|(pair, enabled)| {
21589 let pair_start = pair.start.trim_end();
21590 let pair_end = pair.end.trim_start();
21591
21592 enabled
21593 && pair.newline
21594 && buffer.contains_str_at(range.end, pair_end)
21595 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21596 })
21597}
21598
21599fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21600 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21601 [(buffer, range, _)] => (*buffer, range.clone()),
21602 _ => return false,
21603 };
21604 let pair = {
21605 let mut result: Option<BracketMatch> = None;
21606
21607 for pair in buffer
21608 .all_bracket_ranges(range.clone())
21609 .filter(move |pair| {
21610 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21611 })
21612 {
21613 let len = pair.close_range.end - pair.open_range.start;
21614
21615 if let Some(existing) = &result {
21616 let existing_len = existing.close_range.end - existing.open_range.start;
21617 if len > existing_len {
21618 continue;
21619 }
21620 }
21621
21622 result = Some(pair);
21623 }
21624
21625 result
21626 };
21627 let Some(pair) = pair else {
21628 return false;
21629 };
21630 pair.newline_only
21631 && buffer
21632 .chars_for_range(pair.open_range.end..range.start)
21633 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21634 .all(|c| c.is_whitespace() && c != '\n')
21635}
21636
21637fn update_uncommitted_diff_for_buffer(
21638 editor: Entity<Editor>,
21639 project: &Entity<Project>,
21640 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21641 buffer: Entity<MultiBuffer>,
21642 cx: &mut App,
21643) -> Task<()> {
21644 let mut tasks = Vec::new();
21645 project.update(cx, |project, cx| {
21646 for buffer in buffers {
21647 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21648 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21649 }
21650 }
21651 });
21652 cx.spawn(async move |cx| {
21653 let diffs = future::join_all(tasks).await;
21654 if editor
21655 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21656 .unwrap_or(false)
21657 {
21658 return;
21659 }
21660
21661 buffer
21662 .update(cx, |buffer, cx| {
21663 for diff in diffs.into_iter().flatten() {
21664 buffer.add_diff(diff, cx);
21665 }
21666 })
21667 .ok();
21668 })
21669}
21670
21671fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21672 let tab_size = tab_size.get() as usize;
21673 let mut width = offset;
21674
21675 for ch in text.chars() {
21676 width += if ch == '\t' {
21677 tab_size - (width % tab_size)
21678 } else {
21679 1
21680 };
21681 }
21682
21683 width - offset
21684}
21685
21686#[cfg(test)]
21687mod tests {
21688 use super::*;
21689
21690 #[test]
21691 fn test_string_size_with_expanded_tabs() {
21692 let nz = |val| NonZeroU32::new(val).unwrap();
21693 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21694 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21695 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21696 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21697 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21698 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21699 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21700 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21701 }
21702}
21703
21704/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21705struct WordBreakingTokenizer<'a> {
21706 input: &'a str,
21707}
21708
21709impl<'a> WordBreakingTokenizer<'a> {
21710 fn new(input: &'a str) -> Self {
21711 Self { input }
21712 }
21713}
21714
21715fn is_char_ideographic(ch: char) -> bool {
21716 use unicode_script::Script::*;
21717 use unicode_script::UnicodeScript;
21718 matches!(ch.script(), Han | Tangut | Yi)
21719}
21720
21721fn is_grapheme_ideographic(text: &str) -> bool {
21722 text.chars().any(is_char_ideographic)
21723}
21724
21725fn is_grapheme_whitespace(text: &str) -> bool {
21726 text.chars().any(|x| x.is_whitespace())
21727}
21728
21729fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21730 text.chars()
21731 .next()
21732 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21733}
21734
21735#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21736enum WordBreakToken<'a> {
21737 Word { token: &'a str, grapheme_len: usize },
21738 InlineWhitespace { token: &'a str, grapheme_len: usize },
21739 Newline,
21740}
21741
21742impl<'a> Iterator for WordBreakingTokenizer<'a> {
21743 /// Yields a span, the count of graphemes in the token, and whether it was
21744 /// whitespace. Note that it also breaks at word boundaries.
21745 type Item = WordBreakToken<'a>;
21746
21747 fn next(&mut self) -> Option<Self::Item> {
21748 use unicode_segmentation::UnicodeSegmentation;
21749 if self.input.is_empty() {
21750 return None;
21751 }
21752
21753 let mut iter = self.input.graphemes(true).peekable();
21754 let mut offset = 0;
21755 let mut grapheme_len = 0;
21756 if let Some(first_grapheme) = iter.next() {
21757 let is_newline = first_grapheme == "\n";
21758 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21759 offset += first_grapheme.len();
21760 grapheme_len += 1;
21761 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21762 if let Some(grapheme) = iter.peek().copied()
21763 && should_stay_with_preceding_ideograph(grapheme)
21764 {
21765 offset += grapheme.len();
21766 grapheme_len += 1;
21767 }
21768 } else {
21769 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21770 let mut next_word_bound = words.peek().copied();
21771 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21772 next_word_bound = words.next();
21773 }
21774 while let Some(grapheme) = iter.peek().copied() {
21775 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21776 break;
21777 };
21778 if is_grapheme_whitespace(grapheme) != is_whitespace
21779 || (grapheme == "\n") != is_newline
21780 {
21781 break;
21782 };
21783 offset += grapheme.len();
21784 grapheme_len += 1;
21785 iter.next();
21786 }
21787 }
21788 let token = &self.input[..offset];
21789 self.input = &self.input[offset..];
21790 if token == "\n" {
21791 Some(WordBreakToken::Newline)
21792 } else if is_whitespace {
21793 Some(WordBreakToken::InlineWhitespace {
21794 token,
21795 grapheme_len,
21796 })
21797 } else {
21798 Some(WordBreakToken::Word {
21799 token,
21800 grapheme_len,
21801 })
21802 }
21803 } else {
21804 None
21805 }
21806 }
21807}
21808
21809#[test]
21810fn test_word_breaking_tokenizer() {
21811 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21812 ("", &[]),
21813 (" ", &[whitespace(" ", 2)]),
21814 ("Ʒ", &[word("Ʒ", 1)]),
21815 ("Ǽ", &[word("Ǽ", 1)]),
21816 ("⋑", &[word("⋑", 1)]),
21817 ("⋑⋑", &[word("⋑⋑", 2)]),
21818 (
21819 "原理,进而",
21820 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21821 ),
21822 (
21823 "hello world",
21824 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21825 ),
21826 (
21827 "hello, world",
21828 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21829 ),
21830 (
21831 " hello world",
21832 &[
21833 whitespace(" ", 2),
21834 word("hello", 5),
21835 whitespace(" ", 1),
21836 word("world", 5),
21837 ],
21838 ),
21839 (
21840 "这是什么 \n 钢笔",
21841 &[
21842 word("这", 1),
21843 word("是", 1),
21844 word("什", 1),
21845 word("么", 1),
21846 whitespace(" ", 1),
21847 newline(),
21848 whitespace(" ", 1),
21849 word("钢", 1),
21850 word("笔", 1),
21851 ],
21852 ),
21853 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21854 ];
21855
21856 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21857 WordBreakToken::Word {
21858 token,
21859 grapheme_len,
21860 }
21861 }
21862
21863 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21864 WordBreakToken::InlineWhitespace {
21865 token,
21866 grapheme_len,
21867 }
21868 }
21869
21870 fn newline() -> WordBreakToken<'static> {
21871 WordBreakToken::Newline
21872 }
21873
21874 for (input, result) in tests {
21875 assert_eq!(
21876 WordBreakingTokenizer::new(input)
21877 .collect::<Vec<_>>()
21878 .as_slice(),
21879 *result,
21880 );
21881 }
21882}
21883
21884fn wrap_with_prefix(
21885 first_line_prefix: String,
21886 subsequent_lines_prefix: String,
21887 unwrapped_text: String,
21888 wrap_column: usize,
21889 tab_size: NonZeroU32,
21890 preserve_existing_whitespace: bool,
21891) -> String {
21892 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21893 let subsequent_lines_prefix_len =
21894 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21895 let mut wrapped_text = String::new();
21896 let mut current_line = first_line_prefix;
21897 let mut is_first_line = true;
21898
21899 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21900 let mut current_line_len = first_line_prefix_len;
21901 let mut in_whitespace = false;
21902 for token in tokenizer {
21903 let have_preceding_whitespace = in_whitespace;
21904 match token {
21905 WordBreakToken::Word {
21906 token,
21907 grapheme_len,
21908 } => {
21909 in_whitespace = false;
21910 let current_prefix_len = if is_first_line {
21911 first_line_prefix_len
21912 } else {
21913 subsequent_lines_prefix_len
21914 };
21915 if current_line_len + grapheme_len > wrap_column
21916 && current_line_len != current_prefix_len
21917 {
21918 wrapped_text.push_str(current_line.trim_end());
21919 wrapped_text.push('\n');
21920 is_first_line = false;
21921 current_line = subsequent_lines_prefix.clone();
21922 current_line_len = subsequent_lines_prefix_len;
21923 }
21924 current_line.push_str(token);
21925 current_line_len += grapheme_len;
21926 }
21927 WordBreakToken::InlineWhitespace {
21928 mut token,
21929 mut grapheme_len,
21930 } => {
21931 in_whitespace = true;
21932 if have_preceding_whitespace && !preserve_existing_whitespace {
21933 continue;
21934 }
21935 if !preserve_existing_whitespace {
21936 token = " ";
21937 grapheme_len = 1;
21938 }
21939 let current_prefix_len = if is_first_line {
21940 first_line_prefix_len
21941 } else {
21942 subsequent_lines_prefix_len
21943 };
21944 if current_line_len + grapheme_len > wrap_column {
21945 wrapped_text.push_str(current_line.trim_end());
21946 wrapped_text.push('\n');
21947 is_first_line = false;
21948 current_line = subsequent_lines_prefix.clone();
21949 current_line_len = subsequent_lines_prefix_len;
21950 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21951 current_line.push_str(token);
21952 current_line_len += grapheme_len;
21953 }
21954 }
21955 WordBreakToken::Newline => {
21956 in_whitespace = true;
21957 let current_prefix_len = if is_first_line {
21958 first_line_prefix_len
21959 } else {
21960 subsequent_lines_prefix_len
21961 };
21962 if preserve_existing_whitespace {
21963 wrapped_text.push_str(current_line.trim_end());
21964 wrapped_text.push('\n');
21965 is_first_line = false;
21966 current_line = subsequent_lines_prefix.clone();
21967 current_line_len = subsequent_lines_prefix_len;
21968 } else if have_preceding_whitespace {
21969 continue;
21970 } else if current_line_len + 1 > wrap_column
21971 && current_line_len != current_prefix_len
21972 {
21973 wrapped_text.push_str(current_line.trim_end());
21974 wrapped_text.push('\n');
21975 is_first_line = false;
21976 current_line = subsequent_lines_prefix.clone();
21977 current_line_len = subsequent_lines_prefix_len;
21978 } else if current_line_len != current_prefix_len {
21979 current_line.push(' ');
21980 current_line_len += 1;
21981 }
21982 }
21983 }
21984 }
21985
21986 if !current_line.is_empty() {
21987 wrapped_text.push_str(¤t_line);
21988 }
21989 wrapped_text
21990}
21991
21992#[test]
21993fn test_wrap_with_prefix() {
21994 assert_eq!(
21995 wrap_with_prefix(
21996 "# ".to_string(),
21997 "# ".to_string(),
21998 "abcdefg".to_string(),
21999 4,
22000 NonZeroU32::new(4).unwrap(),
22001 false,
22002 ),
22003 "# abcdefg"
22004 );
22005 assert_eq!(
22006 wrap_with_prefix(
22007 "".to_string(),
22008 "".to_string(),
22009 "\thello world".to_string(),
22010 8,
22011 NonZeroU32::new(4).unwrap(),
22012 false,
22013 ),
22014 "hello\nworld"
22015 );
22016 assert_eq!(
22017 wrap_with_prefix(
22018 "// ".to_string(),
22019 "// ".to_string(),
22020 "xx \nyy zz aa bb cc".to_string(),
22021 12,
22022 NonZeroU32::new(4).unwrap(),
22023 false,
22024 ),
22025 "// xx yy zz\n// aa bb cc"
22026 );
22027 assert_eq!(
22028 wrap_with_prefix(
22029 String::new(),
22030 String::new(),
22031 "这是什么 \n 钢笔".to_string(),
22032 3,
22033 NonZeroU32::new(4).unwrap(),
22034 false,
22035 ),
22036 "这是什\n么 钢\n笔"
22037 );
22038}
22039
22040pub trait CollaborationHub {
22041 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22042 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22043 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22044}
22045
22046impl CollaborationHub for Entity<Project> {
22047 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22048 self.read(cx).collaborators()
22049 }
22050
22051 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22052 self.read(cx).user_store().read(cx).participant_indices()
22053 }
22054
22055 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22056 let this = self.read(cx);
22057 let user_ids = this.collaborators().values().map(|c| c.user_id);
22058 this.user_store().read(cx).participant_names(user_ids, cx)
22059 }
22060}
22061
22062pub trait SemanticsProvider {
22063 fn hover(
22064 &self,
22065 buffer: &Entity<Buffer>,
22066 position: text::Anchor,
22067 cx: &mut App,
22068 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22069
22070 fn inline_values(
22071 &self,
22072 buffer_handle: Entity<Buffer>,
22073 range: Range<text::Anchor>,
22074 cx: &mut App,
22075 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22076
22077 fn inlay_hints(
22078 &self,
22079 buffer_handle: Entity<Buffer>,
22080 range: Range<text::Anchor>,
22081 cx: &mut App,
22082 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22083
22084 fn resolve_inlay_hint(
22085 &self,
22086 hint: InlayHint,
22087 buffer_handle: Entity<Buffer>,
22088 server_id: LanguageServerId,
22089 cx: &mut App,
22090 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22091
22092 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22093
22094 fn document_highlights(
22095 &self,
22096 buffer: &Entity<Buffer>,
22097 position: text::Anchor,
22098 cx: &mut App,
22099 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22100
22101 fn definitions(
22102 &self,
22103 buffer: &Entity<Buffer>,
22104 position: text::Anchor,
22105 kind: GotoDefinitionKind,
22106 cx: &mut App,
22107 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22108
22109 fn range_for_rename(
22110 &self,
22111 buffer: &Entity<Buffer>,
22112 position: text::Anchor,
22113 cx: &mut App,
22114 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22115
22116 fn perform_rename(
22117 &self,
22118 buffer: &Entity<Buffer>,
22119 position: text::Anchor,
22120 new_name: String,
22121 cx: &mut App,
22122 ) -> Option<Task<Result<ProjectTransaction>>>;
22123}
22124
22125pub trait CompletionProvider {
22126 fn completions(
22127 &self,
22128 excerpt_id: ExcerptId,
22129 buffer: &Entity<Buffer>,
22130 buffer_position: text::Anchor,
22131 trigger: CompletionContext,
22132 window: &mut Window,
22133 cx: &mut Context<Editor>,
22134 ) -> Task<Result<Vec<CompletionResponse>>>;
22135
22136 fn resolve_completions(
22137 &self,
22138 _buffer: Entity<Buffer>,
22139 _completion_indices: Vec<usize>,
22140 _completions: Rc<RefCell<Box<[Completion]>>>,
22141 _cx: &mut Context<Editor>,
22142 ) -> Task<Result<bool>> {
22143 Task::ready(Ok(false))
22144 }
22145
22146 fn apply_additional_edits_for_completion(
22147 &self,
22148 _buffer: Entity<Buffer>,
22149 _completions: Rc<RefCell<Box<[Completion]>>>,
22150 _completion_index: usize,
22151 _push_to_history: bool,
22152 _cx: &mut Context<Editor>,
22153 ) -> Task<Result<Option<language::Transaction>>> {
22154 Task::ready(Ok(None))
22155 }
22156
22157 fn is_completion_trigger(
22158 &self,
22159 buffer: &Entity<Buffer>,
22160 position: language::Anchor,
22161 text: &str,
22162 trigger_in_words: bool,
22163 menu_is_open: bool,
22164 cx: &mut Context<Editor>,
22165 ) -> bool;
22166
22167 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22168
22169 fn sort_completions(&self) -> bool {
22170 true
22171 }
22172
22173 fn filter_completions(&self) -> bool {
22174 true
22175 }
22176}
22177
22178pub trait CodeActionProvider {
22179 fn id(&self) -> Arc<str>;
22180
22181 fn code_actions(
22182 &self,
22183 buffer: &Entity<Buffer>,
22184 range: Range<text::Anchor>,
22185 window: &mut Window,
22186 cx: &mut App,
22187 ) -> Task<Result<Vec<CodeAction>>>;
22188
22189 fn apply_code_action(
22190 &self,
22191 buffer_handle: Entity<Buffer>,
22192 action: CodeAction,
22193 excerpt_id: ExcerptId,
22194 push_to_history: bool,
22195 window: &mut Window,
22196 cx: &mut App,
22197 ) -> Task<Result<ProjectTransaction>>;
22198}
22199
22200impl CodeActionProvider for Entity<Project> {
22201 fn id(&self) -> Arc<str> {
22202 "project".into()
22203 }
22204
22205 fn code_actions(
22206 &self,
22207 buffer: &Entity<Buffer>,
22208 range: Range<text::Anchor>,
22209 _window: &mut Window,
22210 cx: &mut App,
22211 ) -> Task<Result<Vec<CodeAction>>> {
22212 self.update(cx, |project, cx| {
22213 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22214 let code_actions = project.code_actions(buffer, range, None, cx);
22215 cx.background_spawn(async move {
22216 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22217 Ok(code_lens_actions
22218 .context("code lens fetch")?
22219 .into_iter()
22220 .flatten()
22221 .chain(
22222 code_actions
22223 .context("code action fetch")?
22224 .into_iter()
22225 .flatten(),
22226 )
22227 .collect())
22228 })
22229 })
22230 }
22231
22232 fn apply_code_action(
22233 &self,
22234 buffer_handle: Entity<Buffer>,
22235 action: CodeAction,
22236 _excerpt_id: ExcerptId,
22237 push_to_history: bool,
22238 _window: &mut Window,
22239 cx: &mut App,
22240 ) -> Task<Result<ProjectTransaction>> {
22241 self.update(cx, |project, cx| {
22242 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22243 })
22244 }
22245}
22246
22247fn snippet_completions(
22248 project: &Project,
22249 buffer: &Entity<Buffer>,
22250 buffer_position: text::Anchor,
22251 cx: &mut App,
22252) -> Task<Result<CompletionResponse>> {
22253 let languages = buffer.read(cx).languages_at(buffer_position);
22254 let snippet_store = project.snippets().read(cx);
22255
22256 let scopes: Vec<_> = languages
22257 .iter()
22258 .filter_map(|language| {
22259 let language_name = language.lsp_id();
22260 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22261
22262 if snippets.is_empty() {
22263 None
22264 } else {
22265 Some((language.default_scope(), snippets))
22266 }
22267 })
22268 .collect();
22269
22270 if scopes.is_empty() {
22271 return Task::ready(Ok(CompletionResponse {
22272 completions: vec![],
22273 display_options: CompletionDisplayOptions::default(),
22274 is_incomplete: false,
22275 }));
22276 }
22277
22278 let snapshot = buffer.read(cx).text_snapshot();
22279 let chars: String = snapshot
22280 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22281 .collect();
22282 let executor = cx.background_executor().clone();
22283
22284 cx.background_spawn(async move {
22285 let mut is_incomplete = false;
22286 let mut completions: Vec<Completion> = Vec::new();
22287 for (scope, snippets) in scopes.into_iter() {
22288 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22289 let mut last_word = chars
22290 .chars()
22291 .take_while(|c| classifier.is_word(*c))
22292 .collect::<String>();
22293 last_word = last_word.chars().rev().collect();
22294
22295 if last_word.is_empty() {
22296 return Ok(CompletionResponse {
22297 completions: vec![],
22298 display_options: CompletionDisplayOptions::default(),
22299 is_incomplete: true,
22300 });
22301 }
22302
22303 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22304 let to_lsp = |point: &text::Anchor| {
22305 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22306 point_to_lsp(end)
22307 };
22308 let lsp_end = to_lsp(&buffer_position);
22309
22310 let candidates = snippets
22311 .iter()
22312 .enumerate()
22313 .flat_map(|(ix, snippet)| {
22314 snippet
22315 .prefix
22316 .iter()
22317 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22318 })
22319 .collect::<Vec<StringMatchCandidate>>();
22320
22321 const MAX_RESULTS: usize = 100;
22322 let mut matches = fuzzy::match_strings(
22323 &candidates,
22324 &last_word,
22325 last_word.chars().any(|c| c.is_uppercase()),
22326 true,
22327 MAX_RESULTS,
22328 &Default::default(),
22329 executor.clone(),
22330 )
22331 .await;
22332
22333 if matches.len() >= MAX_RESULTS {
22334 is_incomplete = true;
22335 }
22336
22337 // Remove all candidates where the query's start does not match the start of any word in the candidate
22338 if let Some(query_start) = last_word.chars().next() {
22339 matches.retain(|string_match| {
22340 split_words(&string_match.string).any(|word| {
22341 // Check that the first codepoint of the word as lowercase matches the first
22342 // codepoint of the query as lowercase
22343 word.chars()
22344 .flat_map(|codepoint| codepoint.to_lowercase())
22345 .zip(query_start.to_lowercase())
22346 .all(|(word_cp, query_cp)| word_cp == query_cp)
22347 })
22348 });
22349 }
22350
22351 let matched_strings = matches
22352 .into_iter()
22353 .map(|m| m.string)
22354 .collect::<HashSet<_>>();
22355
22356 completions.extend(snippets.iter().filter_map(|snippet| {
22357 let matching_prefix = snippet
22358 .prefix
22359 .iter()
22360 .find(|prefix| matched_strings.contains(*prefix))?;
22361 let start = as_offset - last_word.len();
22362 let start = snapshot.anchor_before(start);
22363 let range = start..buffer_position;
22364 let lsp_start = to_lsp(&start);
22365 let lsp_range = lsp::Range {
22366 start: lsp_start,
22367 end: lsp_end,
22368 };
22369 Some(Completion {
22370 replace_range: range,
22371 new_text: snippet.body.clone(),
22372 source: CompletionSource::Lsp {
22373 insert_range: None,
22374 server_id: LanguageServerId(usize::MAX),
22375 resolved: true,
22376 lsp_completion: Box::new(lsp::CompletionItem {
22377 label: snippet.prefix.first().unwrap().clone(),
22378 kind: Some(CompletionItemKind::SNIPPET),
22379 label_details: snippet.description.as_ref().map(|description| {
22380 lsp::CompletionItemLabelDetails {
22381 detail: Some(description.clone()),
22382 description: None,
22383 }
22384 }),
22385 insert_text_format: Some(InsertTextFormat::SNIPPET),
22386 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22387 lsp::InsertReplaceEdit {
22388 new_text: snippet.body.clone(),
22389 insert: lsp_range,
22390 replace: lsp_range,
22391 },
22392 )),
22393 filter_text: Some(snippet.body.clone()),
22394 sort_text: Some(char::MAX.to_string()),
22395 ..lsp::CompletionItem::default()
22396 }),
22397 lsp_defaults: None,
22398 },
22399 label: CodeLabel {
22400 text: matching_prefix.clone(),
22401 runs: Vec::new(),
22402 filter_range: 0..matching_prefix.len(),
22403 },
22404 icon_path: None,
22405 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22406 single_line: snippet.name.clone().into(),
22407 plain_text: snippet
22408 .description
22409 .clone()
22410 .map(|description| description.into()),
22411 }),
22412 insert_text_mode: None,
22413 confirm: None,
22414 })
22415 }))
22416 }
22417
22418 Ok(CompletionResponse {
22419 completions,
22420 display_options: CompletionDisplayOptions::default(),
22421 is_incomplete,
22422 })
22423 })
22424}
22425
22426impl CompletionProvider for Entity<Project> {
22427 fn completions(
22428 &self,
22429 _excerpt_id: ExcerptId,
22430 buffer: &Entity<Buffer>,
22431 buffer_position: text::Anchor,
22432 options: CompletionContext,
22433 _window: &mut Window,
22434 cx: &mut Context<Editor>,
22435 ) -> Task<Result<Vec<CompletionResponse>>> {
22436 self.update(cx, |project, cx| {
22437 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22438 let project_completions = project.completions(buffer, buffer_position, options, cx);
22439 cx.background_spawn(async move {
22440 let mut responses = project_completions.await?;
22441 let snippets = snippets.await?;
22442 if !snippets.completions.is_empty() {
22443 responses.push(snippets);
22444 }
22445 Ok(responses)
22446 })
22447 })
22448 }
22449
22450 fn resolve_completions(
22451 &self,
22452 buffer: Entity<Buffer>,
22453 completion_indices: Vec<usize>,
22454 completions: Rc<RefCell<Box<[Completion]>>>,
22455 cx: &mut Context<Editor>,
22456 ) -> Task<Result<bool>> {
22457 self.update(cx, |project, cx| {
22458 project.lsp_store().update(cx, |lsp_store, cx| {
22459 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22460 })
22461 })
22462 }
22463
22464 fn apply_additional_edits_for_completion(
22465 &self,
22466 buffer: Entity<Buffer>,
22467 completions: Rc<RefCell<Box<[Completion]>>>,
22468 completion_index: usize,
22469 push_to_history: bool,
22470 cx: &mut Context<Editor>,
22471 ) -> Task<Result<Option<language::Transaction>>> {
22472 self.update(cx, |project, cx| {
22473 project.lsp_store().update(cx, |lsp_store, cx| {
22474 lsp_store.apply_additional_edits_for_completion(
22475 buffer,
22476 completions,
22477 completion_index,
22478 push_to_history,
22479 cx,
22480 )
22481 })
22482 })
22483 }
22484
22485 fn is_completion_trigger(
22486 &self,
22487 buffer: &Entity<Buffer>,
22488 position: language::Anchor,
22489 text: &str,
22490 trigger_in_words: bool,
22491 menu_is_open: bool,
22492 cx: &mut Context<Editor>,
22493 ) -> bool {
22494 let mut chars = text.chars();
22495 let char = if let Some(char) = chars.next() {
22496 char
22497 } else {
22498 return false;
22499 };
22500 if chars.next().is_some() {
22501 return false;
22502 }
22503
22504 let buffer = buffer.read(cx);
22505 let snapshot = buffer.snapshot();
22506 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22507 return false;
22508 }
22509 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22510 if trigger_in_words && classifier.is_word(char) {
22511 return true;
22512 }
22513
22514 buffer.completion_triggers().contains(text)
22515 }
22516}
22517
22518impl SemanticsProvider for Entity<Project> {
22519 fn hover(
22520 &self,
22521 buffer: &Entity<Buffer>,
22522 position: text::Anchor,
22523 cx: &mut App,
22524 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22525 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22526 }
22527
22528 fn document_highlights(
22529 &self,
22530 buffer: &Entity<Buffer>,
22531 position: text::Anchor,
22532 cx: &mut App,
22533 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22534 Some(self.update(cx, |project, cx| {
22535 project.document_highlights(buffer, position, cx)
22536 }))
22537 }
22538
22539 fn definitions(
22540 &self,
22541 buffer: &Entity<Buffer>,
22542 position: text::Anchor,
22543 kind: GotoDefinitionKind,
22544 cx: &mut App,
22545 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22546 Some(self.update(cx, |project, cx| match kind {
22547 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22548 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22549 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22550 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22551 }))
22552 }
22553
22554 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22555 self.update(cx, |project, cx| {
22556 if project
22557 .active_debug_session(cx)
22558 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22559 {
22560 return true;
22561 }
22562
22563 buffer.update(cx, |buffer, cx| {
22564 project.any_language_server_supports_inlay_hints(buffer, cx)
22565 })
22566 })
22567 }
22568
22569 fn inline_values(
22570 &self,
22571 buffer_handle: Entity<Buffer>,
22572 range: Range<text::Anchor>,
22573 cx: &mut App,
22574 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22575 self.update(cx, |project, cx| {
22576 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22577
22578 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22579 })
22580 }
22581
22582 fn inlay_hints(
22583 &self,
22584 buffer_handle: Entity<Buffer>,
22585 range: Range<text::Anchor>,
22586 cx: &mut App,
22587 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22588 Some(self.update(cx, |project, cx| {
22589 project.inlay_hints(buffer_handle, range, cx)
22590 }))
22591 }
22592
22593 fn resolve_inlay_hint(
22594 &self,
22595 hint: InlayHint,
22596 buffer_handle: Entity<Buffer>,
22597 server_id: LanguageServerId,
22598 cx: &mut App,
22599 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22600 Some(self.update(cx, |project, cx| {
22601 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22602 }))
22603 }
22604
22605 fn range_for_rename(
22606 &self,
22607 buffer: &Entity<Buffer>,
22608 position: text::Anchor,
22609 cx: &mut App,
22610 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22611 Some(self.update(cx, |project, cx| {
22612 let buffer = buffer.clone();
22613 let task = project.prepare_rename(buffer.clone(), position, cx);
22614 cx.spawn(async move |_, cx| {
22615 Ok(match task.await? {
22616 PrepareRenameResponse::Success(range) => Some(range),
22617 PrepareRenameResponse::InvalidPosition => None,
22618 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22619 // Fallback on using TreeSitter info to determine identifier range
22620 buffer.read_with(cx, |buffer, _| {
22621 let snapshot = buffer.snapshot();
22622 let (range, kind) = snapshot.surrounding_word(position, false);
22623 if kind != Some(CharKind::Word) {
22624 return None;
22625 }
22626 Some(
22627 snapshot.anchor_before(range.start)
22628 ..snapshot.anchor_after(range.end),
22629 )
22630 })?
22631 }
22632 })
22633 })
22634 }))
22635 }
22636
22637 fn perform_rename(
22638 &self,
22639 buffer: &Entity<Buffer>,
22640 position: text::Anchor,
22641 new_name: String,
22642 cx: &mut App,
22643 ) -> Option<Task<Result<ProjectTransaction>>> {
22644 Some(self.update(cx, |project, cx| {
22645 project.perform_rename(buffer.clone(), position, new_name, cx)
22646 }))
22647 }
22648}
22649
22650fn inlay_hint_settings(
22651 location: Anchor,
22652 snapshot: &MultiBufferSnapshot,
22653 cx: &mut Context<Editor>,
22654) -> InlayHintSettings {
22655 let file = snapshot.file_at(location);
22656 let language = snapshot.language_at(location).map(|l| l.name());
22657 language_settings(language, file, cx).inlay_hints
22658}
22659
22660fn consume_contiguous_rows(
22661 contiguous_row_selections: &mut Vec<Selection<Point>>,
22662 selection: &Selection<Point>,
22663 display_map: &DisplaySnapshot,
22664 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22665) -> (MultiBufferRow, MultiBufferRow) {
22666 contiguous_row_selections.push(selection.clone());
22667 let start_row = starting_row(selection, display_map);
22668 let mut end_row = ending_row(selection, display_map);
22669
22670 while let Some(next_selection) = selections.peek() {
22671 if next_selection.start.row <= end_row.0 {
22672 end_row = ending_row(next_selection, display_map);
22673 contiguous_row_selections.push(selections.next().unwrap().clone());
22674 } else {
22675 break;
22676 }
22677 }
22678 (start_row, end_row)
22679}
22680
22681fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22682 if selection.start.column > 0 {
22683 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22684 } else {
22685 MultiBufferRow(selection.start.row)
22686 }
22687}
22688
22689fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22690 if next_selection.end.column > 0 || next_selection.is_empty() {
22691 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22692 } else {
22693 MultiBufferRow(next_selection.end.row)
22694 }
22695}
22696
22697impl EditorSnapshot {
22698 pub fn remote_selections_in_range<'a>(
22699 &'a self,
22700 range: &'a Range<Anchor>,
22701 collaboration_hub: &dyn CollaborationHub,
22702 cx: &'a App,
22703 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22704 let participant_names = collaboration_hub.user_names(cx);
22705 let participant_indices = collaboration_hub.user_participant_indices(cx);
22706 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22707 let collaborators_by_replica_id = collaborators_by_peer_id
22708 .values()
22709 .map(|collaborator| (collaborator.replica_id, collaborator))
22710 .collect::<HashMap<_, _>>();
22711 self.buffer_snapshot
22712 .selections_in_range(range, false)
22713 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22714 if replica_id == AGENT_REPLICA_ID {
22715 Some(RemoteSelection {
22716 replica_id,
22717 selection,
22718 cursor_shape,
22719 line_mode,
22720 collaborator_id: CollaboratorId::Agent,
22721 user_name: Some("Agent".into()),
22722 color: cx.theme().players().agent(),
22723 })
22724 } else {
22725 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22726 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22727 let user_name = participant_names.get(&collaborator.user_id).cloned();
22728 Some(RemoteSelection {
22729 replica_id,
22730 selection,
22731 cursor_shape,
22732 line_mode,
22733 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22734 user_name,
22735 color: if let Some(index) = participant_index {
22736 cx.theme().players().color_for_participant(index.0)
22737 } else {
22738 cx.theme().players().absent()
22739 },
22740 })
22741 }
22742 })
22743 }
22744
22745 pub fn hunks_for_ranges(
22746 &self,
22747 ranges: impl IntoIterator<Item = Range<Point>>,
22748 ) -> Vec<MultiBufferDiffHunk> {
22749 let mut hunks = Vec::new();
22750 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22751 HashMap::default();
22752 for query_range in ranges {
22753 let query_rows =
22754 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22755 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22756 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22757 ) {
22758 // Include deleted hunks that are adjacent to the query range, because
22759 // otherwise they would be missed.
22760 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22761 if hunk.status().is_deleted() {
22762 intersects_range |= hunk.row_range.start == query_rows.end;
22763 intersects_range |= hunk.row_range.end == query_rows.start;
22764 }
22765 if intersects_range {
22766 if !processed_buffer_rows
22767 .entry(hunk.buffer_id)
22768 .or_default()
22769 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22770 {
22771 continue;
22772 }
22773 hunks.push(hunk);
22774 }
22775 }
22776 }
22777
22778 hunks
22779 }
22780
22781 fn display_diff_hunks_for_rows<'a>(
22782 &'a self,
22783 display_rows: Range<DisplayRow>,
22784 folded_buffers: &'a HashSet<BufferId>,
22785 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22786 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22787 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22788
22789 self.buffer_snapshot
22790 .diff_hunks_in_range(buffer_start..buffer_end)
22791 .filter_map(|hunk| {
22792 if folded_buffers.contains(&hunk.buffer_id) {
22793 return None;
22794 }
22795
22796 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22797 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22798
22799 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22800 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22801
22802 let display_hunk = if hunk_display_start.column() != 0 {
22803 DisplayDiffHunk::Folded {
22804 display_row: hunk_display_start.row(),
22805 }
22806 } else {
22807 let mut end_row = hunk_display_end.row();
22808 if hunk_display_end.column() > 0 {
22809 end_row.0 += 1;
22810 }
22811 let is_created_file = hunk.is_created_file();
22812 DisplayDiffHunk::Unfolded {
22813 status: hunk.status(),
22814 diff_base_byte_range: hunk.diff_base_byte_range,
22815 display_row_range: hunk_display_start.row()..end_row,
22816 multi_buffer_range: Anchor::range_in_buffer(
22817 hunk.excerpt_id,
22818 hunk.buffer_id,
22819 hunk.buffer_range,
22820 ),
22821 is_created_file,
22822 }
22823 };
22824
22825 Some(display_hunk)
22826 })
22827 }
22828
22829 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22830 self.display_snapshot.buffer_snapshot.language_at(position)
22831 }
22832
22833 pub fn is_focused(&self) -> bool {
22834 self.is_focused
22835 }
22836
22837 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22838 self.placeholder_text.as_ref()
22839 }
22840
22841 pub fn scroll_position(&self) -> gpui::Point<f32> {
22842 self.scroll_anchor.scroll_position(&self.display_snapshot)
22843 }
22844
22845 fn gutter_dimensions(
22846 &self,
22847 font_id: FontId,
22848 font_size: Pixels,
22849 max_line_number_width: Pixels,
22850 cx: &App,
22851 ) -> Option<GutterDimensions> {
22852 if !self.show_gutter {
22853 return None;
22854 }
22855
22856 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22857 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22858
22859 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22860 matches!(
22861 ProjectSettings::get_global(cx).git.git_gutter,
22862 Some(GitGutterSetting::TrackedFiles)
22863 )
22864 });
22865 let gutter_settings = EditorSettings::get_global(cx).gutter;
22866 let show_line_numbers = self
22867 .show_line_numbers
22868 .unwrap_or(gutter_settings.line_numbers);
22869 let line_gutter_width = if show_line_numbers {
22870 // Avoid flicker-like gutter resizes when the line number gains another digit by
22871 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22872 let min_width_for_number_on_gutter =
22873 ch_advance * gutter_settings.min_line_number_digits as f32;
22874 max_line_number_width.max(min_width_for_number_on_gutter)
22875 } else {
22876 0.0.into()
22877 };
22878
22879 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22880 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22881
22882 let git_blame_entries_width =
22883 self.git_blame_gutter_max_author_length
22884 .map(|max_author_length| {
22885 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22886 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22887
22888 /// The number of characters to dedicate to gaps and margins.
22889 const SPACING_WIDTH: usize = 4;
22890
22891 let max_char_count = max_author_length.min(renderer.max_author_length())
22892 + ::git::SHORT_SHA_LENGTH
22893 + MAX_RELATIVE_TIMESTAMP.len()
22894 + SPACING_WIDTH;
22895
22896 ch_advance * max_char_count
22897 });
22898
22899 let is_singleton = self.buffer_snapshot.is_singleton();
22900
22901 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22902 left_padding += if !is_singleton {
22903 ch_width * 4.0
22904 } else if show_runnables || show_breakpoints {
22905 ch_width * 3.0
22906 } else if show_git_gutter && show_line_numbers {
22907 ch_width * 2.0
22908 } else if show_git_gutter || show_line_numbers {
22909 ch_width
22910 } else {
22911 px(0.)
22912 };
22913
22914 let shows_folds = is_singleton && gutter_settings.folds;
22915
22916 let right_padding = if shows_folds && show_line_numbers {
22917 ch_width * 4.0
22918 } else if shows_folds || (!is_singleton && show_line_numbers) {
22919 ch_width * 3.0
22920 } else if show_line_numbers {
22921 ch_width
22922 } else {
22923 px(0.)
22924 };
22925
22926 Some(GutterDimensions {
22927 left_padding,
22928 right_padding,
22929 width: line_gutter_width + left_padding + right_padding,
22930 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22931 git_blame_entries_width,
22932 })
22933 }
22934
22935 pub fn render_crease_toggle(
22936 &self,
22937 buffer_row: MultiBufferRow,
22938 row_contains_cursor: bool,
22939 editor: Entity<Editor>,
22940 window: &mut Window,
22941 cx: &mut App,
22942 ) -> Option<AnyElement> {
22943 let folded = self.is_line_folded(buffer_row);
22944 let mut is_foldable = false;
22945
22946 if let Some(crease) = self
22947 .crease_snapshot
22948 .query_row(buffer_row, &self.buffer_snapshot)
22949 {
22950 is_foldable = true;
22951 match crease {
22952 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22953 if let Some(render_toggle) = render_toggle {
22954 let toggle_callback =
22955 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22956 if folded {
22957 editor.update(cx, |editor, cx| {
22958 editor.fold_at(buffer_row, window, cx)
22959 });
22960 } else {
22961 editor.update(cx, |editor, cx| {
22962 editor.unfold_at(buffer_row, window, cx)
22963 });
22964 }
22965 });
22966 return Some((render_toggle)(
22967 buffer_row,
22968 folded,
22969 toggle_callback,
22970 window,
22971 cx,
22972 ));
22973 }
22974 }
22975 }
22976 }
22977
22978 is_foldable |= self.starts_indent(buffer_row);
22979
22980 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22981 Some(
22982 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22983 .toggle_state(folded)
22984 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22985 if folded {
22986 this.unfold_at(buffer_row, window, cx);
22987 } else {
22988 this.fold_at(buffer_row, window, cx);
22989 }
22990 }))
22991 .into_any_element(),
22992 )
22993 } else {
22994 None
22995 }
22996 }
22997
22998 pub fn render_crease_trailer(
22999 &self,
23000 buffer_row: MultiBufferRow,
23001 window: &mut Window,
23002 cx: &mut App,
23003 ) -> Option<AnyElement> {
23004 let folded = self.is_line_folded(buffer_row);
23005 if let Crease::Inline { render_trailer, .. } = self
23006 .crease_snapshot
23007 .query_row(buffer_row, &self.buffer_snapshot)?
23008 {
23009 let render_trailer = render_trailer.as_ref()?;
23010 Some(render_trailer(buffer_row, folded, window, cx))
23011 } else {
23012 None
23013 }
23014 }
23015}
23016
23017impl Deref for EditorSnapshot {
23018 type Target = DisplaySnapshot;
23019
23020 fn deref(&self) -> &Self::Target {
23021 &self.display_snapshot
23022 }
23023}
23024
23025#[derive(Clone, Debug, PartialEq, Eq)]
23026pub enum EditorEvent {
23027 InputIgnored {
23028 text: Arc<str>,
23029 },
23030 InputHandled {
23031 utf16_range_to_replace: Option<Range<isize>>,
23032 text: Arc<str>,
23033 },
23034 ExcerptsAdded {
23035 buffer: Entity<Buffer>,
23036 predecessor: ExcerptId,
23037 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23038 },
23039 ExcerptsRemoved {
23040 ids: Vec<ExcerptId>,
23041 removed_buffer_ids: Vec<BufferId>,
23042 },
23043 BufferFoldToggled {
23044 ids: Vec<ExcerptId>,
23045 folded: bool,
23046 },
23047 ExcerptsEdited {
23048 ids: Vec<ExcerptId>,
23049 },
23050 ExcerptsExpanded {
23051 ids: Vec<ExcerptId>,
23052 },
23053 BufferEdited,
23054 Edited {
23055 transaction_id: clock::Lamport,
23056 },
23057 Reparsed(BufferId),
23058 Focused,
23059 FocusedIn,
23060 Blurred,
23061 DirtyChanged,
23062 Saved,
23063 TitleChanged,
23064 DiffBaseChanged,
23065 SelectionsChanged {
23066 local: bool,
23067 },
23068 ScrollPositionChanged {
23069 local: bool,
23070 autoscroll: bool,
23071 },
23072 Closed,
23073 TransactionUndone {
23074 transaction_id: clock::Lamport,
23075 },
23076 TransactionBegun {
23077 transaction_id: clock::Lamport,
23078 },
23079 Reloaded,
23080 CursorShapeChanged,
23081 BreadcrumbsChanged,
23082 PushedToNavHistory {
23083 anchor: Anchor,
23084 is_deactivate: bool,
23085 },
23086}
23087
23088impl EventEmitter<EditorEvent> for Editor {}
23089
23090impl Focusable for Editor {
23091 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23092 self.focus_handle.clone()
23093 }
23094}
23095
23096impl Render for Editor {
23097 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23098 let settings = ThemeSettings::get_global(cx);
23099
23100 let mut text_style = match self.mode {
23101 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23102 color: cx.theme().colors().editor_foreground,
23103 font_family: settings.ui_font.family.clone(),
23104 font_features: settings.ui_font.features.clone(),
23105 font_fallbacks: settings.ui_font.fallbacks.clone(),
23106 font_size: rems(0.875).into(),
23107 font_weight: settings.ui_font.weight,
23108 line_height: relative(settings.buffer_line_height.value()),
23109 ..Default::default()
23110 },
23111 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23112 color: cx.theme().colors().editor_foreground,
23113 font_family: settings.buffer_font.family.clone(),
23114 font_features: settings.buffer_font.features.clone(),
23115 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23116 font_size: settings.buffer_font_size(cx).into(),
23117 font_weight: settings.buffer_font.weight,
23118 line_height: relative(settings.buffer_line_height.value()),
23119 ..Default::default()
23120 },
23121 };
23122 if let Some(text_style_refinement) = &self.text_style_refinement {
23123 text_style.refine(text_style_refinement)
23124 }
23125
23126 let background = match self.mode {
23127 EditorMode::SingleLine => cx.theme().system().transparent,
23128 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23129 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23130 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23131 };
23132
23133 EditorElement::new(
23134 &cx.entity(),
23135 EditorStyle {
23136 background,
23137 border: cx.theme().colors().border,
23138 local_player: cx.theme().players().local(),
23139 text: text_style,
23140 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23141 syntax: cx.theme().syntax().clone(),
23142 status: cx.theme().status().clone(),
23143 inlay_hints_style: make_inlay_hints_style(cx),
23144 edit_prediction_styles: make_suggestion_styles(cx),
23145 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23146 show_underlines: self.diagnostics_enabled(),
23147 },
23148 )
23149 }
23150}
23151
23152impl EntityInputHandler for Editor {
23153 fn text_for_range(
23154 &mut self,
23155 range_utf16: Range<usize>,
23156 adjusted_range: &mut Option<Range<usize>>,
23157 _: &mut Window,
23158 cx: &mut Context<Self>,
23159 ) -> Option<String> {
23160 let snapshot = self.buffer.read(cx).read(cx);
23161 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23162 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23163 if (start.0..end.0) != range_utf16 {
23164 adjusted_range.replace(start.0..end.0);
23165 }
23166 Some(snapshot.text_for_range(start..end).collect())
23167 }
23168
23169 fn selected_text_range(
23170 &mut self,
23171 ignore_disabled_input: bool,
23172 _: &mut Window,
23173 cx: &mut Context<Self>,
23174 ) -> Option<UTF16Selection> {
23175 // Prevent the IME menu from appearing when holding down an alphabetic key
23176 // while input is disabled.
23177 if !ignore_disabled_input && !self.input_enabled {
23178 return None;
23179 }
23180
23181 let selection = self.selections.newest::<OffsetUtf16>(cx);
23182 let range = selection.range();
23183
23184 Some(UTF16Selection {
23185 range: range.start.0..range.end.0,
23186 reversed: selection.reversed,
23187 })
23188 }
23189
23190 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23191 let snapshot = self.buffer.read(cx).read(cx);
23192 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23193 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23194 }
23195
23196 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23197 self.clear_highlights::<InputComposition>(cx);
23198 self.ime_transaction.take();
23199 }
23200
23201 fn replace_text_in_range(
23202 &mut self,
23203 range_utf16: Option<Range<usize>>,
23204 text: &str,
23205 window: &mut Window,
23206 cx: &mut Context<Self>,
23207 ) {
23208 if !self.input_enabled {
23209 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23210 return;
23211 }
23212
23213 self.transact(window, cx, |this, window, cx| {
23214 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23215 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23216 Some(this.selection_replacement_ranges(range_utf16, cx))
23217 } else {
23218 this.marked_text_ranges(cx)
23219 };
23220
23221 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23222 let newest_selection_id = this.selections.newest_anchor().id;
23223 this.selections
23224 .all::<OffsetUtf16>(cx)
23225 .iter()
23226 .zip(ranges_to_replace.iter())
23227 .find_map(|(selection, range)| {
23228 if selection.id == newest_selection_id {
23229 Some(
23230 (range.start.0 as isize - selection.head().0 as isize)
23231 ..(range.end.0 as isize - selection.head().0 as isize),
23232 )
23233 } else {
23234 None
23235 }
23236 })
23237 });
23238
23239 cx.emit(EditorEvent::InputHandled {
23240 utf16_range_to_replace: range_to_replace,
23241 text: text.into(),
23242 });
23243
23244 if let Some(new_selected_ranges) = new_selected_ranges {
23245 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23246 selections.select_ranges(new_selected_ranges)
23247 });
23248 this.backspace(&Default::default(), window, cx);
23249 }
23250
23251 this.handle_input(text, window, cx);
23252 });
23253
23254 if let Some(transaction) = self.ime_transaction {
23255 self.buffer.update(cx, |buffer, cx| {
23256 buffer.group_until_transaction(transaction, cx);
23257 });
23258 }
23259
23260 self.unmark_text(window, cx);
23261 }
23262
23263 fn replace_and_mark_text_in_range(
23264 &mut self,
23265 range_utf16: Option<Range<usize>>,
23266 text: &str,
23267 new_selected_range_utf16: Option<Range<usize>>,
23268 window: &mut Window,
23269 cx: &mut Context<Self>,
23270 ) {
23271 if !self.input_enabled {
23272 return;
23273 }
23274
23275 let transaction = self.transact(window, cx, |this, window, cx| {
23276 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23277 let snapshot = this.buffer.read(cx).read(cx);
23278 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23279 for marked_range in &mut marked_ranges {
23280 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23281 marked_range.start.0 += relative_range_utf16.start;
23282 marked_range.start =
23283 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23284 marked_range.end =
23285 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23286 }
23287 }
23288 Some(marked_ranges)
23289 } else if let Some(range_utf16) = range_utf16 {
23290 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23291 Some(this.selection_replacement_ranges(range_utf16, cx))
23292 } else {
23293 None
23294 };
23295
23296 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23297 let newest_selection_id = this.selections.newest_anchor().id;
23298 this.selections
23299 .all::<OffsetUtf16>(cx)
23300 .iter()
23301 .zip(ranges_to_replace.iter())
23302 .find_map(|(selection, range)| {
23303 if selection.id == newest_selection_id {
23304 Some(
23305 (range.start.0 as isize - selection.head().0 as isize)
23306 ..(range.end.0 as isize - selection.head().0 as isize),
23307 )
23308 } else {
23309 None
23310 }
23311 })
23312 });
23313
23314 cx.emit(EditorEvent::InputHandled {
23315 utf16_range_to_replace: range_to_replace,
23316 text: text.into(),
23317 });
23318
23319 if let Some(ranges) = ranges_to_replace {
23320 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23321 s.select_ranges(ranges)
23322 });
23323 }
23324
23325 let marked_ranges = {
23326 let snapshot = this.buffer.read(cx).read(cx);
23327 this.selections
23328 .disjoint_anchors()
23329 .iter()
23330 .map(|selection| {
23331 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23332 })
23333 .collect::<Vec<_>>()
23334 };
23335
23336 if text.is_empty() {
23337 this.unmark_text(window, cx);
23338 } else {
23339 this.highlight_text::<InputComposition>(
23340 marked_ranges.clone(),
23341 HighlightStyle {
23342 underline: Some(UnderlineStyle {
23343 thickness: px(1.),
23344 color: None,
23345 wavy: false,
23346 }),
23347 ..Default::default()
23348 },
23349 cx,
23350 );
23351 }
23352
23353 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23354 let use_autoclose = this.use_autoclose;
23355 let use_auto_surround = this.use_auto_surround;
23356 this.set_use_autoclose(false);
23357 this.set_use_auto_surround(false);
23358 this.handle_input(text, window, cx);
23359 this.set_use_autoclose(use_autoclose);
23360 this.set_use_auto_surround(use_auto_surround);
23361
23362 if let Some(new_selected_range) = new_selected_range_utf16 {
23363 let snapshot = this.buffer.read(cx).read(cx);
23364 let new_selected_ranges = marked_ranges
23365 .into_iter()
23366 .map(|marked_range| {
23367 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23368 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23369 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23370 snapshot.clip_offset_utf16(new_start, Bias::Left)
23371 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23372 })
23373 .collect::<Vec<_>>();
23374
23375 drop(snapshot);
23376 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23377 selections.select_ranges(new_selected_ranges)
23378 });
23379 }
23380 });
23381
23382 self.ime_transaction = self.ime_transaction.or(transaction);
23383 if let Some(transaction) = self.ime_transaction {
23384 self.buffer.update(cx, |buffer, cx| {
23385 buffer.group_until_transaction(transaction, cx);
23386 });
23387 }
23388
23389 if self.text_highlights::<InputComposition>(cx).is_none() {
23390 self.ime_transaction.take();
23391 }
23392 }
23393
23394 fn bounds_for_range(
23395 &mut self,
23396 range_utf16: Range<usize>,
23397 element_bounds: gpui::Bounds<Pixels>,
23398 window: &mut Window,
23399 cx: &mut Context<Self>,
23400 ) -> Option<gpui::Bounds<Pixels>> {
23401 let text_layout_details = self.text_layout_details(window);
23402 let CharacterDimensions {
23403 em_width,
23404 em_advance,
23405 line_height,
23406 } = self.character_dimensions(window);
23407
23408 let snapshot = self.snapshot(window, cx);
23409 let scroll_position = snapshot.scroll_position();
23410 let scroll_left = scroll_position.x * em_advance;
23411
23412 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23413 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23414 + self.gutter_dimensions.full_width();
23415 let y = line_height * (start.row().as_f32() - scroll_position.y);
23416
23417 Some(Bounds {
23418 origin: element_bounds.origin + point(x, y),
23419 size: size(em_width, line_height),
23420 })
23421 }
23422
23423 fn character_index_for_point(
23424 &mut self,
23425 point: gpui::Point<Pixels>,
23426 _window: &mut Window,
23427 _cx: &mut Context<Self>,
23428 ) -> Option<usize> {
23429 let position_map = self.last_position_map.as_ref()?;
23430 if !position_map.text_hitbox.contains(&point) {
23431 return None;
23432 }
23433 let display_point = position_map.point_for_position(point).previous_valid;
23434 let anchor = position_map
23435 .snapshot
23436 .display_point_to_anchor(display_point, Bias::Left);
23437 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23438 Some(utf16_offset.0)
23439 }
23440}
23441
23442trait SelectionExt {
23443 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23444 fn spanned_rows(
23445 &self,
23446 include_end_if_at_line_start: bool,
23447 map: &DisplaySnapshot,
23448 ) -> Range<MultiBufferRow>;
23449}
23450
23451impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23452 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23453 let start = self
23454 .start
23455 .to_point(&map.buffer_snapshot)
23456 .to_display_point(map);
23457 let end = self
23458 .end
23459 .to_point(&map.buffer_snapshot)
23460 .to_display_point(map);
23461 if self.reversed {
23462 end..start
23463 } else {
23464 start..end
23465 }
23466 }
23467
23468 fn spanned_rows(
23469 &self,
23470 include_end_if_at_line_start: bool,
23471 map: &DisplaySnapshot,
23472 ) -> Range<MultiBufferRow> {
23473 let start = self.start.to_point(&map.buffer_snapshot);
23474 let mut end = self.end.to_point(&map.buffer_snapshot);
23475 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23476 end.row -= 1;
23477 }
23478
23479 let buffer_start = map.prev_line_boundary(start).0;
23480 let buffer_end = map.next_line_boundary(end).0;
23481 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23482 }
23483}
23484
23485impl<T: InvalidationRegion> InvalidationStack<T> {
23486 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23487 where
23488 S: Clone + ToOffset,
23489 {
23490 while let Some(region) = self.last() {
23491 let all_selections_inside_invalidation_ranges =
23492 if selections.len() == region.ranges().len() {
23493 selections
23494 .iter()
23495 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23496 .all(|(selection, invalidation_range)| {
23497 let head = selection.head().to_offset(buffer);
23498 invalidation_range.start <= head && invalidation_range.end >= head
23499 })
23500 } else {
23501 false
23502 };
23503
23504 if all_selections_inside_invalidation_ranges {
23505 break;
23506 } else {
23507 self.pop();
23508 }
23509 }
23510 }
23511}
23512
23513impl<T> Default for InvalidationStack<T> {
23514 fn default() -> Self {
23515 Self(Default::default())
23516 }
23517}
23518
23519impl<T> Deref for InvalidationStack<T> {
23520 type Target = Vec<T>;
23521
23522 fn deref(&self) -> &Self::Target {
23523 &self.0
23524 }
23525}
23526
23527impl<T> DerefMut for InvalidationStack<T> {
23528 fn deref_mut(&mut self) -> &mut Self::Target {
23529 &mut self.0
23530 }
23531}
23532
23533impl InvalidationRegion for SnippetState {
23534 fn ranges(&self) -> &[Range<Anchor>] {
23535 &self.ranges[self.active_index]
23536 }
23537}
23538
23539fn edit_prediction_edit_text(
23540 current_snapshot: &BufferSnapshot,
23541 edits: &[(Range<Anchor>, String)],
23542 edit_preview: &EditPreview,
23543 include_deletions: bool,
23544 cx: &App,
23545) -> HighlightedText {
23546 let edits = edits
23547 .iter()
23548 .map(|(anchor, text)| {
23549 (
23550 anchor.start.text_anchor..anchor.end.text_anchor,
23551 text.clone(),
23552 )
23553 })
23554 .collect::<Vec<_>>();
23555
23556 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23557}
23558
23559fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23560 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23561 // Just show the raw edit text with basic styling
23562 let mut text = String::new();
23563 let mut highlights = Vec::new();
23564
23565 let insertion_highlight_style = HighlightStyle {
23566 color: Some(cx.theme().colors().text),
23567 ..Default::default()
23568 };
23569
23570 for (_, edit_text) in edits {
23571 let start_offset = text.len();
23572 text.push_str(edit_text);
23573 let end_offset = text.len();
23574
23575 if start_offset < end_offset {
23576 highlights.push((start_offset..end_offset, insertion_highlight_style));
23577 }
23578 }
23579
23580 HighlightedText {
23581 text: text.into(),
23582 highlights,
23583 }
23584}
23585
23586pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23587 match severity {
23588 lsp::DiagnosticSeverity::ERROR => colors.error,
23589 lsp::DiagnosticSeverity::WARNING => colors.warning,
23590 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23591 lsp::DiagnosticSeverity::HINT => colors.info,
23592 _ => colors.ignored,
23593 }
23594}
23595
23596pub fn styled_runs_for_code_label<'a>(
23597 label: &'a CodeLabel,
23598 syntax_theme: &'a theme::SyntaxTheme,
23599) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23600 let fade_out = HighlightStyle {
23601 fade_out: Some(0.35),
23602 ..Default::default()
23603 };
23604
23605 let mut prev_end = label.filter_range.end;
23606 label
23607 .runs
23608 .iter()
23609 .enumerate()
23610 .flat_map(move |(ix, (range, highlight_id))| {
23611 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23612 style
23613 } else {
23614 return Default::default();
23615 };
23616 let mut muted_style = style;
23617 muted_style.highlight(fade_out);
23618
23619 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23620 if range.start >= label.filter_range.end {
23621 if range.start > prev_end {
23622 runs.push((prev_end..range.start, fade_out));
23623 }
23624 runs.push((range.clone(), muted_style));
23625 } else if range.end <= label.filter_range.end {
23626 runs.push((range.clone(), style));
23627 } else {
23628 runs.push((range.start..label.filter_range.end, style));
23629 runs.push((label.filter_range.end..range.end, muted_style));
23630 }
23631 prev_end = cmp::max(prev_end, range.end);
23632
23633 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23634 runs.push((prev_end..label.text.len(), fade_out));
23635 }
23636
23637 runs
23638 })
23639}
23640
23641pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23642 let mut prev_index = 0;
23643 let mut prev_codepoint: Option<char> = None;
23644 text.char_indices()
23645 .chain([(text.len(), '\0')])
23646 .filter_map(move |(index, codepoint)| {
23647 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23648 let is_boundary = index == text.len()
23649 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23650 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23651 if is_boundary {
23652 let chunk = &text[prev_index..index];
23653 prev_index = index;
23654 Some(chunk)
23655 } else {
23656 None
23657 }
23658 })
23659}
23660
23661pub trait RangeToAnchorExt: Sized {
23662 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23663
23664 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23665 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23666 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23667 }
23668}
23669
23670impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23671 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23672 let start_offset = self.start.to_offset(snapshot);
23673 let end_offset = self.end.to_offset(snapshot);
23674 if start_offset == end_offset {
23675 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23676 } else {
23677 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23678 }
23679 }
23680}
23681
23682pub trait RowExt {
23683 fn as_f32(&self) -> f32;
23684
23685 fn next_row(&self) -> Self;
23686
23687 fn previous_row(&self) -> Self;
23688
23689 fn minus(&self, other: Self) -> u32;
23690}
23691
23692impl RowExt for DisplayRow {
23693 fn as_f32(&self) -> f32 {
23694 self.0 as f32
23695 }
23696
23697 fn next_row(&self) -> Self {
23698 Self(self.0 + 1)
23699 }
23700
23701 fn previous_row(&self) -> Self {
23702 Self(self.0.saturating_sub(1))
23703 }
23704
23705 fn minus(&self, other: Self) -> u32 {
23706 self.0 - other.0
23707 }
23708}
23709
23710impl RowExt for MultiBufferRow {
23711 fn as_f32(&self) -> f32 {
23712 self.0 as f32
23713 }
23714
23715 fn next_row(&self) -> Self {
23716 Self(self.0 + 1)
23717 }
23718
23719 fn previous_row(&self) -> Self {
23720 Self(self.0.saturating_sub(1))
23721 }
23722
23723 fn minus(&self, other: Self) -> u32 {
23724 self.0 - other.0
23725 }
23726}
23727
23728trait RowRangeExt {
23729 type Row;
23730
23731 fn len(&self) -> usize;
23732
23733 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23734}
23735
23736impl RowRangeExt for Range<MultiBufferRow> {
23737 type Row = MultiBufferRow;
23738
23739 fn len(&self) -> usize {
23740 (self.end.0 - self.start.0) as usize
23741 }
23742
23743 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23744 (self.start.0..self.end.0).map(MultiBufferRow)
23745 }
23746}
23747
23748impl RowRangeExt for Range<DisplayRow> {
23749 type Row = DisplayRow;
23750
23751 fn len(&self) -> usize {
23752 (self.end.0 - self.start.0) as usize
23753 }
23754
23755 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23756 (self.start.0..self.end.0).map(DisplayRow)
23757 }
23758}
23759
23760/// If select range has more than one line, we
23761/// just point the cursor to range.start.
23762fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23763 if range.start.row == range.end.row {
23764 range
23765 } else {
23766 range.start..range.start
23767 }
23768}
23769pub struct KillRing(ClipboardItem);
23770impl Global for KillRing {}
23771
23772const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23773
23774enum BreakpointPromptEditAction {
23775 Log,
23776 Condition,
23777 HitCondition,
23778}
23779
23780struct BreakpointPromptEditor {
23781 pub(crate) prompt: Entity<Editor>,
23782 editor: WeakEntity<Editor>,
23783 breakpoint_anchor: Anchor,
23784 breakpoint: Breakpoint,
23785 edit_action: BreakpointPromptEditAction,
23786 block_ids: HashSet<CustomBlockId>,
23787 editor_margins: Arc<Mutex<EditorMargins>>,
23788 _subscriptions: Vec<Subscription>,
23789}
23790
23791impl BreakpointPromptEditor {
23792 const MAX_LINES: u8 = 4;
23793
23794 fn new(
23795 editor: WeakEntity<Editor>,
23796 breakpoint_anchor: Anchor,
23797 breakpoint: Breakpoint,
23798 edit_action: BreakpointPromptEditAction,
23799 window: &mut Window,
23800 cx: &mut Context<Self>,
23801 ) -> Self {
23802 let base_text = match edit_action {
23803 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23804 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23805 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23806 }
23807 .map(|msg| msg.to_string())
23808 .unwrap_or_default();
23809
23810 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23811 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23812
23813 let prompt = cx.new(|cx| {
23814 let mut prompt = Editor::new(
23815 EditorMode::AutoHeight {
23816 min_lines: 1,
23817 max_lines: Some(Self::MAX_LINES as usize),
23818 },
23819 buffer,
23820 None,
23821 window,
23822 cx,
23823 );
23824 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23825 prompt.set_show_cursor_when_unfocused(false, cx);
23826 prompt.set_placeholder_text(
23827 match edit_action {
23828 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23829 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23830 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23831 },
23832 cx,
23833 );
23834
23835 prompt
23836 });
23837
23838 Self {
23839 prompt,
23840 editor,
23841 breakpoint_anchor,
23842 breakpoint,
23843 edit_action,
23844 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23845 block_ids: Default::default(),
23846 _subscriptions: vec![],
23847 }
23848 }
23849
23850 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23851 self.block_ids.extend(block_ids)
23852 }
23853
23854 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23855 if let Some(editor) = self.editor.upgrade() {
23856 let message = self
23857 .prompt
23858 .read(cx)
23859 .buffer
23860 .read(cx)
23861 .as_singleton()
23862 .expect("A multi buffer in breakpoint prompt isn't possible")
23863 .read(cx)
23864 .as_rope()
23865 .to_string();
23866
23867 editor.update(cx, |editor, cx| {
23868 editor.edit_breakpoint_at_anchor(
23869 self.breakpoint_anchor,
23870 self.breakpoint.clone(),
23871 match self.edit_action {
23872 BreakpointPromptEditAction::Log => {
23873 BreakpointEditAction::EditLogMessage(message.into())
23874 }
23875 BreakpointPromptEditAction::Condition => {
23876 BreakpointEditAction::EditCondition(message.into())
23877 }
23878 BreakpointPromptEditAction::HitCondition => {
23879 BreakpointEditAction::EditHitCondition(message.into())
23880 }
23881 },
23882 cx,
23883 );
23884
23885 editor.remove_blocks(self.block_ids.clone(), None, cx);
23886 cx.focus_self(window);
23887 });
23888 }
23889 }
23890
23891 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23892 self.editor
23893 .update(cx, |editor, cx| {
23894 editor.remove_blocks(self.block_ids.clone(), None, cx);
23895 window.focus(&editor.focus_handle);
23896 })
23897 .log_err();
23898 }
23899
23900 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23901 let settings = ThemeSettings::get_global(cx);
23902 let text_style = TextStyle {
23903 color: if self.prompt.read(cx).read_only(cx) {
23904 cx.theme().colors().text_disabled
23905 } else {
23906 cx.theme().colors().text
23907 },
23908 font_family: settings.buffer_font.family.clone(),
23909 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23910 font_size: settings.buffer_font_size(cx).into(),
23911 font_weight: settings.buffer_font.weight,
23912 line_height: relative(settings.buffer_line_height.value()),
23913 ..Default::default()
23914 };
23915 EditorElement::new(
23916 &self.prompt,
23917 EditorStyle {
23918 background: cx.theme().colors().editor_background,
23919 local_player: cx.theme().players().local(),
23920 text: text_style,
23921 ..Default::default()
23922 },
23923 )
23924 }
23925}
23926
23927impl Render for BreakpointPromptEditor {
23928 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23929 let editor_margins = *self.editor_margins.lock();
23930 let gutter_dimensions = editor_margins.gutter;
23931 h_flex()
23932 .key_context("Editor")
23933 .bg(cx.theme().colors().editor_background)
23934 .border_y_1()
23935 .border_color(cx.theme().status().info_border)
23936 .size_full()
23937 .py(window.line_height() / 2.5)
23938 .on_action(cx.listener(Self::confirm))
23939 .on_action(cx.listener(Self::cancel))
23940 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23941 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23942 }
23943}
23944
23945impl Focusable for BreakpointPromptEditor {
23946 fn focus_handle(&self, cx: &App) -> FocusHandle {
23947 self.prompt.focus_handle(cx)
23948 }
23949}
23950
23951fn all_edits_insertions_or_deletions(
23952 edits: &Vec<(Range<Anchor>, String)>,
23953 snapshot: &MultiBufferSnapshot,
23954) -> bool {
23955 let mut all_insertions = true;
23956 let mut all_deletions = true;
23957
23958 for (range, new_text) in edits.iter() {
23959 let range_is_empty = range.to_offset(snapshot).is_empty();
23960 let text_is_empty = new_text.is_empty();
23961
23962 if range_is_empty != text_is_empty {
23963 if range_is_empty {
23964 all_deletions = false;
23965 } else {
23966 all_insertions = false;
23967 }
23968 } else {
23969 return false;
23970 }
23971
23972 if !all_insertions && !all_deletions {
23973 return false;
23974 }
23975 }
23976 all_insertions || all_deletions
23977}
23978
23979struct MissingEditPredictionKeybindingTooltip;
23980
23981impl Render for MissingEditPredictionKeybindingTooltip {
23982 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23983 ui::tooltip_container(window, cx, |container, _, cx| {
23984 container
23985 .flex_shrink_0()
23986 .max_w_80()
23987 .min_h(rems_from_px(124.))
23988 .justify_between()
23989 .child(
23990 v_flex()
23991 .flex_1()
23992 .text_ui_sm(cx)
23993 .child(Label::new("Conflict with Accept Keybinding"))
23994 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23995 )
23996 .child(
23997 h_flex()
23998 .pb_1()
23999 .gap_1()
24000 .items_end()
24001 .w_full()
24002 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24003 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24004 }))
24005 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24006 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24007 })),
24008 )
24009 })
24010 }
24011}
24012
24013#[derive(Debug, Clone, Copy, PartialEq)]
24014pub struct LineHighlight {
24015 pub background: Background,
24016 pub border: Option<gpui::Hsla>,
24017 pub include_gutter: bool,
24018 pub type_id: Option<TypeId>,
24019}
24020
24021struct LineManipulationResult {
24022 pub new_text: String,
24023 pub line_count_before: usize,
24024 pub line_count_after: usize,
24025}
24026
24027fn render_diff_hunk_controls(
24028 row: u32,
24029 status: &DiffHunkStatus,
24030 hunk_range: Range<Anchor>,
24031 is_created_file: bool,
24032 line_height: Pixels,
24033 editor: &Entity<Editor>,
24034 _window: &mut Window,
24035 cx: &mut App,
24036) -> AnyElement {
24037 h_flex()
24038 .h(line_height)
24039 .mr_1()
24040 .gap_1()
24041 .px_0p5()
24042 .pb_1()
24043 .border_x_1()
24044 .border_b_1()
24045 .border_color(cx.theme().colors().border_variant)
24046 .rounded_b_lg()
24047 .bg(cx.theme().colors().editor_background)
24048 .gap_1()
24049 .block_mouse_except_scroll()
24050 .shadow_md()
24051 .child(if status.has_secondary_hunk() {
24052 Button::new(("stage", row as u64), "Stage")
24053 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24054 .tooltip({
24055 let focus_handle = editor.focus_handle(cx);
24056 move |window, cx| {
24057 Tooltip::for_action_in(
24058 "Stage Hunk",
24059 &::git::ToggleStaged,
24060 &focus_handle,
24061 window,
24062 cx,
24063 )
24064 }
24065 })
24066 .on_click({
24067 let editor = editor.clone();
24068 move |_event, _window, cx| {
24069 editor.update(cx, |editor, cx| {
24070 editor.stage_or_unstage_diff_hunks(
24071 true,
24072 vec![hunk_range.start..hunk_range.start],
24073 cx,
24074 );
24075 });
24076 }
24077 })
24078 } else {
24079 Button::new(("unstage", row as u64), "Unstage")
24080 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24081 .tooltip({
24082 let focus_handle = editor.focus_handle(cx);
24083 move |window, cx| {
24084 Tooltip::for_action_in(
24085 "Unstage Hunk",
24086 &::git::ToggleStaged,
24087 &focus_handle,
24088 window,
24089 cx,
24090 )
24091 }
24092 })
24093 .on_click({
24094 let editor = editor.clone();
24095 move |_event, _window, cx| {
24096 editor.update(cx, |editor, cx| {
24097 editor.stage_or_unstage_diff_hunks(
24098 false,
24099 vec![hunk_range.start..hunk_range.start],
24100 cx,
24101 );
24102 });
24103 }
24104 })
24105 })
24106 .child(
24107 Button::new(("restore", row as u64), "Restore")
24108 .tooltip({
24109 let focus_handle = editor.focus_handle(cx);
24110 move |window, cx| {
24111 Tooltip::for_action_in(
24112 "Restore Hunk",
24113 &::git::Restore,
24114 &focus_handle,
24115 window,
24116 cx,
24117 )
24118 }
24119 })
24120 .on_click({
24121 let editor = editor.clone();
24122 move |_event, window, cx| {
24123 editor.update(cx, |editor, cx| {
24124 let snapshot = editor.snapshot(window, cx);
24125 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24126 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24127 });
24128 }
24129 })
24130 .disabled(is_created_file),
24131 )
24132 .when(
24133 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24134 |el| {
24135 el.child(
24136 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24137 .shape(IconButtonShape::Square)
24138 .icon_size(IconSize::Small)
24139 // .disabled(!has_multiple_hunks)
24140 .tooltip({
24141 let focus_handle = editor.focus_handle(cx);
24142 move |window, cx| {
24143 Tooltip::for_action_in(
24144 "Next Hunk",
24145 &GoToHunk,
24146 &focus_handle,
24147 window,
24148 cx,
24149 )
24150 }
24151 })
24152 .on_click({
24153 let editor = editor.clone();
24154 move |_event, window, cx| {
24155 editor.update(cx, |editor, cx| {
24156 let snapshot = editor.snapshot(window, cx);
24157 let position =
24158 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24159 editor.go_to_hunk_before_or_after_position(
24160 &snapshot,
24161 position,
24162 Direction::Next,
24163 window,
24164 cx,
24165 );
24166 editor.expand_selected_diff_hunks(cx);
24167 });
24168 }
24169 }),
24170 )
24171 .child(
24172 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24173 .shape(IconButtonShape::Square)
24174 .icon_size(IconSize::Small)
24175 // .disabled(!has_multiple_hunks)
24176 .tooltip({
24177 let focus_handle = editor.focus_handle(cx);
24178 move |window, cx| {
24179 Tooltip::for_action_in(
24180 "Previous Hunk",
24181 &GoToPreviousHunk,
24182 &focus_handle,
24183 window,
24184 cx,
24185 )
24186 }
24187 })
24188 .on_click({
24189 let editor = editor.clone();
24190 move |_event, window, cx| {
24191 editor.update(cx, |editor, cx| {
24192 let snapshot = editor.snapshot(window, cx);
24193 let point =
24194 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24195 editor.go_to_hunk_before_or_after_position(
24196 &snapshot,
24197 point,
24198 Direction::Prev,
24199 window,
24200 cx,
24201 );
24202 editor.expand_selected_diff_hunks(cx);
24203 });
24204 }
24205 }),
24206 )
24207 },
24208 )
24209 .into_any_element()
24210}
24211
24212pub fn multibuffer_context_lines(cx: &App) -> u32 {
24213 EditorSettings::try_get(cx)
24214 .map(|settings| settings.excerpt_context_lines)
24215 .unwrap_or(2)
24216 .clamp(1, 32)
24217}