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 editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, Capability, CharKind,
113 CodeLabel, CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview,
114 HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection,
115 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216use zed_actions;
217
218use crate::{
219 code_context_menus::CompletionsMenuSource,
220 hover_links::{find_url, find_url_from_range},
221};
222use crate::{
223 editor_settings::MultiCursorModifier,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
230const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
231const MAX_LINE_LEN: usize = 1024;
232const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
233const MAX_SELECTION_HISTORY_LEN: usize = 1024;
234pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
235#[doc(hidden)]
236pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
237const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
238
239pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
241pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 DebuggerValue(usize),
280 // LSP
281 Hint(usize),
282 Color(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::DebuggerValue(id) => *id,
290 Self::Hint(id) => *id,
291 Self::Color(id) => *id,
292 }
293 }
294}
295
296pub enum ActiveDebugLine {}
297pub enum DebugStackFrameLine {}
298enum DocumentHighlightRead {}
299enum DocumentHighlightWrite {}
300enum InputComposition {}
301pub enum PendingInput {}
302enum SelectedTextHighlight {}
303
304pub enum ConflictsOuter {}
305pub enum ConflictsOurs {}
306pub enum ConflictsTheirs {}
307pub enum ConflictsOursMarker {}
308pub enum ConflictsTheirsMarker {}
309
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub enum Navigated {
312 Yes,
313 No,
314}
315
316impl Navigated {
317 pub fn from_bool(yes: bool) -> Navigated {
318 if yes { Navigated::Yes } else { Navigated::No }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323enum DisplayDiffHunk {
324 Folded {
325 display_row: DisplayRow,
326 },
327 Unfolded {
328 is_created_file: bool,
329 diff_base_byte_range: Range<usize>,
330 display_row_range: Range<DisplayRow>,
331 multi_buffer_range: Range<Anchor>,
332 status: DiffHunkStatus,
333 },
334}
335
336pub enum HideMouseCursorOrigin {
337 TypingAction,
338 MovementAction,
339}
340
341pub fn init_settings(cx: &mut App) {
342 EditorSettings::register(cx);
343}
344
345pub fn init(cx: &mut App) {
346 init_settings(cx);
347
348 cx.set_global(GlobalBlameRenderer(Arc::new(())));
349
350 workspace::register_project_item::<Editor>(cx);
351 workspace::FollowableViewRegistry::register::<Editor>(cx);
352 workspace::register_serializable_item::<Editor>(cx);
353
354 cx.observe_new(
355 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
356 workspace.register_action(Editor::new_file);
357 workspace.register_action(Editor::new_file_vertical);
358 workspace.register_action(Editor::new_file_horizontal);
359 workspace.register_action(Editor::cancel_language_server_work);
360 workspace.register_action(Editor::toggle_focus);
361 },
362 )
363 .detach();
364
365 cx.on_action(move |_: &workspace::NewFile, cx| {
366 let app_state = workspace::AppState::global(cx);
367 if let Some(app_state) = app_state.upgrade() {
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 Editor::new_file(workspace, &Default::default(), window, cx)
374 },
375 )
376 .detach();
377 }
378 });
379 cx.on_action(move |_: &workspace::NewWindow, cx| {
380 let app_state = workspace::AppState::global(cx);
381 if let Some(app_state) = app_state.upgrade() {
382 workspace::open_new(
383 Default::default(),
384 app_state,
385 cx,
386 |workspace, window, cx| {
387 cx.activate(true);
388 Editor::new_file(workspace, &Default::default(), window, cx)
389 },
390 )
391 .detach();
392 }
393 });
394}
395
396pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
397 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
398}
399
400pub trait DiagnosticRenderer {
401 fn render_group(
402 &self,
403 diagnostic_group: Vec<DiagnosticEntry<Point>>,
404 buffer_id: BufferId,
405 snapshot: EditorSnapshot,
406 editor: WeakEntity<Editor>,
407 cx: &mut App,
408 ) -> Vec<BlockProperties<Anchor>>;
409
410 fn render_hover(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 range: Range<Point>,
414 buffer_id: BufferId,
415 cx: &mut App,
416 ) -> Option<Entity<markdown::Markdown>>;
417
418 fn open_link(
419 &self,
420 editor: &mut Editor,
421 link: SharedString,
422 window: &mut Window,
423 cx: &mut Context<Editor>,
424 );
425}
426
427pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
428
429impl GlobalDiagnosticRenderer {
430 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
431 cx.try_global::<Self>().map(|g| g.0.clone())
432 }
433}
434
435impl gpui::Global for GlobalDiagnosticRenderer {}
436pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
437 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
438}
439
440pub struct SearchWithinRange;
441
442trait InvalidationRegion {
443 fn ranges(&self) -> &[Range<Anchor>];
444}
445
446#[derive(Clone, Debug, PartialEq)]
447pub enum SelectPhase {
448 Begin {
449 position: DisplayPoint,
450 add: bool,
451 click_count: usize,
452 },
453 BeginColumnar {
454 position: DisplayPoint,
455 reset: bool,
456 mode: ColumnarMode,
457 goal_column: u32,
458 },
459 Extend {
460 position: DisplayPoint,
461 click_count: usize,
462 },
463 Update {
464 position: DisplayPoint,
465 goal_column: u32,
466 scroll_delta: gpui::Point<f32>,
467 },
468 End,
469}
470
471#[derive(Clone, Debug, PartialEq)]
472pub enum ColumnarMode {
473 FromMouse,
474 FromSelection,
475}
476
477#[derive(Clone, Debug)]
478pub enum SelectMode {
479 Character,
480 Word(Range<Anchor>),
481 Line(Range<Anchor>),
482 All,
483}
484
485#[derive(Clone, PartialEq, Eq, Debug)]
486pub enum EditorMode {
487 SingleLine,
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868struct ChangeLocation {
869 current: Option<Vec<Anchor>>,
870 original: Vec<Anchor>,
871}
872impl ChangeLocation {
873 fn locations(&self) -> &[Anchor] {
874 self.current.as_ref().unwrap_or(&self.original)
875 }
876}
877
878/// A set of caret positions, registered when the editor was edited.
879pub struct ChangeList {
880 changes: Vec<ChangeLocation>,
881 /// Currently "selected" change.
882 position: Option<usize>,
883}
884
885impl ChangeList {
886 pub fn new() -> Self {
887 Self {
888 changes: Vec::new(),
889 position: None,
890 }
891 }
892
893 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
894 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
895 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
896 if self.changes.is_empty() {
897 return None;
898 }
899
900 let prev = self.position.unwrap_or(self.changes.len());
901 let next = if direction == Direction::Prev {
902 prev.saturating_sub(count)
903 } else {
904 (prev + count).min(self.changes.len() - 1)
905 };
906 self.position = Some(next);
907 self.changes.get(next).map(|change| change.locations())
908 }
909
910 /// Adds a new change to the list, resetting the change list position.
911 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
912 self.position.take();
913 if let Some(last) = self.changes.last_mut()
914 && group
915 {
916 last.current = Some(new_positions)
917 } else {
918 self.changes.push(ChangeLocation {
919 original: new_positions,
920 current: None,
921 });
922 }
923 }
924
925 pub fn last(&self) -> Option<&[Anchor]> {
926 self.changes.last().map(|change| change.locations())
927 }
928
929 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.original.as_slice())
931 }
932
933 pub fn invert_last_group(&mut self) {
934 if let Some(last) = self.changes.last_mut() {
935 if let Some(current) = last.current.as_mut() {
936 mem::swap(&mut last.original, current);
937 }
938 }
939 }
940}
941
942#[derive(Clone)]
943struct InlineBlamePopoverState {
944 scroll_handle: ScrollHandle,
945 commit_message: Option<ParsedCommitMessage>,
946 markdown: Entity<Markdown>,
947}
948
949struct InlineBlamePopover {
950 position: gpui::Point<Pixels>,
951 hide_task: Option<Task<()>>,
952 popover_bounds: Option<Bounds<Pixels>>,
953 popover_state: InlineBlamePopoverState,
954 keyboard_grace: bool,
955}
956
957enum SelectionDragState {
958 /// State when no drag related activity is detected.
959 None,
960 /// State when the mouse is down on a selection that is about to be dragged.
961 ReadyToDrag {
962 selection: Selection<Anchor>,
963 click_position: gpui::Point<Pixels>,
964 mouse_down_time: Instant,
965 },
966 /// State when the mouse is dragging the selection in the editor.
967 Dragging {
968 selection: Selection<Anchor>,
969 drop_cursor: Selection<Anchor>,
970 hide_drop_cursor: bool,
971 },
972}
973
974enum ColumnarSelectionState {
975 FromMouse {
976 selection_tail: Anchor,
977 display_point: Option<DisplayPoint>,
978 },
979 FromSelection {
980 selection_tail: Anchor,
981 },
982}
983
984/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
985/// a breakpoint on them.
986#[derive(Clone, Copy, Debug, PartialEq, Eq)]
987struct PhantomBreakpointIndicator {
988 display_row: DisplayRow,
989 /// There's a small debounce between hovering over the line and showing the indicator.
990 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
991 is_active: bool,
992 collides_with_existing_breakpoint: bool,
993}
994
995/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
996///
997/// See the [module level documentation](self) for more information.
998pub struct Editor {
999 focus_handle: FocusHandle,
1000 last_focused_descendant: Option<WeakFocusHandle>,
1001 /// The text buffer being edited
1002 buffer: Entity<MultiBuffer>,
1003 /// Map of how text in the buffer should be displayed.
1004 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1005 pub display_map: Entity<DisplayMap>,
1006 pub selections: SelectionsCollection,
1007 pub scroll_manager: ScrollManager,
1008 /// When inline assist editors are linked, they all render cursors because
1009 /// typing enters text into each of them, even the ones that aren't focused.
1010 pub(crate) show_cursor_when_unfocused: bool,
1011 columnar_selection_state: Option<ColumnarSelectionState>,
1012 add_selections_state: Option<AddSelectionsState>,
1013 select_next_state: Option<SelectNextState>,
1014 select_prev_state: Option<SelectNextState>,
1015 selection_history: SelectionHistory,
1016 defer_selection_effects: bool,
1017 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1018 autoclose_regions: Vec<AutocloseRegion>,
1019 snippet_stack: InvalidationStack<SnippetState>,
1020 select_syntax_node_history: SelectSyntaxNodeHistory,
1021 ime_transaction: Option<TransactionId>,
1022 pub diagnostics_max_severity: DiagnosticSeverity,
1023 active_diagnostics: ActiveDiagnostic,
1024 show_inline_diagnostics: bool,
1025 inline_diagnostics_update: Task<()>,
1026 inline_diagnostics_enabled: bool,
1027 diagnostics_enabled: bool,
1028 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1029 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1030 hard_wrap: Option<usize>,
1031
1032 // TODO: make this a access method
1033 pub project: Option<Entity<Project>>,
1034 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1035 completion_provider: Option<Rc<dyn CompletionProvider>>,
1036 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1037 blink_manager: Entity<BlinkManager>,
1038 show_cursor_names: bool,
1039 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1040 pub show_local_selections: bool,
1041 mode: EditorMode,
1042 show_breadcrumbs: bool,
1043 show_gutter: bool,
1044 show_scrollbars: ScrollbarAxes,
1045 minimap_visibility: MinimapVisibility,
1046 offset_content: bool,
1047 disable_expand_excerpt_buttons: bool,
1048 show_line_numbers: Option<bool>,
1049 use_relative_line_numbers: Option<bool>,
1050 show_git_diff_gutter: Option<bool>,
1051 show_code_actions: Option<bool>,
1052 show_runnables: Option<bool>,
1053 show_breakpoints: Option<bool>,
1054 show_wrap_guides: Option<bool>,
1055 show_indent_guides: Option<bool>,
1056 placeholder_text: Option<Arc<str>>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_inline_completion: Option<InlineCompletionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 inline_completions_hidden_for_vim_mode: bool,
1104 show_inline_completions_override: Option<bool>,
1105 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177}
1178
1179#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1180enum NextScrollCursorCenterTopBottom {
1181 #[default]
1182 Center,
1183 Top,
1184 Bottom,
1185}
1186
1187impl NextScrollCursorCenterTopBottom {
1188 fn next(&self) -> Self {
1189 match self {
1190 Self::Center => Self::Top,
1191 Self::Top => Self::Bottom,
1192 Self::Bottom => Self::Center,
1193 }
1194 }
1195}
1196
1197#[derive(Clone)]
1198pub struct EditorSnapshot {
1199 pub mode: EditorMode,
1200 show_gutter: bool,
1201 show_line_numbers: Option<bool>,
1202 show_git_diff_gutter: Option<bool>,
1203 show_code_actions: Option<bool>,
1204 show_runnables: Option<bool>,
1205 show_breakpoints: Option<bool>,
1206 git_blame_gutter_max_author_length: Option<usize>,
1207 pub display_snapshot: DisplaySnapshot,
1208 pub placeholder_text: Option<Arc<str>>,
1209 is_focused: bool,
1210 scroll_anchor: ScrollAnchor,
1211 ongoing_scroll: OngoingScroll,
1212 current_line_highlight: CurrentLineHighlight,
1213 gutter_hovered: bool,
1214}
1215
1216#[derive(Default, Debug, Clone, Copy)]
1217pub struct GutterDimensions {
1218 pub left_padding: Pixels,
1219 pub right_padding: Pixels,
1220 pub width: Pixels,
1221 pub margin: Pixels,
1222 pub git_blame_entries_width: Option<Pixels>,
1223}
1224
1225impl GutterDimensions {
1226 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1227 Self {
1228 margin: Self::default_gutter_margin(font_id, font_size, cx),
1229 ..Default::default()
1230 }
1231 }
1232
1233 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1234 -cx.text_system().descent(font_id, font_size)
1235 }
1236 /// The full width of the space taken up by the gutter.
1237 pub fn full_width(&self) -> Pixels {
1238 self.margin + self.width
1239 }
1240
1241 /// The width of the space reserved for the fold indicators,
1242 /// use alongside 'justify_end' and `gutter_width` to
1243 /// right align content with the line numbers
1244 pub fn fold_area_width(&self) -> Pixels {
1245 self.margin + self.right_padding
1246 }
1247}
1248
1249struct CharacterDimensions {
1250 em_width: Pixels,
1251 em_advance: Pixels,
1252 line_height: Pixels,
1253}
1254
1255#[derive(Debug)]
1256pub struct RemoteSelection {
1257 pub replica_id: ReplicaId,
1258 pub selection: Selection<Anchor>,
1259 pub cursor_shape: CursorShape,
1260 pub collaborator_id: CollaboratorId,
1261 pub line_mode: bool,
1262 pub user_name: Option<SharedString>,
1263 pub color: PlayerColor,
1264}
1265
1266#[derive(Clone, Debug)]
1267struct SelectionHistoryEntry {
1268 selections: Arc<[Selection<Anchor>]>,
1269 select_next_state: Option<SelectNextState>,
1270 select_prev_state: Option<SelectNextState>,
1271 add_selections_state: Option<AddSelectionsState>,
1272}
1273
1274#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1275enum SelectionHistoryMode {
1276 Normal,
1277 Undoing,
1278 Redoing,
1279 Skipping,
1280}
1281
1282#[derive(Clone, PartialEq, Eq, Hash)]
1283struct HoveredCursor {
1284 replica_id: u16,
1285 selection_id: usize,
1286}
1287
1288impl Default for SelectionHistoryMode {
1289 fn default() -> Self {
1290 Self::Normal
1291 }
1292}
1293
1294#[derive(Debug)]
1295/// SelectionEffects controls the side-effects of updating the selection.
1296///
1297/// The default behaviour does "what you mostly want":
1298/// - it pushes to the nav history if the cursor moved by >10 lines
1299/// - it re-triggers completion requests
1300/// - it scrolls to fit
1301///
1302/// You might want to modify these behaviours. For example when doing a "jump"
1303/// like go to definition, we always want to add to nav history; but when scrolling
1304/// in vim mode we never do.
1305///
1306/// Similarly, you might want to disable scrolling if you don't want the viewport to
1307/// move.
1308pub struct SelectionEffects {
1309 nav_history: Option<bool>,
1310 completions: bool,
1311 scroll: Option<Autoscroll>,
1312}
1313
1314impl Default for SelectionEffects {
1315 fn default() -> Self {
1316 Self {
1317 nav_history: None,
1318 completions: true,
1319 scroll: Some(Autoscroll::fit()),
1320 }
1321 }
1322}
1323impl SelectionEffects {
1324 pub fn scroll(scroll: Autoscroll) -> Self {
1325 Self {
1326 scroll: Some(scroll),
1327 ..Default::default()
1328 }
1329 }
1330
1331 pub fn no_scroll() -> Self {
1332 Self {
1333 scroll: None,
1334 ..Default::default()
1335 }
1336 }
1337
1338 pub fn completions(self, completions: bool) -> Self {
1339 Self {
1340 completions,
1341 ..self
1342 }
1343 }
1344
1345 pub fn nav_history(self, nav_history: bool) -> Self {
1346 Self {
1347 nav_history: Some(nav_history),
1348 ..self
1349 }
1350 }
1351}
1352
1353struct DeferredSelectionEffectsState {
1354 changed: bool,
1355 effects: SelectionEffects,
1356 old_cursor_position: Anchor,
1357 history_entry: SelectionHistoryEntry,
1358}
1359
1360#[derive(Default)]
1361struct SelectionHistory {
1362 #[allow(clippy::type_complexity)]
1363 selections_by_transaction:
1364 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1365 mode: SelectionHistoryMode,
1366 undo_stack: VecDeque<SelectionHistoryEntry>,
1367 redo_stack: VecDeque<SelectionHistoryEntry>,
1368}
1369
1370impl SelectionHistory {
1371 #[track_caller]
1372 fn insert_transaction(
1373 &mut self,
1374 transaction_id: TransactionId,
1375 selections: Arc<[Selection<Anchor>]>,
1376 ) {
1377 if selections.is_empty() {
1378 log::error!(
1379 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1380 std::panic::Location::caller()
1381 );
1382 return;
1383 }
1384 self.selections_by_transaction
1385 .insert(transaction_id, (selections, None));
1386 }
1387
1388 #[allow(clippy::type_complexity)]
1389 fn transaction(
1390 &self,
1391 transaction_id: TransactionId,
1392 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1393 self.selections_by_transaction.get(&transaction_id)
1394 }
1395
1396 #[allow(clippy::type_complexity)]
1397 fn transaction_mut(
1398 &mut self,
1399 transaction_id: TransactionId,
1400 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1401 self.selections_by_transaction.get_mut(&transaction_id)
1402 }
1403
1404 fn push(&mut self, entry: SelectionHistoryEntry) {
1405 if !entry.selections.is_empty() {
1406 match self.mode {
1407 SelectionHistoryMode::Normal => {
1408 self.push_undo(entry);
1409 self.redo_stack.clear();
1410 }
1411 SelectionHistoryMode::Undoing => self.push_redo(entry),
1412 SelectionHistoryMode::Redoing => self.push_undo(entry),
1413 SelectionHistoryMode::Skipping => {}
1414 }
1415 }
1416 }
1417
1418 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1419 if self
1420 .undo_stack
1421 .back()
1422 .map_or(true, |e| e.selections != entry.selections)
1423 {
1424 self.undo_stack.push_back(entry);
1425 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1426 self.undo_stack.pop_front();
1427 }
1428 }
1429 }
1430
1431 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1432 if self
1433 .redo_stack
1434 .back()
1435 .map_or(true, |e| e.selections != entry.selections)
1436 {
1437 self.redo_stack.push_back(entry);
1438 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1439 self.redo_stack.pop_front();
1440 }
1441 }
1442 }
1443}
1444
1445#[derive(Clone, Copy)]
1446pub struct RowHighlightOptions {
1447 pub autoscroll: bool,
1448 pub include_gutter: bool,
1449}
1450
1451impl Default for RowHighlightOptions {
1452 fn default() -> Self {
1453 Self {
1454 autoscroll: Default::default(),
1455 include_gutter: true,
1456 }
1457 }
1458}
1459
1460struct RowHighlight {
1461 index: usize,
1462 range: Range<Anchor>,
1463 color: Hsla,
1464 options: RowHighlightOptions,
1465 type_id: TypeId,
1466}
1467
1468#[derive(Clone, Debug)]
1469struct AddSelectionsState {
1470 groups: Vec<AddSelectionsGroup>,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsGroup {
1475 above: bool,
1476 stack: Vec<usize>,
1477}
1478
1479#[derive(Clone)]
1480struct SelectNextState {
1481 query: AhoCorasick,
1482 wordwise: bool,
1483 done: bool,
1484}
1485
1486impl std::fmt::Debug for SelectNextState {
1487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1488 f.debug_struct(std::any::type_name::<Self>())
1489 .field("wordwise", &self.wordwise)
1490 .field("done", &self.done)
1491 .finish()
1492 }
1493}
1494
1495#[derive(Debug)]
1496struct AutocloseRegion {
1497 selection_id: usize,
1498 range: Range<Anchor>,
1499 pair: BracketPair,
1500}
1501
1502#[derive(Debug)]
1503struct SnippetState {
1504 ranges: Vec<Vec<Range<Anchor>>>,
1505 active_index: usize,
1506 choices: Vec<Option<Vec<String>>>,
1507}
1508
1509#[doc(hidden)]
1510pub struct RenameState {
1511 pub range: Range<Anchor>,
1512 pub old_name: Arc<str>,
1513 pub editor: Entity<Editor>,
1514 block_id: CustomBlockId,
1515}
1516
1517struct InvalidationStack<T>(Vec<T>);
1518
1519struct RegisteredInlineCompletionProvider {
1520 provider: Arc<dyn InlineCompletionProviderHandle>,
1521 _subscription: Subscription,
1522}
1523
1524#[derive(Debug, PartialEq, Eq)]
1525pub struct ActiveDiagnosticGroup {
1526 pub active_range: Range<Anchor>,
1527 pub active_message: String,
1528 pub group_id: usize,
1529 pub blocks: HashSet<CustomBlockId>,
1530}
1531
1532#[derive(Debug, PartialEq, Eq)]
1533
1534pub(crate) enum ActiveDiagnostic {
1535 None,
1536 All,
1537 Group(ActiveDiagnosticGroup),
1538}
1539
1540#[derive(Serialize, Deserialize, Clone, Debug)]
1541pub struct ClipboardSelection {
1542 /// The number of bytes in this selection.
1543 pub len: usize,
1544 /// Whether this was a full-line selection.
1545 pub is_entire_line: bool,
1546 /// The indentation of the first line when this content was originally copied.
1547 pub first_line_indent: u32,
1548}
1549
1550// selections, scroll behavior, was newest selection reversed
1551type SelectSyntaxNodeHistoryState = (
1552 Box<[Selection<usize>]>,
1553 SelectSyntaxNodeScrollBehavior,
1554 bool,
1555);
1556
1557#[derive(Default)]
1558struct SelectSyntaxNodeHistory {
1559 stack: Vec<SelectSyntaxNodeHistoryState>,
1560 // disable temporarily to allow changing selections without losing the stack
1561 pub disable_clearing: bool,
1562}
1563
1564impl SelectSyntaxNodeHistory {
1565 pub fn try_clear(&mut self) {
1566 if !self.disable_clearing {
1567 self.stack.clear();
1568 }
1569 }
1570
1571 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1572 self.stack.push(selection);
1573 }
1574
1575 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1576 self.stack.pop()
1577 }
1578}
1579
1580enum SelectSyntaxNodeScrollBehavior {
1581 CursorTop,
1582 FitSelection,
1583 CursorBottom,
1584}
1585
1586#[derive(Debug)]
1587pub(crate) struct NavigationData {
1588 cursor_anchor: Anchor,
1589 cursor_position: Point,
1590 scroll_anchor: ScrollAnchor,
1591 scroll_top_row: u32,
1592}
1593
1594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1595pub enum GotoDefinitionKind {
1596 Symbol,
1597 Declaration,
1598 Type,
1599 Implementation,
1600}
1601
1602#[derive(Debug, Clone)]
1603enum InlayHintRefreshReason {
1604 ModifiersChanged(bool),
1605 Toggle(bool),
1606 SettingsChange(InlayHintSettings),
1607 NewLinesShown,
1608 BufferEdited(HashSet<Arc<Language>>),
1609 RefreshRequested,
1610 ExcerptsRemoved(Vec<ExcerptId>),
1611}
1612
1613impl InlayHintRefreshReason {
1614 fn description(&self) -> &'static str {
1615 match self {
1616 Self::ModifiersChanged(_) => "modifiers changed",
1617 Self::Toggle(_) => "toggle",
1618 Self::SettingsChange(_) => "settings change",
1619 Self::NewLinesShown => "new lines shown",
1620 Self::BufferEdited(_) => "buffer edited",
1621 Self::RefreshRequested => "refresh requested",
1622 Self::ExcerptsRemoved(_) => "excerpts removed",
1623 }
1624 }
1625}
1626
1627pub enum FormatTarget {
1628 Buffers(HashSet<Entity<Buffer>>),
1629 Ranges(Vec<Range<MultiBufferPoint>>),
1630}
1631
1632pub(crate) struct FocusedBlock {
1633 id: BlockId,
1634 focus_handle: WeakFocusHandle,
1635}
1636
1637#[derive(Clone)]
1638enum JumpData {
1639 MultiBufferRow {
1640 row: MultiBufferRow,
1641 line_offset_from_top: u32,
1642 },
1643 MultiBufferPoint {
1644 excerpt_id: ExcerptId,
1645 position: Point,
1646 anchor: text::Anchor,
1647 line_offset_from_top: u32,
1648 },
1649}
1650
1651pub enum MultibufferSelectionMode {
1652 First,
1653 All,
1654}
1655
1656#[derive(Clone, Copy, Debug, Default)]
1657pub struct RewrapOptions {
1658 pub override_language_settings: bool,
1659 pub preserve_existing_whitespace: bool,
1660}
1661
1662impl Editor {
1663 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1667 }
1668
1669 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::full(), buffer, None, window, cx)
1673 }
1674
1675 pub fn auto_height(
1676 min_lines: usize,
1677 max_lines: usize,
1678 window: &mut Window,
1679 cx: &mut Context<Self>,
1680 ) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::AutoHeight {
1685 min_lines,
1686 max_lines: Some(max_lines),
1687 },
1688 buffer,
1689 None,
1690 window,
1691 cx,
1692 )
1693 }
1694
1695 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1696 /// The editor grows as tall as needed to fit its content.
1697 pub fn auto_height_unbounded(
1698 min_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: None,
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 pub fn for_buffer(
1717 buffer: Entity<Buffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1723 Self::new(EditorMode::full(), buffer, project, window, cx)
1724 }
1725
1726 pub fn for_multibuffer(
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Self::new(EditorMode::full(), buffer, project, window, cx)
1733 }
1734
1735 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let mut clone = Self::new(
1737 self.mode.clone(),
1738 self.buffer.clone(),
1739 self.project.clone(),
1740 window,
1741 cx,
1742 );
1743 self.display_map.update(cx, |display_map, cx| {
1744 let snapshot = display_map.snapshot(cx);
1745 clone.display_map.update(cx, |display_map, cx| {
1746 display_map.set_state(&snapshot, cx);
1747 });
1748 });
1749 clone.folds_did_change(cx);
1750 clone.selections.clone_state(&self.selections);
1751 clone.scroll_manager.clone_state(&self.scroll_manager);
1752 clone.searchable = self.searchable;
1753 clone.read_only = self.read_only;
1754 clone
1755 }
1756
1757 pub fn new(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 Editor::new_internal(mode, buffer, project, None, window, cx)
1765 }
1766
1767 fn new_internal(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 display_map: Option<Entity<DisplayMap>>,
1772 window: &mut Window,
1773 cx: &mut Context<Self>,
1774 ) -> Self {
1775 debug_assert!(
1776 display_map.is_none() || mode.is_minimap(),
1777 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1778 );
1779
1780 let full_mode = mode.is_full();
1781 let is_minimap = mode.is_minimap();
1782 let diagnostics_max_severity = if full_mode {
1783 EditorSettings::get_global(cx)
1784 .diagnostics_max_severity
1785 .unwrap_or(DiagnosticSeverity::Hint)
1786 } else {
1787 DiagnosticSeverity::Off
1788 };
1789 let style = window.text_style();
1790 let font_size = style.font_size.to_pixels(window.rem_size());
1791 let editor = cx.entity().downgrade();
1792 let fold_placeholder = FoldPlaceholder {
1793 constrain_width: true,
1794 render: Arc::new(move |fold_id, fold_range, cx| {
1795 let editor = editor.clone();
1796 div()
1797 .id(fold_id)
1798 .bg(cx.theme().colors().ghost_element_background)
1799 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1800 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1801 .rounded_xs()
1802 .size_full()
1803 .cursor_pointer()
1804 .child("⋯")
1805 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1806 .on_click(move |_, _window, cx| {
1807 editor
1808 .update(cx, |editor, cx| {
1809 editor.unfold_ranges(
1810 &[fold_range.start..fold_range.end],
1811 true,
1812 false,
1813 cx,
1814 );
1815 cx.stop_propagation();
1816 })
1817 .ok();
1818 })
1819 .into_any()
1820 }),
1821 merge_adjacent: true,
1822 ..FoldPlaceholder::default()
1823 };
1824 let display_map = display_map.unwrap_or_else(|| {
1825 cx.new(|cx| {
1826 DisplayMap::new(
1827 buffer.clone(),
1828 style.font(),
1829 font_size,
1830 None,
1831 FILE_HEADER_HEIGHT,
1832 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1833 fold_placeholder,
1834 diagnostics_max_severity,
1835 cx,
1836 )
1837 })
1838 });
1839
1840 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1841
1842 let blink_manager = cx.new(|cx| {
1843 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1844 if is_minimap {
1845 blink_manager.disable(cx);
1846 }
1847 blink_manager
1848 });
1849
1850 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1851 .then(|| language_settings::SoftWrap::None);
1852
1853 let mut project_subscriptions = Vec::new();
1854 if full_mode {
1855 if let Some(project) = project.as_ref() {
1856 project_subscriptions.push(cx.subscribe_in(
1857 project,
1858 window,
1859 |editor, _, event, window, cx| match event {
1860 project::Event::RefreshCodeLens => {
1861 // we always query lens with actions, without storing them, always refreshing them
1862 }
1863 project::Event::RefreshInlayHints => {
1864 editor
1865 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1866 }
1867 project::Event::LanguageServerAdded(..)
1868 | project::Event::LanguageServerRemoved(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task =
1871 Some(editor.refresh_runnables(window, cx));
1872 }
1873 editor.update_lsp_data(true, None, window, cx);
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 _ => {}
1896 },
1897 ));
1898 if let Some(task_inventory) = project
1899 .read(cx)
1900 .task_store()
1901 .read(cx)
1902 .task_inventory()
1903 .cloned()
1904 {
1905 project_subscriptions.push(cx.observe_in(
1906 &task_inventory,
1907 window,
1908 |editor, _, window, cx| {
1909 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1910 },
1911 ));
1912 };
1913
1914 project_subscriptions.push(cx.subscribe_in(
1915 &project.read(cx).breakpoint_store(),
1916 window,
1917 |editor, _, event, window, cx| match event {
1918 BreakpointStoreEvent::ClearDebugLines => {
1919 editor.clear_row_highlights::<ActiveDebugLine>();
1920 editor.refresh_inline_values(cx);
1921 }
1922 BreakpointStoreEvent::SetDebugLine => {
1923 if editor.go_to_active_debug_line(window, cx) {
1924 cx.stop_propagation();
1925 }
1926
1927 editor.refresh_inline_values(cx);
1928 }
1929 _ => {}
1930 },
1931 ));
1932 let git_store = project.read(cx).git_store().clone();
1933 let project = project.clone();
1934 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1935 match event {
1936 GitStoreEvent::RepositoryUpdated(
1937 _,
1938 RepositoryEvent::Updated {
1939 new_instance: true, ..
1940 },
1941 _,
1942 ) => {
1943 this.load_diff_task = Some(
1944 update_uncommitted_diff_for_buffer(
1945 cx.entity(),
1946 &project,
1947 this.buffer.read(cx).all_buffers(),
1948 this.buffer.clone(),
1949 cx,
1950 )
1951 .shared(),
1952 );
1953 }
1954 _ => {}
1955 }
1956 }));
1957 }
1958 }
1959
1960 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1961
1962 let inlay_hint_settings =
1963 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1964 let focus_handle = cx.focus_handle();
1965 if !is_minimap {
1966 cx.on_focus(&focus_handle, window, Self::handle_focus)
1967 .detach();
1968 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1969 .detach();
1970 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1971 .detach();
1972 cx.on_blur(&focus_handle, window, Self::handle_blur)
1973 .detach();
1974 cx.observe_pending_input(window, Self::observe_pending_input)
1975 .detach();
1976 }
1977
1978 let show_indent_guides = if matches!(
1979 mode,
1980 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1981 ) {
1982 Some(false)
1983 } else {
1984 None
1985 };
1986
1987 let breakpoint_store = match (&mode, project.as_ref()) {
1988 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1989 _ => None,
1990 };
1991
1992 let mut code_action_providers = Vec::new();
1993 let mut load_uncommitted_diff = None;
1994 if let Some(project) = project.clone() {
1995 load_uncommitted_diff = Some(
1996 update_uncommitted_diff_for_buffer(
1997 cx.entity(),
1998 &project,
1999 buffer.read(cx).all_buffers(),
2000 buffer.clone(),
2001 cx,
2002 )
2003 .shared(),
2004 );
2005 code_action_providers.push(Rc::new(project) as Rc<_>);
2006 }
2007
2008 let mut editor = Self {
2009 focus_handle,
2010 show_cursor_when_unfocused: false,
2011 last_focused_descendant: None,
2012 buffer: buffer.clone(),
2013 display_map: display_map.clone(),
2014 selections,
2015 scroll_manager: ScrollManager::new(cx),
2016 columnar_selection_state: None,
2017 add_selections_state: None,
2018 select_next_state: None,
2019 select_prev_state: None,
2020 selection_history: SelectionHistory::default(),
2021 defer_selection_effects: false,
2022 deferred_selection_effects_state: None,
2023 autoclose_regions: Vec::new(),
2024 snippet_stack: InvalidationStack::default(),
2025 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2026 ime_transaction: None,
2027 active_diagnostics: ActiveDiagnostic::None,
2028 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2029 inline_diagnostics_update: Task::ready(()),
2030 inline_diagnostics: Vec::new(),
2031 soft_wrap_mode_override,
2032 diagnostics_max_severity,
2033 hard_wrap: None,
2034 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2035 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2036 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2037 project,
2038 blink_manager: blink_manager.clone(),
2039 show_local_selections: true,
2040 show_scrollbars: ScrollbarAxes {
2041 horizontal: full_mode,
2042 vertical: full_mode,
2043 },
2044 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2045 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2046 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2047 show_gutter: full_mode,
2048 show_line_numbers: (!full_mode).then_some(false),
2049 use_relative_line_numbers: None,
2050 disable_expand_excerpt_buttons: !full_mode,
2051 show_git_diff_gutter: None,
2052 show_code_actions: None,
2053 show_runnables: None,
2054 show_breakpoints: None,
2055 show_wrap_guides: None,
2056 show_indent_guides,
2057 placeholder_text: None,
2058 highlight_order: 0,
2059 highlighted_rows: HashMap::default(),
2060 background_highlights: TreeMap::default(),
2061 gutter_highlights: TreeMap::default(),
2062 scrollbar_marker_state: ScrollbarMarkerState::default(),
2063 active_indent_guides_state: ActiveIndentGuidesState::default(),
2064 nav_history: None,
2065 context_menu: RefCell::new(None),
2066 context_menu_options: None,
2067 mouse_context_menu: None,
2068 completion_tasks: Vec::new(),
2069 inline_blame_popover: None,
2070 inline_blame_popover_show_task: None,
2071 signature_help_state: SignatureHelpState::default(),
2072 auto_signature_help: None,
2073 find_all_references_task_sources: Vec::new(),
2074 next_completion_id: 0,
2075 next_inlay_id: 0,
2076 code_action_providers,
2077 available_code_actions: None,
2078 code_actions_task: None,
2079 quick_selection_highlight_task: None,
2080 debounced_selection_highlight_task: None,
2081 document_highlights_task: None,
2082 linked_editing_range_task: None,
2083 pending_rename: None,
2084 searchable: !is_minimap,
2085 cursor_shape: EditorSettings::get_global(cx)
2086 .cursor_shape
2087 .unwrap_or_default(),
2088 current_line_highlight: None,
2089 autoindent_mode: Some(AutoindentMode::EachLine),
2090 collapse_matches: false,
2091 workspace: None,
2092 input_enabled: !is_minimap,
2093 use_modal_editing: full_mode,
2094 read_only: is_minimap,
2095 use_autoclose: true,
2096 use_auto_surround: true,
2097 auto_replace_emoji_shortcode: false,
2098 jsx_tag_auto_close_enabled_in_any_buffer: false,
2099 leader_id: None,
2100 remote_id: None,
2101 hover_state: HoverState::default(),
2102 pending_mouse_down: None,
2103 hovered_link_state: None,
2104 edit_prediction_provider: None,
2105 active_inline_completion: None,
2106 stale_inline_completion_in_menu: None,
2107 edit_prediction_preview: EditPredictionPreview::Inactive {
2108 released_too_fast: false,
2109 },
2110 inline_diagnostics_enabled: full_mode,
2111 diagnostics_enabled: full_mode,
2112 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2113 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2114 gutter_hovered: false,
2115 pixel_position_of_newest_cursor: None,
2116 last_bounds: None,
2117 last_position_map: None,
2118 expect_bounds_change: None,
2119 gutter_dimensions: GutterDimensions::default(),
2120 style: None,
2121 show_cursor_names: false,
2122 hovered_cursors: HashMap::default(),
2123 next_editor_action_id: EditorActionId::default(),
2124 editor_actions: Rc::default(),
2125 inline_completions_hidden_for_vim_mode: false,
2126 show_inline_completions_override: None,
2127 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2128 edit_prediction_settings: EditPredictionSettings::Disabled,
2129 edit_prediction_indent_conflict: false,
2130 edit_prediction_requires_modifier_in_indent_conflict: true,
2131 custom_context_menu: None,
2132 show_git_blame_gutter: false,
2133 show_git_blame_inline: false,
2134 show_selection_menu: None,
2135 show_git_blame_inline_delay_task: None,
2136 git_blame_inline_enabled: full_mode
2137 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2138 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2139 serialize_dirty_buffers: !is_minimap
2140 && ProjectSettings::get_global(cx)
2141 .session
2142 .restore_unsaved_buffers,
2143 blame: None,
2144 blame_subscription: None,
2145 tasks: BTreeMap::default(),
2146
2147 breakpoint_store,
2148 gutter_breakpoint_indicator: (None, None),
2149 hovered_diff_hunk_row: None,
2150 _subscriptions: (!is_minimap)
2151 .then(|| {
2152 vec![
2153 cx.observe(&buffer, Self::on_buffer_changed),
2154 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2155 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2156 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2157 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2158 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2159 cx.observe_window_activation(window, |editor, window, cx| {
2160 let active = window.is_window_active();
2161 editor.blink_manager.update(cx, |blink_manager, cx| {
2162 if active {
2163 blink_manager.enable(cx);
2164 } else {
2165 blink_manager.disable(cx);
2166 }
2167 });
2168 if active {
2169 editor.show_mouse_cursor(cx);
2170 }
2171 }),
2172 ]
2173 })
2174 .unwrap_or_default(),
2175 tasks_update_task: None,
2176 pull_diagnostics_task: Task::ready(()),
2177 colors: None,
2178 next_color_inlay_id: 0,
2179 linked_edit_ranges: Default::default(),
2180 in_project_search: false,
2181 previous_search_ranges: None,
2182 breadcrumb_header: None,
2183 focused_block: None,
2184 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2185 addons: HashMap::default(),
2186 registered_buffers: HashMap::default(),
2187 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2188 selection_mark_mode: false,
2189 toggle_fold_multiple_buffers: Task::ready(()),
2190 serialize_selections: Task::ready(()),
2191 serialize_folds: Task::ready(()),
2192 text_style_refinement: None,
2193 load_diff_task: load_uncommitted_diff,
2194 temporary_diff_override: false,
2195 mouse_cursor_hidden: false,
2196 minimap: None,
2197 hide_mouse_mode: EditorSettings::get_global(cx)
2198 .hide_mouse
2199 .unwrap_or_default(),
2200 change_list: ChangeList::new(),
2201 mode,
2202 selection_drag_state: SelectionDragState::None,
2203 folding_newlines: Task::ready(()),
2204 };
2205
2206 if is_minimap {
2207 return editor;
2208 }
2209
2210 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2211 editor
2212 ._subscriptions
2213 .push(cx.observe(breakpoints, |_, _, cx| {
2214 cx.notify();
2215 }));
2216 }
2217 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2218 editor._subscriptions.extend(project_subscriptions);
2219
2220 editor._subscriptions.push(cx.subscribe_in(
2221 &cx.entity(),
2222 window,
2223 |editor, _, e: &EditorEvent, window, cx| match e {
2224 EditorEvent::ScrollPositionChanged { local, .. } => {
2225 if *local {
2226 let new_anchor = editor.scroll_manager.anchor();
2227 let snapshot = editor.snapshot(window, cx);
2228 editor.update_restoration_data(cx, move |data| {
2229 data.scroll_position = (
2230 new_anchor.top_row(&snapshot.buffer_snapshot),
2231 new_anchor.offset,
2232 );
2233 });
2234 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2235 editor.inline_blame_popover.take();
2236 }
2237 }
2238 EditorEvent::Edited { .. } => {
2239 if !vim_enabled(cx) {
2240 let (map, selections) = editor.selections.all_adjusted_display(cx);
2241 let pop_state = editor
2242 .change_list
2243 .last()
2244 .map(|previous| {
2245 previous.len() == selections.len()
2246 && previous.iter().enumerate().all(|(ix, p)| {
2247 p.to_display_point(&map).row()
2248 == selections[ix].head().row()
2249 })
2250 })
2251 .unwrap_or(false);
2252 let new_positions = selections
2253 .into_iter()
2254 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2255 .collect();
2256 editor
2257 .change_list
2258 .push_to_change_list(pop_state, new_positions);
2259 }
2260 }
2261 _ => (),
2262 },
2263 ));
2264
2265 if let Some(dap_store) = editor
2266 .project
2267 .as_ref()
2268 .map(|project| project.read(cx).dap_store())
2269 {
2270 let weak_editor = cx.weak_entity();
2271
2272 editor
2273 ._subscriptions
2274 .push(
2275 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2276 let session_entity = cx.entity();
2277 weak_editor
2278 .update(cx, |editor, cx| {
2279 editor._subscriptions.push(
2280 cx.subscribe(&session_entity, Self::on_debug_session_event),
2281 );
2282 })
2283 .ok();
2284 }),
2285 );
2286
2287 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2288 editor
2289 ._subscriptions
2290 .push(cx.subscribe(&session, Self::on_debug_session_event));
2291 }
2292 }
2293
2294 // skip adding the initial selection to selection history
2295 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2296 editor.end_selection(window, cx);
2297 editor.selection_history.mode = SelectionHistoryMode::Normal;
2298
2299 editor.scroll_manager.show_scrollbars(window, cx);
2300 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2301
2302 if full_mode {
2303 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2304 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2305
2306 if editor.git_blame_inline_enabled {
2307 editor.start_git_blame_inline(false, window, cx);
2308 }
2309
2310 editor.go_to_active_debug_line(window, cx);
2311
2312 if let Some(buffer) = buffer.read(cx).as_singleton() {
2313 if let Some(project) = editor.project.as_ref() {
2314 let handle = project.update(cx, |project, cx| {
2315 project.register_buffer_with_language_servers(&buffer, cx)
2316 });
2317 editor
2318 .registered_buffers
2319 .insert(buffer.read(cx).remote_id(), handle);
2320 }
2321 }
2322
2323 editor.minimap =
2324 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2325 editor.colors = Some(LspColorData::new(cx));
2326 editor.update_lsp_data(false, None, window, cx);
2327 }
2328
2329 if editor.mode.is_full() {
2330 editor.report_editor_event("Editor Opened", None, cx);
2331 }
2332
2333 editor
2334 }
2335
2336 pub fn deploy_mouse_context_menu(
2337 &mut self,
2338 position: gpui::Point<Pixels>,
2339 context_menu: Entity<ContextMenu>,
2340 window: &mut Window,
2341 cx: &mut Context<Self>,
2342 ) {
2343 self.mouse_context_menu = Some(MouseContextMenu::new(
2344 self,
2345 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2346 context_menu,
2347 window,
2348 cx,
2349 ));
2350 }
2351
2352 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2353 self.mouse_context_menu
2354 .as_ref()
2355 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2356 }
2357
2358 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2359 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2360 }
2361
2362 fn key_context_internal(
2363 &self,
2364 has_active_edit_prediction: bool,
2365 window: &Window,
2366 cx: &App,
2367 ) -> KeyContext {
2368 let mut key_context = KeyContext::new_with_defaults();
2369 key_context.add("Editor");
2370 let mode = match self.mode {
2371 EditorMode::SingleLine { .. } => "single_line",
2372 EditorMode::AutoHeight { .. } => "auto_height",
2373 EditorMode::Minimap { .. } => "minimap",
2374 EditorMode::Full { .. } => "full",
2375 };
2376
2377 if EditorSettings::jupyter_enabled(cx) {
2378 key_context.add("jupyter");
2379 }
2380
2381 key_context.set("mode", mode);
2382 if self.pending_rename.is_some() {
2383 key_context.add("renaming");
2384 }
2385
2386 match self.context_menu.borrow().as_ref() {
2387 Some(CodeContextMenu::Completions(menu)) => {
2388 if menu.visible() {
2389 key_context.add("menu");
2390 key_context.add("showing_completions");
2391 }
2392 }
2393 Some(CodeContextMenu::CodeActions(menu)) => {
2394 if menu.visible() {
2395 key_context.add("menu");
2396 key_context.add("showing_code_actions")
2397 }
2398 }
2399 None => {}
2400 }
2401
2402 if self.signature_help_state.has_multiple_signatures() {
2403 key_context.add("showing_signature_help");
2404 }
2405
2406 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2407 if !self.focus_handle(cx).contains_focused(window, cx)
2408 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2409 {
2410 for addon in self.addons.values() {
2411 addon.extend_key_context(&mut key_context, cx)
2412 }
2413 }
2414
2415 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2416 if let Some(extension) = singleton_buffer
2417 .read(cx)
2418 .file()
2419 .and_then(|file| file.path().extension()?.to_str())
2420 {
2421 key_context.set("extension", extension.to_string());
2422 }
2423 } else {
2424 key_context.add("multibuffer");
2425 }
2426
2427 if has_active_edit_prediction {
2428 if self.edit_prediction_in_conflict() {
2429 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2430 } else {
2431 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2432 key_context.add("copilot_suggestion");
2433 }
2434 }
2435
2436 if self.selection_mark_mode {
2437 key_context.add("selection_mode");
2438 }
2439
2440 key_context
2441 }
2442
2443 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2444 if self.mouse_cursor_hidden {
2445 self.mouse_cursor_hidden = false;
2446 cx.notify();
2447 }
2448 }
2449
2450 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2451 let hide_mouse_cursor = match origin {
2452 HideMouseCursorOrigin::TypingAction => {
2453 matches!(
2454 self.hide_mouse_mode,
2455 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2456 )
2457 }
2458 HideMouseCursorOrigin::MovementAction => {
2459 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2460 }
2461 };
2462 if self.mouse_cursor_hidden != hide_mouse_cursor {
2463 self.mouse_cursor_hidden = hide_mouse_cursor;
2464 cx.notify();
2465 }
2466 }
2467
2468 pub fn edit_prediction_in_conflict(&self) -> bool {
2469 if !self.show_edit_predictions_in_menu() {
2470 return false;
2471 }
2472
2473 let showing_completions = self
2474 .context_menu
2475 .borrow()
2476 .as_ref()
2477 .map_or(false, |context| {
2478 matches!(context, CodeContextMenu::Completions(_))
2479 });
2480
2481 showing_completions
2482 || self.edit_prediction_requires_modifier()
2483 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2484 // bindings to insert tab characters.
2485 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2486 }
2487
2488 pub fn accept_edit_prediction_keybind(
2489 &self,
2490 accept_partial: bool,
2491 window: &Window,
2492 cx: &App,
2493 ) -> AcceptEditPredictionBinding {
2494 let key_context = self.key_context_internal(true, window, cx);
2495 let in_conflict = self.edit_prediction_in_conflict();
2496
2497 let bindings = if accept_partial {
2498 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2499 } else {
2500 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2501 };
2502
2503 // TODO: if the binding contains multiple keystrokes, display all of them, not
2504 // just the first one.
2505 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2506 !in_conflict
2507 || binding
2508 .keystrokes()
2509 .first()
2510 .map_or(false, |keystroke| keystroke.modifiers.modified())
2511 }))
2512 }
2513
2514 pub fn new_file(
2515 workspace: &mut Workspace,
2516 _: &workspace::NewFile,
2517 window: &mut Window,
2518 cx: &mut Context<Workspace>,
2519 ) {
2520 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2521 "Failed to create buffer",
2522 window,
2523 cx,
2524 |e, _, _| match e.error_code() {
2525 ErrorCode::RemoteUpgradeRequired => Some(format!(
2526 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2527 e.error_tag("required").unwrap_or("the latest version")
2528 )),
2529 _ => None,
2530 },
2531 );
2532 }
2533
2534 pub fn new_in_workspace(
2535 workspace: &mut Workspace,
2536 window: &mut Window,
2537 cx: &mut Context<Workspace>,
2538 ) -> Task<Result<Entity<Editor>>> {
2539 let project = workspace.project().clone();
2540 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2541
2542 cx.spawn_in(window, async move |workspace, cx| {
2543 let buffer = create.await?;
2544 workspace.update_in(cx, |workspace, window, cx| {
2545 let editor =
2546 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2547 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2548 editor
2549 })
2550 })
2551 }
2552
2553 fn new_file_vertical(
2554 workspace: &mut Workspace,
2555 _: &workspace::NewFileSplitVertical,
2556 window: &mut Window,
2557 cx: &mut Context<Workspace>,
2558 ) {
2559 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2560 }
2561
2562 fn new_file_horizontal(
2563 workspace: &mut Workspace,
2564 _: &workspace::NewFileSplitHorizontal,
2565 window: &mut Window,
2566 cx: &mut Context<Workspace>,
2567 ) {
2568 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2569 }
2570
2571 fn new_file_in_direction(
2572 workspace: &mut Workspace,
2573 direction: SplitDirection,
2574 window: &mut Window,
2575 cx: &mut Context<Workspace>,
2576 ) {
2577 let project = workspace.project().clone();
2578 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2579
2580 cx.spawn_in(window, async move |workspace, cx| {
2581 let buffer = create.await?;
2582 workspace.update_in(cx, move |workspace, window, cx| {
2583 workspace.split_item(
2584 direction,
2585 Box::new(
2586 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2587 ),
2588 window,
2589 cx,
2590 )
2591 })?;
2592 anyhow::Ok(())
2593 })
2594 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2595 match e.error_code() {
2596 ErrorCode::RemoteUpgradeRequired => Some(format!(
2597 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2598 e.error_tag("required").unwrap_or("the latest version")
2599 )),
2600 _ => None,
2601 }
2602 });
2603 }
2604
2605 pub fn leader_id(&self) -> Option<CollaboratorId> {
2606 self.leader_id
2607 }
2608
2609 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2610 &self.buffer
2611 }
2612
2613 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2614 self.workspace.as_ref()?.0.upgrade()
2615 }
2616
2617 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2618 self.buffer().read(cx).title(cx)
2619 }
2620
2621 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2622 let git_blame_gutter_max_author_length = self
2623 .render_git_blame_gutter(cx)
2624 .then(|| {
2625 if let Some(blame) = self.blame.as_ref() {
2626 let max_author_length =
2627 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2628 Some(max_author_length)
2629 } else {
2630 None
2631 }
2632 })
2633 .flatten();
2634
2635 EditorSnapshot {
2636 mode: self.mode.clone(),
2637 show_gutter: self.show_gutter,
2638 show_line_numbers: self.show_line_numbers,
2639 show_git_diff_gutter: self.show_git_diff_gutter,
2640 show_code_actions: self.show_code_actions,
2641 show_runnables: self.show_runnables,
2642 show_breakpoints: self.show_breakpoints,
2643 git_blame_gutter_max_author_length,
2644 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2645 scroll_anchor: self.scroll_manager.anchor(),
2646 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2647 placeholder_text: self.placeholder_text.clone(),
2648 is_focused: self.focus_handle.is_focused(window),
2649 current_line_highlight: self
2650 .current_line_highlight
2651 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2652 gutter_hovered: self.gutter_hovered,
2653 }
2654 }
2655
2656 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2657 self.buffer.read(cx).language_at(point, cx)
2658 }
2659
2660 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2661 self.buffer.read(cx).read(cx).file_at(point).cloned()
2662 }
2663
2664 pub fn active_excerpt(
2665 &self,
2666 cx: &App,
2667 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2668 self.buffer
2669 .read(cx)
2670 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2671 }
2672
2673 pub fn mode(&self) -> &EditorMode {
2674 &self.mode
2675 }
2676
2677 pub fn set_mode(&mut self, mode: EditorMode) {
2678 self.mode = mode;
2679 }
2680
2681 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2682 self.collaboration_hub.as_deref()
2683 }
2684
2685 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2686 self.collaboration_hub = Some(hub);
2687 }
2688
2689 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2690 self.in_project_search = in_project_search;
2691 }
2692
2693 pub fn set_custom_context_menu(
2694 &mut self,
2695 f: impl 'static
2696 + Fn(
2697 &mut Self,
2698 DisplayPoint,
2699 &mut Window,
2700 &mut Context<Self>,
2701 ) -> Option<Entity<ui::ContextMenu>>,
2702 ) {
2703 self.custom_context_menu = Some(Box::new(f))
2704 }
2705
2706 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2707 self.completion_provider = provider;
2708 }
2709
2710 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2711 self.semantics_provider.clone()
2712 }
2713
2714 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2715 self.semantics_provider = provider;
2716 }
2717
2718 pub fn set_edit_prediction_provider<T>(
2719 &mut self,
2720 provider: Option<Entity<T>>,
2721 window: &mut Window,
2722 cx: &mut Context<Self>,
2723 ) where
2724 T: EditPredictionProvider,
2725 {
2726 self.edit_prediction_provider =
2727 provider.map(|provider| RegisteredInlineCompletionProvider {
2728 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2729 if this.focus_handle.is_focused(window) {
2730 this.update_visible_inline_completion(window, cx);
2731 }
2732 }),
2733 provider: Arc::new(provider),
2734 });
2735 self.update_edit_prediction_settings(cx);
2736 self.refresh_inline_completion(false, false, window, cx);
2737 }
2738
2739 pub fn placeholder_text(&self) -> Option<&str> {
2740 self.placeholder_text.as_deref()
2741 }
2742
2743 pub fn set_placeholder_text(
2744 &mut self,
2745 placeholder_text: impl Into<Arc<str>>,
2746 cx: &mut Context<Self>,
2747 ) {
2748 let placeholder_text = Some(placeholder_text.into());
2749 if self.placeholder_text != placeholder_text {
2750 self.placeholder_text = placeholder_text;
2751 cx.notify();
2752 }
2753 }
2754
2755 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2756 self.cursor_shape = cursor_shape;
2757
2758 // Disrupt blink for immediate user feedback that the cursor shape has changed
2759 self.blink_manager.update(cx, BlinkManager::show_cursor);
2760
2761 cx.notify();
2762 }
2763
2764 pub fn set_current_line_highlight(
2765 &mut self,
2766 current_line_highlight: Option<CurrentLineHighlight>,
2767 ) {
2768 self.current_line_highlight = current_line_highlight;
2769 }
2770
2771 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2772 self.collapse_matches = collapse_matches;
2773 }
2774
2775 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2776 let buffers = self.buffer.read(cx).all_buffers();
2777 let Some(project) = self.project.as_ref() else {
2778 return;
2779 };
2780 project.update(cx, |project, cx| {
2781 for buffer in buffers {
2782 self.registered_buffers
2783 .entry(buffer.read(cx).remote_id())
2784 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2785 }
2786 })
2787 }
2788
2789 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2790 if self.collapse_matches {
2791 return range.start..range.start;
2792 }
2793 range.clone()
2794 }
2795
2796 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2797 if self.display_map.read(cx).clip_at_line_ends != clip {
2798 self.display_map
2799 .update(cx, |map, _| map.clip_at_line_ends = clip);
2800 }
2801 }
2802
2803 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2804 self.input_enabled = input_enabled;
2805 }
2806
2807 pub fn set_inline_completions_hidden_for_vim_mode(
2808 &mut self,
2809 hidden: bool,
2810 window: &mut Window,
2811 cx: &mut Context<Self>,
2812 ) {
2813 if hidden != self.inline_completions_hidden_for_vim_mode {
2814 self.inline_completions_hidden_for_vim_mode = hidden;
2815 if hidden {
2816 self.update_visible_inline_completion(window, cx);
2817 } else {
2818 self.refresh_inline_completion(true, false, window, cx);
2819 }
2820 }
2821 }
2822
2823 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2824 self.menu_inline_completions_policy = value;
2825 }
2826
2827 pub fn set_autoindent(&mut self, autoindent: bool) {
2828 if autoindent {
2829 self.autoindent_mode = Some(AutoindentMode::EachLine);
2830 } else {
2831 self.autoindent_mode = None;
2832 }
2833 }
2834
2835 pub fn read_only(&self, cx: &App) -> bool {
2836 self.read_only || self.buffer.read(cx).read_only()
2837 }
2838
2839 pub fn set_read_only(&mut self, read_only: bool) {
2840 self.read_only = read_only;
2841 }
2842
2843 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2844 self.use_autoclose = autoclose;
2845 }
2846
2847 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2848 self.use_auto_surround = auto_surround;
2849 }
2850
2851 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2852 self.auto_replace_emoji_shortcode = auto_replace;
2853 }
2854
2855 pub fn toggle_edit_predictions(
2856 &mut self,
2857 _: &ToggleEditPrediction,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 if self.show_inline_completions_override.is_some() {
2862 self.set_show_edit_predictions(None, window, cx);
2863 } else {
2864 let show_edit_predictions = !self.edit_predictions_enabled();
2865 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2866 }
2867 }
2868
2869 pub fn set_show_edit_predictions(
2870 &mut self,
2871 show_edit_predictions: Option<bool>,
2872 window: &mut Window,
2873 cx: &mut Context<Self>,
2874 ) {
2875 self.show_inline_completions_override = show_edit_predictions;
2876 self.update_edit_prediction_settings(cx);
2877
2878 if let Some(false) = show_edit_predictions {
2879 self.discard_inline_completion(false, cx);
2880 } else {
2881 self.refresh_inline_completion(false, true, window, cx);
2882 }
2883 }
2884
2885 fn inline_completions_disabled_in_scope(
2886 &self,
2887 buffer: &Entity<Buffer>,
2888 buffer_position: language::Anchor,
2889 cx: &App,
2890 ) -> bool {
2891 let snapshot = buffer.read(cx).snapshot();
2892 let settings = snapshot.settings_at(buffer_position, cx);
2893
2894 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2895 return false;
2896 };
2897
2898 scope.override_name().map_or(false, |scope_name| {
2899 settings
2900 .edit_predictions_disabled_in
2901 .iter()
2902 .any(|s| s == scope_name)
2903 })
2904 }
2905
2906 pub fn set_use_modal_editing(&mut self, to: bool) {
2907 self.use_modal_editing = to;
2908 }
2909
2910 pub fn use_modal_editing(&self) -> bool {
2911 self.use_modal_editing
2912 }
2913
2914 fn selections_did_change(
2915 &mut self,
2916 local: bool,
2917 old_cursor_position: &Anchor,
2918 effects: SelectionEffects,
2919 window: &mut Window,
2920 cx: &mut Context<Self>,
2921 ) {
2922 window.invalidate_character_coordinates();
2923
2924 // Copy selections to primary selection buffer
2925 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2926 if local {
2927 let selections = self.selections.all::<usize>(cx);
2928 let buffer_handle = self.buffer.read(cx).read(cx);
2929
2930 let mut text = String::new();
2931 for (index, selection) in selections.iter().enumerate() {
2932 let text_for_selection = buffer_handle
2933 .text_for_range(selection.start..selection.end)
2934 .collect::<String>();
2935
2936 text.push_str(&text_for_selection);
2937 if index != selections.len() - 1 {
2938 text.push('\n');
2939 }
2940 }
2941
2942 if !text.is_empty() {
2943 cx.write_to_primary(ClipboardItem::new_string(text));
2944 }
2945 }
2946
2947 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2948 self.buffer.update(cx, |buffer, cx| {
2949 buffer.set_active_selections(
2950 &self.selections.disjoint_anchors(),
2951 self.selections.line_mode,
2952 self.cursor_shape,
2953 cx,
2954 )
2955 });
2956 }
2957 let display_map = self
2958 .display_map
2959 .update(cx, |display_map, cx| display_map.snapshot(cx));
2960 let buffer = &display_map.buffer_snapshot;
2961 if self.selections.count() == 1 {
2962 self.add_selections_state = None;
2963 }
2964 self.select_next_state = None;
2965 self.select_prev_state = None;
2966 self.select_syntax_node_history.try_clear();
2967 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2968 self.snippet_stack
2969 .invalidate(&self.selections.disjoint_anchors(), buffer);
2970 self.take_rename(false, window, cx);
2971
2972 let newest_selection = self.selections.newest_anchor();
2973 let new_cursor_position = newest_selection.head();
2974 let selection_start = newest_selection.start;
2975
2976 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2977 self.push_to_nav_history(
2978 *old_cursor_position,
2979 Some(new_cursor_position.to_point(buffer)),
2980 false,
2981 effects.nav_history == Some(true),
2982 cx,
2983 );
2984 }
2985
2986 if local {
2987 if let Some(buffer_id) = new_cursor_position.buffer_id {
2988 if !self.registered_buffers.contains_key(&buffer_id) {
2989 if let Some(project) = self.project.as_ref() {
2990 project.update(cx, |project, cx| {
2991 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2992 return;
2993 };
2994 self.registered_buffers.insert(
2995 buffer_id,
2996 project.register_buffer_with_language_servers(&buffer, cx),
2997 );
2998 })
2999 }
3000 }
3001 }
3002
3003 let mut context_menu = self.context_menu.borrow_mut();
3004 let completion_menu = match context_menu.as_ref() {
3005 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3006 Some(CodeContextMenu::CodeActions(_)) => {
3007 *context_menu = None;
3008 None
3009 }
3010 None => None,
3011 };
3012 let completion_position = completion_menu.map(|menu| menu.initial_position);
3013 drop(context_menu);
3014
3015 if effects.completions {
3016 if let Some(completion_position) = completion_position {
3017 let start_offset = selection_start.to_offset(buffer);
3018 let position_matches = start_offset == completion_position.to_offset(buffer);
3019 let continue_showing = if position_matches {
3020 if self.snippet_stack.is_empty() {
3021 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3022 } else {
3023 // Snippet choices can be shown even when the cursor is in whitespace.
3024 // Dismissing the menu with actions like backspace is handled by
3025 // invalidation regions.
3026 true
3027 }
3028 } else {
3029 false
3030 };
3031
3032 if continue_showing {
3033 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3034 } else {
3035 self.hide_context_menu(window, cx);
3036 }
3037 }
3038 }
3039
3040 hide_hover(self, cx);
3041
3042 if old_cursor_position.to_display_point(&display_map).row()
3043 != new_cursor_position.to_display_point(&display_map).row()
3044 {
3045 self.available_code_actions.take();
3046 }
3047 self.refresh_code_actions(window, cx);
3048 self.refresh_document_highlights(cx);
3049 self.refresh_selected_text_highlights(false, window, cx);
3050 refresh_matching_bracket_highlights(self, window, cx);
3051 self.update_visible_inline_completion(window, cx);
3052 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3053 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3054 self.inline_blame_popover.take();
3055 if self.git_blame_inline_enabled {
3056 self.start_inline_blame_timer(window, cx);
3057 }
3058 }
3059
3060 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3061 cx.emit(EditorEvent::SelectionsChanged { local });
3062
3063 let selections = &self.selections.disjoint;
3064 if selections.len() == 1 {
3065 cx.emit(SearchEvent::ActiveMatchChanged)
3066 }
3067 if local {
3068 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3069 let inmemory_selections = selections
3070 .iter()
3071 .map(|s| {
3072 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3073 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3074 })
3075 .collect();
3076 self.update_restoration_data(cx, |data| {
3077 data.selections = inmemory_selections;
3078 });
3079
3080 if WorkspaceSettings::get(None, cx).restore_on_startup
3081 != RestoreOnStartupBehavior::None
3082 {
3083 if let Some(workspace_id) =
3084 self.workspace.as_ref().and_then(|workspace| workspace.1)
3085 {
3086 let snapshot = self.buffer().read(cx).snapshot(cx);
3087 let selections = selections.clone();
3088 let background_executor = cx.background_executor().clone();
3089 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3090 self.serialize_selections = cx.background_spawn(async move {
3091 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3092 let db_selections = selections
3093 .iter()
3094 .map(|selection| {
3095 (
3096 selection.start.to_offset(&snapshot),
3097 selection.end.to_offset(&snapshot),
3098 )
3099 })
3100 .collect();
3101
3102 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3103 .await
3104 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3105 .log_err();
3106 });
3107 }
3108 }
3109 }
3110 }
3111
3112 cx.notify();
3113 }
3114
3115 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3116 use text::ToOffset as _;
3117 use text::ToPoint as _;
3118
3119 if self.mode.is_minimap()
3120 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3121 {
3122 return;
3123 }
3124
3125 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3126 return;
3127 };
3128
3129 let snapshot = singleton.read(cx).snapshot();
3130 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3131 let display_snapshot = display_map.snapshot(cx);
3132
3133 display_snapshot
3134 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3135 .map(|fold| {
3136 fold.range.start.text_anchor.to_point(&snapshot)
3137 ..fold.range.end.text_anchor.to_point(&snapshot)
3138 })
3139 .collect()
3140 });
3141 self.update_restoration_data(cx, |data| {
3142 data.folds = inmemory_folds;
3143 });
3144
3145 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3146 return;
3147 };
3148 let background_executor = cx.background_executor().clone();
3149 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3150 let db_folds = self.display_map.update(cx, |display_map, cx| {
3151 display_map
3152 .snapshot(cx)
3153 .folds_in_range(0..snapshot.len())
3154 .map(|fold| {
3155 (
3156 fold.range.start.text_anchor.to_offset(&snapshot),
3157 fold.range.end.text_anchor.to_offset(&snapshot),
3158 )
3159 })
3160 .collect()
3161 });
3162 self.serialize_folds = cx.background_spawn(async move {
3163 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3164 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3165 .await
3166 .with_context(|| {
3167 format!(
3168 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3169 )
3170 })
3171 .log_err();
3172 });
3173 }
3174
3175 pub fn sync_selections(
3176 &mut self,
3177 other: Entity<Editor>,
3178 cx: &mut Context<Self>,
3179 ) -> gpui::Subscription {
3180 let other_selections = other.read(cx).selections.disjoint.to_vec();
3181 self.selections.change_with(cx, |selections| {
3182 selections.select_anchors(other_selections);
3183 });
3184
3185 let other_subscription =
3186 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3187 EditorEvent::SelectionsChanged { local: true } => {
3188 let other_selections = other.read(cx).selections.disjoint.to_vec();
3189 if other_selections.is_empty() {
3190 return;
3191 }
3192 this.selections.change_with(cx, |selections| {
3193 selections.select_anchors(other_selections);
3194 });
3195 }
3196 _ => {}
3197 });
3198
3199 let this_subscription =
3200 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3201 EditorEvent::SelectionsChanged { local: true } => {
3202 let these_selections = this.selections.disjoint.to_vec();
3203 if these_selections.is_empty() {
3204 return;
3205 }
3206 other.update(cx, |other_editor, cx| {
3207 other_editor.selections.change_with(cx, |selections| {
3208 selections.select_anchors(these_selections);
3209 })
3210 });
3211 }
3212 _ => {}
3213 });
3214
3215 Subscription::join(other_subscription, this_subscription)
3216 }
3217
3218 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3219 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3220 /// effects of selection change occur at the end of the transaction.
3221 pub fn change_selections<R>(
3222 &mut self,
3223 effects: SelectionEffects,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3227 ) -> R {
3228 if let Some(state) = &mut self.deferred_selection_effects_state {
3229 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3230 state.effects.completions = effects.completions;
3231 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3232 let (changed, result) = self.selections.change_with(cx, change);
3233 state.changed |= changed;
3234 return result;
3235 }
3236 let mut state = DeferredSelectionEffectsState {
3237 changed: false,
3238 effects,
3239 old_cursor_position: self.selections.newest_anchor().head(),
3240 history_entry: SelectionHistoryEntry {
3241 selections: self.selections.disjoint_anchors(),
3242 select_next_state: self.select_next_state.clone(),
3243 select_prev_state: self.select_prev_state.clone(),
3244 add_selections_state: self.add_selections_state.clone(),
3245 },
3246 };
3247 let (changed, result) = self.selections.change_with(cx, change);
3248 state.changed = state.changed || changed;
3249 if self.defer_selection_effects {
3250 self.deferred_selection_effects_state = Some(state);
3251 } else {
3252 self.apply_selection_effects(state, window, cx);
3253 }
3254 result
3255 }
3256
3257 /// Defers the effects of selection change, so that the effects of multiple calls to
3258 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3259 /// to selection history and the state of popovers based on selection position aren't
3260 /// erroneously updated.
3261 pub fn with_selection_effects_deferred<R>(
3262 &mut self,
3263 window: &mut Window,
3264 cx: &mut Context<Self>,
3265 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3266 ) -> R {
3267 let already_deferred = self.defer_selection_effects;
3268 self.defer_selection_effects = true;
3269 let result = update(self, window, cx);
3270 if !already_deferred {
3271 self.defer_selection_effects = false;
3272 if let Some(state) = self.deferred_selection_effects_state.take() {
3273 self.apply_selection_effects(state, window, cx);
3274 }
3275 }
3276 result
3277 }
3278
3279 fn apply_selection_effects(
3280 &mut self,
3281 state: DeferredSelectionEffectsState,
3282 window: &mut Window,
3283 cx: &mut Context<Self>,
3284 ) {
3285 if state.changed {
3286 self.selection_history.push(state.history_entry);
3287
3288 if let Some(autoscroll) = state.effects.scroll {
3289 self.request_autoscroll(autoscroll, cx);
3290 }
3291
3292 let old_cursor_position = &state.old_cursor_position;
3293
3294 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3295
3296 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3297 self.show_signature_help(&ShowSignatureHelp, window, cx);
3298 }
3299 }
3300 }
3301
3302 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3303 where
3304 I: IntoIterator<Item = (Range<S>, T)>,
3305 S: ToOffset,
3306 T: Into<Arc<str>>,
3307 {
3308 if self.read_only(cx) {
3309 return;
3310 }
3311
3312 self.buffer
3313 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3314 }
3315
3316 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3317 where
3318 I: IntoIterator<Item = (Range<S>, T)>,
3319 S: ToOffset,
3320 T: Into<Arc<str>>,
3321 {
3322 if self.read_only(cx) {
3323 return;
3324 }
3325
3326 self.buffer.update(cx, |buffer, cx| {
3327 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3328 });
3329 }
3330
3331 pub fn edit_with_block_indent<I, S, T>(
3332 &mut self,
3333 edits: I,
3334 original_indent_columns: Vec<Option<u32>>,
3335 cx: &mut Context<Self>,
3336 ) where
3337 I: IntoIterator<Item = (Range<S>, T)>,
3338 S: ToOffset,
3339 T: Into<Arc<str>>,
3340 {
3341 if self.read_only(cx) {
3342 return;
3343 }
3344
3345 self.buffer.update(cx, |buffer, cx| {
3346 buffer.edit(
3347 edits,
3348 Some(AutoindentMode::Block {
3349 original_indent_columns,
3350 }),
3351 cx,
3352 )
3353 });
3354 }
3355
3356 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3357 self.hide_context_menu(window, cx);
3358
3359 match phase {
3360 SelectPhase::Begin {
3361 position,
3362 add,
3363 click_count,
3364 } => self.begin_selection(position, add, click_count, window, cx),
3365 SelectPhase::BeginColumnar {
3366 position,
3367 goal_column,
3368 reset,
3369 mode,
3370 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3371 SelectPhase::Extend {
3372 position,
3373 click_count,
3374 } => self.extend_selection(position, click_count, window, cx),
3375 SelectPhase::Update {
3376 position,
3377 goal_column,
3378 scroll_delta,
3379 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3380 SelectPhase::End => self.end_selection(window, cx),
3381 }
3382 }
3383
3384 fn extend_selection(
3385 &mut self,
3386 position: DisplayPoint,
3387 click_count: usize,
3388 window: &mut Window,
3389 cx: &mut Context<Self>,
3390 ) {
3391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3392 let tail = self.selections.newest::<usize>(cx).tail();
3393 self.begin_selection(position, false, click_count, window, cx);
3394
3395 let position = position.to_offset(&display_map, Bias::Left);
3396 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3397
3398 let mut pending_selection = self
3399 .selections
3400 .pending_anchor()
3401 .expect("extend_selection not called with pending selection");
3402 if position >= tail {
3403 pending_selection.start = tail_anchor;
3404 } else {
3405 pending_selection.end = tail_anchor;
3406 pending_selection.reversed = true;
3407 }
3408
3409 let mut pending_mode = self.selections.pending_mode().unwrap();
3410 match &mut pending_mode {
3411 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3412 _ => {}
3413 }
3414
3415 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3416 SelectionEffects::scroll(Autoscroll::fit())
3417 } else {
3418 SelectionEffects::no_scroll()
3419 };
3420
3421 self.change_selections(effects, window, cx, |s| {
3422 s.set_pending(pending_selection, pending_mode)
3423 });
3424 }
3425
3426 fn begin_selection(
3427 &mut self,
3428 position: DisplayPoint,
3429 add: bool,
3430 click_count: usize,
3431 window: &mut Window,
3432 cx: &mut Context<Self>,
3433 ) {
3434 if !self.focus_handle.is_focused(window) {
3435 self.last_focused_descendant = None;
3436 window.focus(&self.focus_handle);
3437 }
3438
3439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3440 let buffer = &display_map.buffer_snapshot;
3441 let position = display_map.clip_point(position, Bias::Left);
3442
3443 let start;
3444 let end;
3445 let mode;
3446 let mut auto_scroll;
3447 match click_count {
3448 1 => {
3449 start = buffer.anchor_before(position.to_point(&display_map));
3450 end = start;
3451 mode = SelectMode::Character;
3452 auto_scroll = true;
3453 }
3454 2 => {
3455 let position = display_map
3456 .clip_point(position, Bias::Left)
3457 .to_offset(&display_map, Bias::Left);
3458 let (range, _) = buffer.surrounding_word(position, false);
3459 start = buffer.anchor_before(range.start);
3460 end = buffer.anchor_before(range.end);
3461 mode = SelectMode::Word(start..end);
3462 auto_scroll = true;
3463 }
3464 3 => {
3465 let position = display_map
3466 .clip_point(position, Bias::Left)
3467 .to_point(&display_map);
3468 let line_start = display_map.prev_line_boundary(position).0;
3469 let next_line_start = buffer.clip_point(
3470 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3471 Bias::Left,
3472 );
3473 start = buffer.anchor_before(line_start);
3474 end = buffer.anchor_before(next_line_start);
3475 mode = SelectMode::Line(start..end);
3476 auto_scroll = true;
3477 }
3478 _ => {
3479 start = buffer.anchor_before(0);
3480 end = buffer.anchor_before(buffer.len());
3481 mode = SelectMode::All;
3482 auto_scroll = false;
3483 }
3484 }
3485 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3486
3487 let point_to_delete: Option<usize> = {
3488 let selected_points: Vec<Selection<Point>> =
3489 self.selections.disjoint_in_range(start..end, cx);
3490
3491 if !add || click_count > 1 {
3492 None
3493 } else if !selected_points.is_empty() {
3494 Some(selected_points[0].id)
3495 } else {
3496 let clicked_point_already_selected =
3497 self.selections.disjoint.iter().find(|selection| {
3498 selection.start.to_point(buffer) == start.to_point(buffer)
3499 || selection.end.to_point(buffer) == end.to_point(buffer)
3500 });
3501
3502 clicked_point_already_selected.map(|selection| selection.id)
3503 }
3504 };
3505
3506 let selections_count = self.selections.count();
3507 let effects = if auto_scroll {
3508 SelectionEffects::default()
3509 } else {
3510 SelectionEffects::no_scroll()
3511 };
3512
3513 self.change_selections(effects, window, cx, |s| {
3514 if let Some(point_to_delete) = point_to_delete {
3515 s.delete(point_to_delete);
3516
3517 if selections_count == 1 {
3518 s.set_pending_anchor_range(start..end, mode);
3519 }
3520 } else {
3521 if !add {
3522 s.clear_disjoint();
3523 }
3524
3525 s.set_pending_anchor_range(start..end, mode);
3526 }
3527 });
3528 }
3529
3530 fn begin_columnar_selection(
3531 &mut self,
3532 position: DisplayPoint,
3533 goal_column: u32,
3534 reset: bool,
3535 mode: ColumnarMode,
3536 window: &mut Window,
3537 cx: &mut Context<Self>,
3538 ) {
3539 if !self.focus_handle.is_focused(window) {
3540 self.last_focused_descendant = None;
3541 window.focus(&self.focus_handle);
3542 }
3543
3544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3545
3546 if reset {
3547 let pointer_position = display_map
3548 .buffer_snapshot
3549 .anchor_before(position.to_point(&display_map));
3550
3551 self.change_selections(
3552 SelectionEffects::scroll(Autoscroll::newest()),
3553 window,
3554 cx,
3555 |s| {
3556 s.clear_disjoint();
3557 s.set_pending_anchor_range(
3558 pointer_position..pointer_position,
3559 SelectMode::Character,
3560 );
3561 },
3562 );
3563 };
3564
3565 let tail = self.selections.newest::<Point>(cx).tail();
3566 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3567 self.columnar_selection_state = match mode {
3568 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3569 selection_tail: selection_anchor,
3570 display_point: if reset {
3571 if position.column() != goal_column {
3572 Some(DisplayPoint::new(position.row(), goal_column))
3573 } else {
3574 None
3575 }
3576 } else {
3577 None
3578 },
3579 }),
3580 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3581 selection_tail: selection_anchor,
3582 }),
3583 };
3584
3585 if !reset {
3586 self.select_columns(position, goal_column, &display_map, window, cx);
3587 }
3588 }
3589
3590 fn update_selection(
3591 &mut self,
3592 position: DisplayPoint,
3593 goal_column: u32,
3594 scroll_delta: gpui::Point<f32>,
3595 window: &mut Window,
3596 cx: &mut Context<Self>,
3597 ) {
3598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3599
3600 if self.columnar_selection_state.is_some() {
3601 self.select_columns(position, goal_column, &display_map, window, cx);
3602 } else if let Some(mut pending) = self.selections.pending_anchor() {
3603 let buffer = &display_map.buffer_snapshot;
3604 let head;
3605 let tail;
3606 let mode = self.selections.pending_mode().unwrap();
3607 match &mode {
3608 SelectMode::Character => {
3609 head = position.to_point(&display_map);
3610 tail = pending.tail().to_point(buffer);
3611 }
3612 SelectMode::Word(original_range) => {
3613 let offset = display_map
3614 .clip_point(position, Bias::Left)
3615 .to_offset(&display_map, Bias::Left);
3616 let original_range = original_range.to_offset(buffer);
3617
3618 let head_offset = if buffer.is_inside_word(offset, false)
3619 || original_range.contains(&offset)
3620 {
3621 let (word_range, _) = buffer.surrounding_word(offset, false);
3622 if word_range.start < original_range.start {
3623 word_range.start
3624 } else {
3625 word_range.end
3626 }
3627 } else {
3628 offset
3629 };
3630
3631 head = head_offset.to_point(buffer);
3632 if head_offset <= original_range.start {
3633 tail = original_range.end.to_point(buffer);
3634 } else {
3635 tail = original_range.start.to_point(buffer);
3636 }
3637 }
3638 SelectMode::Line(original_range) => {
3639 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3640
3641 let position = display_map
3642 .clip_point(position, Bias::Left)
3643 .to_point(&display_map);
3644 let line_start = display_map.prev_line_boundary(position).0;
3645 let next_line_start = buffer.clip_point(
3646 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3647 Bias::Left,
3648 );
3649
3650 if line_start < original_range.start {
3651 head = line_start
3652 } else {
3653 head = next_line_start
3654 }
3655
3656 if head <= original_range.start {
3657 tail = original_range.end;
3658 } else {
3659 tail = original_range.start;
3660 }
3661 }
3662 SelectMode::All => {
3663 return;
3664 }
3665 };
3666
3667 if head < tail {
3668 pending.start = buffer.anchor_before(head);
3669 pending.end = buffer.anchor_before(tail);
3670 pending.reversed = true;
3671 } else {
3672 pending.start = buffer.anchor_before(tail);
3673 pending.end = buffer.anchor_before(head);
3674 pending.reversed = false;
3675 }
3676
3677 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3678 s.set_pending(pending, mode);
3679 });
3680 } else {
3681 log::error!("update_selection dispatched with no pending selection");
3682 return;
3683 }
3684
3685 self.apply_scroll_delta(scroll_delta, window, cx);
3686 cx.notify();
3687 }
3688
3689 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3690 self.columnar_selection_state.take();
3691 if self.selections.pending_anchor().is_some() {
3692 let selections = self.selections.all::<usize>(cx);
3693 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3694 s.select(selections);
3695 s.clear_pending();
3696 });
3697 }
3698 }
3699
3700 fn select_columns(
3701 &mut self,
3702 head: DisplayPoint,
3703 goal_column: u32,
3704 display_map: &DisplaySnapshot,
3705 window: &mut Window,
3706 cx: &mut Context<Self>,
3707 ) {
3708 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3709 return;
3710 };
3711
3712 let tail = match columnar_state {
3713 ColumnarSelectionState::FromMouse {
3714 selection_tail,
3715 display_point,
3716 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3717 ColumnarSelectionState::FromSelection { selection_tail } => {
3718 selection_tail.to_display_point(&display_map)
3719 }
3720 };
3721
3722 let start_row = cmp::min(tail.row(), head.row());
3723 let end_row = cmp::max(tail.row(), head.row());
3724 let start_column = cmp::min(tail.column(), goal_column);
3725 let end_column = cmp::max(tail.column(), goal_column);
3726 let reversed = start_column < tail.column();
3727
3728 let selection_ranges = (start_row.0..=end_row.0)
3729 .map(DisplayRow)
3730 .filter_map(|row| {
3731 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3732 || start_column <= display_map.line_len(row))
3733 && !display_map.is_block_line(row)
3734 {
3735 let start = display_map
3736 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3737 .to_point(display_map);
3738 let end = display_map
3739 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3740 .to_point(display_map);
3741 if reversed {
3742 Some(end..start)
3743 } else {
3744 Some(start..end)
3745 }
3746 } else {
3747 None
3748 }
3749 })
3750 .collect::<Vec<_>>();
3751
3752 let ranges = match columnar_state {
3753 ColumnarSelectionState::FromMouse { .. } => {
3754 let mut non_empty_ranges = selection_ranges
3755 .iter()
3756 .filter(|selection_range| selection_range.start != selection_range.end)
3757 .peekable();
3758 if non_empty_ranges.peek().is_some() {
3759 non_empty_ranges.cloned().collect()
3760 } else {
3761 selection_ranges
3762 }
3763 }
3764 _ => selection_ranges,
3765 };
3766
3767 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3768 s.select_ranges(ranges);
3769 });
3770 cx.notify();
3771 }
3772
3773 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3774 self.selections
3775 .all_adjusted(cx)
3776 .iter()
3777 .any(|selection| !selection.is_empty())
3778 }
3779
3780 pub fn has_pending_nonempty_selection(&self) -> bool {
3781 let pending_nonempty_selection = match self.selections.pending_anchor() {
3782 Some(Selection { start, end, .. }) => start != end,
3783 None => false,
3784 };
3785
3786 pending_nonempty_selection
3787 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3788 }
3789
3790 pub fn has_pending_selection(&self) -> bool {
3791 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3792 }
3793
3794 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3795 self.selection_mark_mode = false;
3796 self.selection_drag_state = SelectionDragState::None;
3797
3798 if self.clear_expanded_diff_hunks(cx) {
3799 cx.notify();
3800 return;
3801 }
3802 if self.dismiss_menus_and_popups(true, window, cx) {
3803 return;
3804 }
3805
3806 if self.mode.is_full()
3807 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3808 {
3809 return;
3810 }
3811
3812 cx.propagate();
3813 }
3814
3815 pub fn dismiss_menus_and_popups(
3816 &mut self,
3817 is_user_requested: bool,
3818 window: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) -> bool {
3821 if self.take_rename(false, window, cx).is_some() {
3822 return true;
3823 }
3824
3825 if hide_hover(self, cx) {
3826 return true;
3827 }
3828
3829 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3830 return true;
3831 }
3832
3833 if self.hide_context_menu(window, cx).is_some() {
3834 return true;
3835 }
3836
3837 if self.mouse_context_menu.take().is_some() {
3838 return true;
3839 }
3840
3841 if is_user_requested && self.discard_inline_completion(true, cx) {
3842 return true;
3843 }
3844
3845 if self.snippet_stack.pop().is_some() {
3846 return true;
3847 }
3848
3849 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3850 self.dismiss_diagnostics(cx);
3851 return true;
3852 }
3853
3854 false
3855 }
3856
3857 fn linked_editing_ranges_for(
3858 &self,
3859 selection: Range<text::Anchor>,
3860 cx: &App,
3861 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3862 if self.linked_edit_ranges.is_empty() {
3863 return None;
3864 }
3865 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3866 selection.end.buffer_id.and_then(|end_buffer_id| {
3867 if selection.start.buffer_id != Some(end_buffer_id) {
3868 return None;
3869 }
3870 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3871 let snapshot = buffer.read(cx).snapshot();
3872 self.linked_edit_ranges
3873 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3874 .map(|ranges| (ranges, snapshot, buffer))
3875 })?;
3876 use text::ToOffset as TO;
3877 // find offset from the start of current range to current cursor position
3878 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3879
3880 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3881 let start_difference = start_offset - start_byte_offset;
3882 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3883 let end_difference = end_offset - start_byte_offset;
3884 // Current range has associated linked ranges.
3885 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3886 for range in linked_ranges.iter() {
3887 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3888 let end_offset = start_offset + end_difference;
3889 let start_offset = start_offset + start_difference;
3890 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3891 continue;
3892 }
3893 if self.selections.disjoint_anchor_ranges().any(|s| {
3894 if s.start.buffer_id != selection.start.buffer_id
3895 || s.end.buffer_id != selection.end.buffer_id
3896 {
3897 return false;
3898 }
3899 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3900 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3901 }) {
3902 continue;
3903 }
3904 let start = buffer_snapshot.anchor_after(start_offset);
3905 let end = buffer_snapshot.anchor_after(end_offset);
3906 linked_edits
3907 .entry(buffer.clone())
3908 .or_default()
3909 .push(start..end);
3910 }
3911 Some(linked_edits)
3912 }
3913
3914 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3915 let text: Arc<str> = text.into();
3916
3917 if self.read_only(cx) {
3918 return;
3919 }
3920
3921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3922
3923 let selections = self.selections.all_adjusted(cx);
3924 let mut bracket_inserted = false;
3925 let mut edits = Vec::new();
3926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3927 let mut new_selections = Vec::with_capacity(selections.len());
3928 let mut new_autoclose_regions = Vec::new();
3929 let snapshot = self.buffer.read(cx).read(cx);
3930 let mut clear_linked_edit_ranges = false;
3931
3932 for (selection, autoclose_region) in
3933 self.selections_with_autoclose_regions(selections, &snapshot)
3934 {
3935 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3936 // Determine if the inserted text matches the opening or closing
3937 // bracket of any of this language's bracket pairs.
3938 let mut bracket_pair = None;
3939 let mut is_bracket_pair_start = false;
3940 let mut is_bracket_pair_end = false;
3941 if !text.is_empty() {
3942 let mut bracket_pair_matching_end = None;
3943 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3944 // and they are removing the character that triggered IME popup.
3945 for (pair, enabled) in scope.brackets() {
3946 if !pair.close && !pair.surround {
3947 continue;
3948 }
3949
3950 if enabled && pair.start.ends_with(text.as_ref()) {
3951 let prefix_len = pair.start.len() - text.len();
3952 let preceding_text_matches_prefix = prefix_len == 0
3953 || (selection.start.column >= (prefix_len as u32)
3954 && snapshot.contains_str_at(
3955 Point::new(
3956 selection.start.row,
3957 selection.start.column - (prefix_len as u32),
3958 ),
3959 &pair.start[..prefix_len],
3960 ));
3961 if preceding_text_matches_prefix {
3962 bracket_pair = Some(pair.clone());
3963 is_bracket_pair_start = true;
3964 break;
3965 }
3966 }
3967 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3968 {
3969 // take first bracket pair matching end, but don't break in case a later bracket
3970 // pair matches start
3971 bracket_pair_matching_end = Some(pair.clone());
3972 }
3973 }
3974 if let Some(end) = bracket_pair_matching_end
3975 && bracket_pair.is_none()
3976 {
3977 bracket_pair = Some(end);
3978 is_bracket_pair_end = true;
3979 }
3980 }
3981
3982 if let Some(bracket_pair) = bracket_pair {
3983 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3984 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3985 let auto_surround =
3986 self.use_auto_surround && snapshot_settings.use_auto_surround;
3987 if selection.is_empty() {
3988 if is_bracket_pair_start {
3989 // If the inserted text is a suffix of an opening bracket and the
3990 // selection is preceded by the rest of the opening bracket, then
3991 // insert the closing bracket.
3992 let following_text_allows_autoclose = snapshot
3993 .chars_at(selection.start)
3994 .next()
3995 .map_or(true, |c| scope.should_autoclose_before(c));
3996
3997 let preceding_text_allows_autoclose = selection.start.column == 0
3998 || snapshot.reversed_chars_at(selection.start).next().map_or(
3999 true,
4000 |c| {
4001 bracket_pair.start != bracket_pair.end
4002 || !snapshot
4003 .char_classifier_at(selection.start)
4004 .is_word(c)
4005 },
4006 );
4007
4008 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4009 && bracket_pair.start.len() == 1
4010 {
4011 let target = bracket_pair.start.chars().next().unwrap();
4012 let current_line_count = snapshot
4013 .reversed_chars_at(selection.start)
4014 .take_while(|&c| c != '\n')
4015 .filter(|&c| c == target)
4016 .count();
4017 current_line_count % 2 == 1
4018 } else {
4019 false
4020 };
4021
4022 if autoclose
4023 && bracket_pair.close
4024 && following_text_allows_autoclose
4025 && preceding_text_allows_autoclose
4026 && !is_closing_quote
4027 {
4028 let anchor = snapshot.anchor_before(selection.end);
4029 new_selections.push((selection.map(|_| anchor), text.len()));
4030 new_autoclose_regions.push((
4031 anchor,
4032 text.len(),
4033 selection.id,
4034 bracket_pair.clone(),
4035 ));
4036 edits.push((
4037 selection.range(),
4038 format!("{}{}", text, bracket_pair.end).into(),
4039 ));
4040 bracket_inserted = true;
4041 continue;
4042 }
4043 }
4044
4045 if let Some(region) = autoclose_region {
4046 // If the selection is followed by an auto-inserted closing bracket,
4047 // then don't insert that closing bracket again; just move the selection
4048 // past the closing bracket.
4049 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4050 && text.as_ref() == region.pair.end.as_str();
4051 if should_skip {
4052 let anchor = snapshot.anchor_after(selection.end);
4053 new_selections
4054 .push((selection.map(|_| anchor), region.pair.end.len()));
4055 continue;
4056 }
4057 }
4058
4059 let always_treat_brackets_as_autoclosed = snapshot
4060 .language_settings_at(selection.start, cx)
4061 .always_treat_brackets_as_autoclosed;
4062 if always_treat_brackets_as_autoclosed
4063 && is_bracket_pair_end
4064 && snapshot.contains_str_at(selection.end, text.as_ref())
4065 {
4066 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4067 // and the inserted text is a closing bracket and the selection is followed
4068 // by the closing bracket then move the selection past the closing bracket.
4069 let anchor = snapshot.anchor_after(selection.end);
4070 new_selections.push((selection.map(|_| anchor), text.len()));
4071 continue;
4072 }
4073 }
4074 // If an opening bracket is 1 character long and is typed while
4075 // text is selected, then surround that text with the bracket pair.
4076 else if auto_surround
4077 && bracket_pair.surround
4078 && is_bracket_pair_start
4079 && bracket_pair.start.chars().count() == 1
4080 {
4081 edits.push((selection.start..selection.start, text.clone()));
4082 edits.push((
4083 selection.end..selection.end,
4084 bracket_pair.end.as_str().into(),
4085 ));
4086 bracket_inserted = true;
4087 new_selections.push((
4088 Selection {
4089 id: selection.id,
4090 start: snapshot.anchor_after(selection.start),
4091 end: snapshot.anchor_before(selection.end),
4092 reversed: selection.reversed,
4093 goal: selection.goal,
4094 },
4095 0,
4096 ));
4097 continue;
4098 }
4099 }
4100 }
4101
4102 if self.auto_replace_emoji_shortcode
4103 && selection.is_empty()
4104 && text.as_ref().ends_with(':')
4105 {
4106 if let Some(possible_emoji_short_code) =
4107 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4108 {
4109 if !possible_emoji_short_code.is_empty() {
4110 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4111 let emoji_shortcode_start = Point::new(
4112 selection.start.row,
4113 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4114 );
4115
4116 // Remove shortcode from buffer
4117 edits.push((
4118 emoji_shortcode_start..selection.start,
4119 "".to_string().into(),
4120 ));
4121 new_selections.push((
4122 Selection {
4123 id: selection.id,
4124 start: snapshot.anchor_after(emoji_shortcode_start),
4125 end: snapshot.anchor_before(selection.start),
4126 reversed: selection.reversed,
4127 goal: selection.goal,
4128 },
4129 0,
4130 ));
4131
4132 // Insert emoji
4133 let selection_start_anchor = snapshot.anchor_after(selection.start);
4134 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4135 edits.push((selection.start..selection.end, emoji.to_string().into()));
4136
4137 continue;
4138 }
4139 }
4140 }
4141 }
4142
4143 // If not handling any auto-close operation, then just replace the selected
4144 // text with the given input and move the selection to the end of the
4145 // newly inserted text.
4146 let anchor = snapshot.anchor_after(selection.end);
4147 if !self.linked_edit_ranges.is_empty() {
4148 let start_anchor = snapshot.anchor_before(selection.start);
4149
4150 let is_word_char = text.chars().next().map_or(true, |char| {
4151 let classifier = snapshot
4152 .char_classifier_at(start_anchor.to_offset(&snapshot))
4153 .ignore_punctuation(true);
4154 classifier.is_word(char)
4155 });
4156
4157 if is_word_char {
4158 if let Some(ranges) = self
4159 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4160 {
4161 for (buffer, edits) in ranges {
4162 linked_edits
4163 .entry(buffer.clone())
4164 .or_default()
4165 .extend(edits.into_iter().map(|range| (range, text.clone())));
4166 }
4167 }
4168 } else {
4169 clear_linked_edit_ranges = true;
4170 }
4171 }
4172
4173 new_selections.push((selection.map(|_| anchor), 0));
4174 edits.push((selection.start..selection.end, text.clone()));
4175 }
4176
4177 drop(snapshot);
4178
4179 self.transact(window, cx, |this, window, cx| {
4180 if clear_linked_edit_ranges {
4181 this.linked_edit_ranges.clear();
4182 }
4183 let initial_buffer_versions =
4184 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4185
4186 this.buffer.update(cx, |buffer, cx| {
4187 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4188 });
4189 for (buffer, edits) in linked_edits {
4190 buffer.update(cx, |buffer, cx| {
4191 let snapshot = buffer.snapshot();
4192 let edits = edits
4193 .into_iter()
4194 .map(|(range, text)| {
4195 use text::ToPoint as TP;
4196 let end_point = TP::to_point(&range.end, &snapshot);
4197 let start_point = TP::to_point(&range.start, &snapshot);
4198 (start_point..end_point, text)
4199 })
4200 .sorted_by_key(|(range, _)| range.start);
4201 buffer.edit(edits, None, cx);
4202 })
4203 }
4204 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4205 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4206 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4207 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4208 .zip(new_selection_deltas)
4209 .map(|(selection, delta)| Selection {
4210 id: selection.id,
4211 start: selection.start + delta,
4212 end: selection.end + delta,
4213 reversed: selection.reversed,
4214 goal: SelectionGoal::None,
4215 })
4216 .collect::<Vec<_>>();
4217
4218 let mut i = 0;
4219 for (position, delta, selection_id, pair) in new_autoclose_regions {
4220 let position = position.to_offset(&map.buffer_snapshot) + delta;
4221 let start = map.buffer_snapshot.anchor_before(position);
4222 let end = map.buffer_snapshot.anchor_after(position);
4223 while let Some(existing_state) = this.autoclose_regions.get(i) {
4224 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4225 Ordering::Less => i += 1,
4226 Ordering::Greater => break,
4227 Ordering::Equal => {
4228 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4229 Ordering::Less => i += 1,
4230 Ordering::Equal => break,
4231 Ordering::Greater => break,
4232 }
4233 }
4234 }
4235 }
4236 this.autoclose_regions.insert(
4237 i,
4238 AutocloseRegion {
4239 selection_id,
4240 range: start..end,
4241 pair,
4242 },
4243 );
4244 }
4245
4246 let had_active_inline_completion = this.has_active_inline_completion();
4247 this.change_selections(
4248 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4249 window,
4250 cx,
4251 |s| s.select(new_selections),
4252 );
4253
4254 if !bracket_inserted {
4255 if let Some(on_type_format_task) =
4256 this.trigger_on_type_formatting(text.to_string(), window, cx)
4257 {
4258 on_type_format_task.detach_and_log_err(cx);
4259 }
4260 }
4261
4262 let editor_settings = EditorSettings::get_global(cx);
4263 if bracket_inserted
4264 && (editor_settings.auto_signature_help
4265 || editor_settings.show_signature_help_after_edits)
4266 {
4267 this.show_signature_help(&ShowSignatureHelp, window, cx);
4268 }
4269
4270 let trigger_in_words =
4271 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4272 if this.hard_wrap.is_some() {
4273 let latest: Range<Point> = this.selections.newest(cx).range();
4274 if latest.is_empty()
4275 && this
4276 .buffer()
4277 .read(cx)
4278 .snapshot(cx)
4279 .line_len(MultiBufferRow(latest.start.row))
4280 == latest.start.column
4281 {
4282 this.rewrap_impl(
4283 RewrapOptions {
4284 override_language_settings: true,
4285 preserve_existing_whitespace: true,
4286 },
4287 cx,
4288 )
4289 }
4290 }
4291 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4292 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4293 this.refresh_inline_completion(true, false, window, cx);
4294 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4295 });
4296 }
4297
4298 fn find_possible_emoji_shortcode_at_position(
4299 snapshot: &MultiBufferSnapshot,
4300 position: Point,
4301 ) -> Option<String> {
4302 let mut chars = Vec::new();
4303 let mut found_colon = false;
4304 for char in snapshot.reversed_chars_at(position).take(100) {
4305 // Found a possible emoji shortcode in the middle of the buffer
4306 if found_colon {
4307 if char.is_whitespace() {
4308 chars.reverse();
4309 return Some(chars.iter().collect());
4310 }
4311 // If the previous character is not a whitespace, we are in the middle of a word
4312 // and we only want to complete the shortcode if the word is made up of other emojis
4313 let mut containing_word = String::new();
4314 for ch in snapshot
4315 .reversed_chars_at(position)
4316 .skip(chars.len() + 1)
4317 .take(100)
4318 {
4319 if ch.is_whitespace() {
4320 break;
4321 }
4322 containing_word.push(ch);
4323 }
4324 let containing_word = containing_word.chars().rev().collect::<String>();
4325 if util::word_consists_of_emojis(containing_word.as_str()) {
4326 chars.reverse();
4327 return Some(chars.iter().collect());
4328 }
4329 }
4330
4331 if char.is_whitespace() || !char.is_ascii() {
4332 return None;
4333 }
4334 if char == ':' {
4335 found_colon = true;
4336 } else {
4337 chars.push(char);
4338 }
4339 }
4340 // Found a possible emoji shortcode at the beginning of the buffer
4341 chars.reverse();
4342 Some(chars.iter().collect())
4343 }
4344
4345 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4347 self.transact(window, cx, |this, window, cx| {
4348 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4349 let selections = this.selections.all::<usize>(cx);
4350 let multi_buffer = this.buffer.read(cx);
4351 let buffer = multi_buffer.snapshot(cx);
4352 selections
4353 .iter()
4354 .map(|selection| {
4355 let start_point = selection.start.to_point(&buffer);
4356 let mut existing_indent =
4357 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4358 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4359 let start = selection.start;
4360 let end = selection.end;
4361 let selection_is_empty = start == end;
4362 let language_scope = buffer.language_scope_at(start);
4363 let (
4364 comment_delimiter,
4365 doc_delimiter,
4366 insert_extra_newline,
4367 indent_on_newline,
4368 indent_on_extra_newline,
4369 ) = if let Some(language) = &language_scope {
4370 let mut insert_extra_newline =
4371 insert_extra_newline_brackets(&buffer, start..end, language)
4372 || insert_extra_newline_tree_sitter(&buffer, start..end);
4373
4374 // Comment extension on newline is allowed only for cursor selections
4375 let comment_delimiter = maybe!({
4376 if !selection_is_empty {
4377 return None;
4378 }
4379
4380 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4381 return None;
4382 }
4383
4384 let delimiters = language.line_comment_prefixes();
4385 let max_len_of_delimiter =
4386 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4387 let (snapshot, range) =
4388 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4389
4390 let num_of_whitespaces = snapshot
4391 .chars_for_range(range.clone())
4392 .take_while(|c| c.is_whitespace())
4393 .count();
4394 let comment_candidate = snapshot
4395 .chars_for_range(range.clone())
4396 .skip(num_of_whitespaces)
4397 .take(max_len_of_delimiter)
4398 .collect::<String>();
4399 let (delimiter, trimmed_len) = delimiters
4400 .iter()
4401 .filter_map(|delimiter| {
4402 let prefix = delimiter.trim_end();
4403 if comment_candidate.starts_with(prefix) {
4404 Some((delimiter, prefix.len()))
4405 } else {
4406 None
4407 }
4408 })
4409 .max_by_key(|(_, len)| *len)?;
4410
4411 if let Some(BlockCommentConfig {
4412 start: block_start, ..
4413 }) = language.block_comment()
4414 {
4415 let block_start_trimmed = block_start.trim_end();
4416 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4417 let line_content = snapshot
4418 .chars_for_range(range)
4419 .skip(num_of_whitespaces)
4420 .take(block_start_trimmed.len())
4421 .collect::<String>();
4422
4423 if line_content.starts_with(block_start_trimmed) {
4424 return None;
4425 }
4426 }
4427 }
4428
4429 let cursor_is_placed_after_comment_marker =
4430 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4431 if cursor_is_placed_after_comment_marker {
4432 Some(delimiter.clone())
4433 } else {
4434 None
4435 }
4436 });
4437
4438 let mut indent_on_newline = IndentSize::spaces(0);
4439 let mut indent_on_extra_newline = IndentSize::spaces(0);
4440
4441 let doc_delimiter = maybe!({
4442 if !selection_is_empty {
4443 return None;
4444 }
4445
4446 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4447 return None;
4448 }
4449
4450 let BlockCommentConfig {
4451 start: start_tag,
4452 end: end_tag,
4453 prefix: delimiter,
4454 tab_size: len,
4455 } = language.documentation_comment()?;
4456 let is_within_block_comment = buffer
4457 .language_scope_at(start_point)
4458 .is_some_and(|scope| scope.override_name() == Some("comment"));
4459 if !is_within_block_comment {
4460 return None;
4461 }
4462
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
4471 // 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.
4472 let column = start_point.column;
4473 let cursor_is_after_start_tag = {
4474 let start_tag_len = start_tag.len();
4475 let start_tag_line = snapshot
4476 .chars_for_range(range.clone())
4477 .skip(num_of_whitespaces)
4478 .take(start_tag_len)
4479 .collect::<String>();
4480 if start_tag_line.starts_with(start_tag.as_ref()) {
4481 num_of_whitespaces + start_tag_len <= column as usize
4482 } else {
4483 false
4484 }
4485 };
4486
4487 let cursor_is_after_delimiter = {
4488 let delimiter_trim = delimiter.trim_end();
4489 let delimiter_line = snapshot
4490 .chars_for_range(range.clone())
4491 .skip(num_of_whitespaces)
4492 .take(delimiter_trim.len())
4493 .collect::<String>();
4494 if delimiter_line.starts_with(delimiter_trim) {
4495 num_of_whitespaces + delimiter_trim.len() <= column as usize
4496 } else {
4497 false
4498 }
4499 };
4500
4501 let cursor_is_before_end_tag_if_exists = {
4502 let mut char_position = 0u32;
4503 let mut end_tag_offset = None;
4504
4505 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4506 if let Some(byte_pos) = chunk.find(&**end_tag) {
4507 let chars_before_match =
4508 chunk[..byte_pos].chars().count() as u32;
4509 end_tag_offset =
4510 Some(char_position + chars_before_match);
4511 break 'outer;
4512 }
4513 char_position += chunk.chars().count() as u32;
4514 }
4515
4516 if let Some(end_tag_offset) = end_tag_offset {
4517 let cursor_is_before_end_tag = column <= end_tag_offset;
4518 if cursor_is_after_start_tag {
4519 if cursor_is_before_end_tag {
4520 insert_extra_newline = true;
4521 }
4522 let cursor_is_at_start_of_end_tag =
4523 column == end_tag_offset;
4524 if cursor_is_at_start_of_end_tag {
4525 indent_on_extra_newline.len = *len;
4526 }
4527 }
4528 cursor_is_before_end_tag
4529 } else {
4530 true
4531 }
4532 };
4533
4534 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4535 && cursor_is_before_end_tag_if_exists
4536 {
4537 if cursor_is_after_start_tag {
4538 indent_on_newline.len = *len;
4539 }
4540 Some(delimiter.clone())
4541 } else {
4542 None
4543 }
4544 });
4545
4546 (
4547 comment_delimiter,
4548 doc_delimiter,
4549 insert_extra_newline,
4550 indent_on_newline,
4551 indent_on_extra_newline,
4552 )
4553 } else {
4554 (
4555 None,
4556 None,
4557 false,
4558 IndentSize::default(),
4559 IndentSize::default(),
4560 )
4561 };
4562
4563 let prevent_auto_indent = doc_delimiter.is_some();
4564 let delimiter = comment_delimiter.or(doc_delimiter);
4565
4566 let capacity_for_delimiter =
4567 delimiter.as_deref().map(str::len).unwrap_or_default();
4568 let mut new_text = String::with_capacity(
4569 1 + capacity_for_delimiter
4570 + existing_indent.len as usize
4571 + indent_on_newline.len as usize
4572 + indent_on_extra_newline.len as usize,
4573 );
4574 new_text.push('\n');
4575 new_text.extend(existing_indent.chars());
4576 new_text.extend(indent_on_newline.chars());
4577
4578 if let Some(delimiter) = &delimiter {
4579 new_text.push_str(delimiter);
4580 }
4581
4582 if insert_extra_newline {
4583 new_text.push('\n');
4584 new_text.extend(existing_indent.chars());
4585 new_text.extend(indent_on_extra_newline.chars());
4586 }
4587
4588 let anchor = buffer.anchor_after(end);
4589 let new_selection = selection.map(|_| anchor);
4590 (
4591 ((start..end, new_text), prevent_auto_indent),
4592 (insert_extra_newline, new_selection),
4593 )
4594 })
4595 .unzip()
4596 };
4597
4598 let mut auto_indent_edits = Vec::new();
4599 let mut edits = Vec::new();
4600 for (edit, prevent_auto_indent) in edits_with_flags {
4601 if prevent_auto_indent {
4602 edits.push(edit);
4603 } else {
4604 auto_indent_edits.push(edit);
4605 }
4606 }
4607 if !edits.is_empty() {
4608 this.edit(edits, cx);
4609 }
4610 if !auto_indent_edits.is_empty() {
4611 this.edit_with_autoindent(auto_indent_edits, cx);
4612 }
4613
4614 let buffer = this.buffer.read(cx).snapshot(cx);
4615 let new_selections = selection_info
4616 .into_iter()
4617 .map(|(extra_newline_inserted, new_selection)| {
4618 let mut cursor = new_selection.end.to_point(&buffer);
4619 if extra_newline_inserted {
4620 cursor.row -= 1;
4621 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4622 }
4623 new_selection.map(|_| cursor)
4624 })
4625 .collect();
4626
4627 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4628 this.refresh_inline_completion(true, false, window, cx);
4629 });
4630 }
4631
4632 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4634
4635 let buffer = self.buffer.read(cx);
4636 let snapshot = buffer.snapshot(cx);
4637
4638 let mut edits = Vec::new();
4639 let mut rows = Vec::new();
4640
4641 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4642 let cursor = selection.head();
4643 let row = cursor.row;
4644
4645 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4646
4647 let newline = "\n".to_string();
4648 edits.push((start_of_line..start_of_line, newline));
4649
4650 rows.push(row + rows_inserted as u32);
4651 }
4652
4653 self.transact(window, cx, |editor, window, cx| {
4654 editor.edit(edits, cx);
4655
4656 editor.change_selections(Default::default(), window, cx, |s| {
4657 let mut index = 0;
4658 s.move_cursors_with(|map, _, _| {
4659 let row = rows[index];
4660 index += 1;
4661
4662 let point = Point::new(row, 0);
4663 let boundary = map.next_line_boundary(point).1;
4664 let clipped = map.clip_point(boundary, Bias::Left);
4665
4666 (clipped, SelectionGoal::None)
4667 });
4668 });
4669
4670 let mut indent_edits = Vec::new();
4671 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4672 for row in rows {
4673 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4674 for (row, indent) in indents {
4675 if indent.len == 0 {
4676 continue;
4677 }
4678
4679 let text = match indent.kind {
4680 IndentKind::Space => " ".repeat(indent.len as usize),
4681 IndentKind::Tab => "\t".repeat(indent.len as usize),
4682 };
4683 let point = Point::new(row.0, 0);
4684 indent_edits.push((point..point, text));
4685 }
4686 }
4687 editor.edit(indent_edits, cx);
4688 });
4689 }
4690
4691 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4693
4694 let buffer = self.buffer.read(cx);
4695 let snapshot = buffer.snapshot(cx);
4696
4697 let mut edits = Vec::new();
4698 let mut rows = Vec::new();
4699 let mut rows_inserted = 0;
4700
4701 for selection in self.selections.all_adjusted(cx) {
4702 let cursor = selection.head();
4703 let row = cursor.row;
4704
4705 let point = Point::new(row + 1, 0);
4706 let start_of_line = snapshot.clip_point(point, Bias::Left);
4707
4708 let newline = "\n".to_string();
4709 edits.push((start_of_line..start_of_line, newline));
4710
4711 rows_inserted += 1;
4712 rows.push(row + rows_inserted);
4713 }
4714
4715 self.transact(window, cx, |editor, window, cx| {
4716 editor.edit(edits, cx);
4717
4718 editor.change_selections(Default::default(), window, cx, |s| {
4719 let mut index = 0;
4720 s.move_cursors_with(|map, _, _| {
4721 let row = rows[index];
4722 index += 1;
4723
4724 let point = Point::new(row, 0);
4725 let boundary = map.next_line_boundary(point).1;
4726 let clipped = map.clip_point(boundary, Bias::Left);
4727
4728 (clipped, SelectionGoal::None)
4729 });
4730 });
4731
4732 let mut indent_edits = Vec::new();
4733 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4734 for row in rows {
4735 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4736 for (row, indent) in indents {
4737 if indent.len == 0 {
4738 continue;
4739 }
4740
4741 let text = match indent.kind {
4742 IndentKind::Space => " ".repeat(indent.len as usize),
4743 IndentKind::Tab => "\t".repeat(indent.len as usize),
4744 };
4745 let point = Point::new(row.0, 0);
4746 indent_edits.push((point..point, text));
4747 }
4748 }
4749 editor.edit(indent_edits, cx);
4750 });
4751 }
4752
4753 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4754 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4755 original_indent_columns: Vec::new(),
4756 });
4757 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4758 }
4759
4760 fn insert_with_autoindent_mode(
4761 &mut self,
4762 text: &str,
4763 autoindent_mode: Option<AutoindentMode>,
4764 window: &mut Window,
4765 cx: &mut Context<Self>,
4766 ) {
4767 if self.read_only(cx) {
4768 return;
4769 }
4770
4771 let text: Arc<str> = text.into();
4772 self.transact(window, cx, |this, window, cx| {
4773 let old_selections = this.selections.all_adjusted(cx);
4774 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4775 let anchors = {
4776 let snapshot = buffer.read(cx);
4777 old_selections
4778 .iter()
4779 .map(|s| {
4780 let anchor = snapshot.anchor_after(s.head());
4781 s.map(|_| anchor)
4782 })
4783 .collect::<Vec<_>>()
4784 };
4785 buffer.edit(
4786 old_selections
4787 .iter()
4788 .map(|s| (s.start..s.end, text.clone())),
4789 autoindent_mode,
4790 cx,
4791 );
4792 anchors
4793 });
4794
4795 this.change_selections(Default::default(), window, cx, |s| {
4796 s.select_anchors(selection_anchors);
4797 });
4798
4799 cx.notify();
4800 });
4801 }
4802
4803 fn trigger_completion_on_input(
4804 &mut self,
4805 text: &str,
4806 trigger_in_words: bool,
4807 window: &mut Window,
4808 cx: &mut Context<Self>,
4809 ) {
4810 let completions_source = self
4811 .context_menu
4812 .borrow()
4813 .as_ref()
4814 .and_then(|menu| match menu {
4815 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4816 CodeContextMenu::CodeActions(_) => None,
4817 });
4818
4819 match completions_source {
4820 Some(CompletionsMenuSource::Words) => {
4821 self.show_word_completions(&ShowWordCompletions, window, cx)
4822 }
4823 Some(CompletionsMenuSource::Normal)
4824 | Some(CompletionsMenuSource::SnippetChoices)
4825 | None
4826 if self.is_completion_trigger(
4827 text,
4828 trigger_in_words,
4829 completions_source.is_some(),
4830 cx,
4831 ) =>
4832 {
4833 self.show_completions(
4834 &ShowCompletions {
4835 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4836 },
4837 window,
4838 cx,
4839 )
4840 }
4841 _ => {
4842 self.hide_context_menu(window, cx);
4843 }
4844 }
4845 }
4846
4847 fn is_completion_trigger(
4848 &self,
4849 text: &str,
4850 trigger_in_words: bool,
4851 menu_is_open: bool,
4852 cx: &mut Context<Self>,
4853 ) -> bool {
4854 let position = self.selections.newest_anchor().head();
4855 let multibuffer = self.buffer.read(cx);
4856 let Some(buffer) = position
4857 .buffer_id
4858 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4859 else {
4860 return false;
4861 };
4862
4863 if let Some(completion_provider) = &self.completion_provider {
4864 completion_provider.is_completion_trigger(
4865 &buffer,
4866 position.text_anchor,
4867 text,
4868 trigger_in_words,
4869 menu_is_open,
4870 cx,
4871 )
4872 } else {
4873 false
4874 }
4875 }
4876
4877 /// If any empty selections is touching the start of its innermost containing autoclose
4878 /// region, expand it to select the brackets.
4879 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4880 let selections = self.selections.all::<usize>(cx);
4881 let buffer = self.buffer.read(cx).read(cx);
4882 let new_selections = self
4883 .selections_with_autoclose_regions(selections, &buffer)
4884 .map(|(mut selection, region)| {
4885 if !selection.is_empty() {
4886 return selection;
4887 }
4888
4889 if let Some(region) = region {
4890 let mut range = region.range.to_offset(&buffer);
4891 if selection.start == range.start && range.start >= region.pair.start.len() {
4892 range.start -= region.pair.start.len();
4893 if buffer.contains_str_at(range.start, ®ion.pair.start)
4894 && buffer.contains_str_at(range.end, ®ion.pair.end)
4895 {
4896 range.end += region.pair.end.len();
4897 selection.start = range.start;
4898 selection.end = range.end;
4899
4900 return selection;
4901 }
4902 }
4903 }
4904
4905 let always_treat_brackets_as_autoclosed = buffer
4906 .language_settings_at(selection.start, cx)
4907 .always_treat_brackets_as_autoclosed;
4908
4909 if !always_treat_brackets_as_autoclosed {
4910 return selection;
4911 }
4912
4913 if let Some(scope) = buffer.language_scope_at(selection.start) {
4914 for (pair, enabled) in scope.brackets() {
4915 if !enabled || !pair.close {
4916 continue;
4917 }
4918
4919 if buffer.contains_str_at(selection.start, &pair.end) {
4920 let pair_start_len = pair.start.len();
4921 if buffer.contains_str_at(
4922 selection.start.saturating_sub(pair_start_len),
4923 &pair.start,
4924 ) {
4925 selection.start -= pair_start_len;
4926 selection.end += pair.end.len();
4927
4928 return selection;
4929 }
4930 }
4931 }
4932 }
4933
4934 selection
4935 })
4936 .collect();
4937
4938 drop(buffer);
4939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4940 selections.select(new_selections)
4941 });
4942 }
4943
4944 /// Iterate the given selections, and for each one, find the smallest surrounding
4945 /// autoclose region. This uses the ordering of the selections and the autoclose
4946 /// regions to avoid repeated comparisons.
4947 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4948 &'a self,
4949 selections: impl IntoIterator<Item = Selection<D>>,
4950 buffer: &'a MultiBufferSnapshot,
4951 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4952 let mut i = 0;
4953 let mut regions = self.autoclose_regions.as_slice();
4954 selections.into_iter().map(move |selection| {
4955 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4956
4957 let mut enclosing = None;
4958 while let Some(pair_state) = regions.get(i) {
4959 if pair_state.range.end.to_offset(buffer) < range.start {
4960 regions = ®ions[i + 1..];
4961 i = 0;
4962 } else if pair_state.range.start.to_offset(buffer) > range.end {
4963 break;
4964 } else {
4965 if pair_state.selection_id == selection.id {
4966 enclosing = Some(pair_state);
4967 }
4968 i += 1;
4969 }
4970 }
4971
4972 (selection, enclosing)
4973 })
4974 }
4975
4976 /// Remove any autoclose regions that no longer contain their selection.
4977 fn invalidate_autoclose_regions(
4978 &mut self,
4979 mut selections: &[Selection<Anchor>],
4980 buffer: &MultiBufferSnapshot,
4981 ) {
4982 self.autoclose_regions.retain(|state| {
4983 let mut i = 0;
4984 while let Some(selection) = selections.get(i) {
4985 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4986 selections = &selections[1..];
4987 continue;
4988 }
4989 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4990 break;
4991 }
4992 if selection.id == state.selection_id {
4993 return true;
4994 } else {
4995 i += 1;
4996 }
4997 }
4998 false
4999 });
5000 }
5001
5002 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5003 let offset = position.to_offset(buffer);
5004 let (word_range, kind) = buffer.surrounding_word(offset, true);
5005 if offset > word_range.start && kind == Some(CharKind::Word) {
5006 Some(
5007 buffer
5008 .text_for_range(word_range.start..offset)
5009 .collect::<String>(),
5010 )
5011 } else {
5012 None
5013 }
5014 }
5015
5016 pub fn toggle_inline_values(
5017 &mut self,
5018 _: &ToggleInlineValues,
5019 _: &mut Window,
5020 cx: &mut Context<Self>,
5021 ) {
5022 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5023
5024 self.refresh_inline_values(cx);
5025 }
5026
5027 pub fn toggle_inlay_hints(
5028 &mut self,
5029 _: &ToggleInlayHints,
5030 _: &mut Window,
5031 cx: &mut Context<Self>,
5032 ) {
5033 self.refresh_inlay_hints(
5034 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5035 cx,
5036 );
5037 }
5038
5039 pub fn inlay_hints_enabled(&self) -> bool {
5040 self.inlay_hint_cache.enabled
5041 }
5042
5043 pub fn inline_values_enabled(&self) -> bool {
5044 self.inline_value_cache.enabled
5045 }
5046
5047 #[cfg(any(test, feature = "test-support"))]
5048 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5049 self.display_map
5050 .read(cx)
5051 .current_inlays()
5052 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5053 .cloned()
5054 .collect()
5055 }
5056
5057 #[cfg(any(test, feature = "test-support"))]
5058 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5059 self.display_map
5060 .read(cx)
5061 .current_inlays()
5062 .cloned()
5063 .collect()
5064 }
5065
5066 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5067 if self.semantics_provider.is_none() || !self.mode.is_full() {
5068 return;
5069 }
5070
5071 let reason_description = reason.description();
5072 let ignore_debounce = matches!(
5073 reason,
5074 InlayHintRefreshReason::SettingsChange(_)
5075 | InlayHintRefreshReason::Toggle(_)
5076 | InlayHintRefreshReason::ExcerptsRemoved(_)
5077 | InlayHintRefreshReason::ModifiersChanged(_)
5078 );
5079 let (invalidate_cache, required_languages) = match reason {
5080 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5081 match self.inlay_hint_cache.modifiers_override(enabled) {
5082 Some(enabled) => {
5083 if enabled {
5084 (InvalidationStrategy::RefreshRequested, None)
5085 } else {
5086 self.splice_inlays(
5087 &self
5088 .visible_inlay_hints(cx)
5089 .iter()
5090 .map(|inlay| inlay.id)
5091 .collect::<Vec<InlayId>>(),
5092 Vec::new(),
5093 cx,
5094 );
5095 return;
5096 }
5097 }
5098 None => return,
5099 }
5100 }
5101 InlayHintRefreshReason::Toggle(enabled) => {
5102 if self.inlay_hint_cache.toggle(enabled) {
5103 if enabled {
5104 (InvalidationStrategy::RefreshRequested, None)
5105 } else {
5106 self.splice_inlays(
5107 &self
5108 .visible_inlay_hints(cx)
5109 .iter()
5110 .map(|inlay| inlay.id)
5111 .collect::<Vec<InlayId>>(),
5112 Vec::new(),
5113 cx,
5114 );
5115 return;
5116 }
5117 } else {
5118 return;
5119 }
5120 }
5121 InlayHintRefreshReason::SettingsChange(new_settings) => {
5122 match self.inlay_hint_cache.update_settings(
5123 &self.buffer,
5124 new_settings,
5125 self.visible_inlay_hints(cx),
5126 cx,
5127 ) {
5128 ControlFlow::Break(Some(InlaySplice {
5129 to_remove,
5130 to_insert,
5131 })) => {
5132 self.splice_inlays(&to_remove, to_insert, cx);
5133 return;
5134 }
5135 ControlFlow::Break(None) => return,
5136 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5137 }
5138 }
5139 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5140 if let Some(InlaySplice {
5141 to_remove,
5142 to_insert,
5143 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5144 {
5145 self.splice_inlays(&to_remove, to_insert, cx);
5146 }
5147 self.display_map.update(cx, |display_map, _| {
5148 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5149 });
5150 return;
5151 }
5152 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5153 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5154 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5155 }
5156 InlayHintRefreshReason::RefreshRequested => {
5157 (InvalidationStrategy::RefreshRequested, None)
5158 }
5159 };
5160
5161 if let Some(InlaySplice {
5162 to_remove,
5163 to_insert,
5164 }) = self.inlay_hint_cache.spawn_hint_refresh(
5165 reason_description,
5166 self.visible_excerpts(required_languages.as_ref(), cx),
5167 invalidate_cache,
5168 ignore_debounce,
5169 cx,
5170 ) {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 }
5174
5175 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5176 self.display_map
5177 .read(cx)
5178 .current_inlays()
5179 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5180 .cloned()
5181 .collect()
5182 }
5183
5184 pub fn visible_excerpts(
5185 &self,
5186 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5187 cx: &mut Context<Editor>,
5188 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5189 let Some(project) = self.project.as_ref() else {
5190 return HashMap::default();
5191 };
5192 let project = project.read(cx);
5193 let multi_buffer = self.buffer().read(cx);
5194 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5195 let multi_buffer_visible_start = self
5196 .scroll_manager
5197 .anchor()
5198 .anchor
5199 .to_point(&multi_buffer_snapshot);
5200 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5201 multi_buffer_visible_start
5202 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5203 Bias::Left,
5204 );
5205 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5206 multi_buffer_snapshot
5207 .range_to_buffer_ranges(multi_buffer_visible_range)
5208 .into_iter()
5209 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5210 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5211 let buffer_file = project::File::from_dyn(buffer.file())?;
5212 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5213 let worktree_entry = buffer_worktree
5214 .read(cx)
5215 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5216 if worktree_entry.is_ignored {
5217 return None;
5218 }
5219
5220 let language = buffer.language()?;
5221 if let Some(restrict_to_languages) = restrict_to_languages {
5222 if !restrict_to_languages.contains(language) {
5223 return None;
5224 }
5225 }
5226 Some((
5227 excerpt_id,
5228 (
5229 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5230 buffer.version().clone(),
5231 excerpt_visible_range,
5232 ),
5233 ))
5234 })
5235 .collect()
5236 }
5237
5238 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5239 TextLayoutDetails {
5240 text_system: window.text_system().clone(),
5241 editor_style: self.style.clone().unwrap(),
5242 rem_size: window.rem_size(),
5243 scroll_anchor: self.scroll_manager.anchor(),
5244 visible_rows: self.visible_line_count(),
5245 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5246 }
5247 }
5248
5249 pub fn splice_inlays(
5250 &self,
5251 to_remove: &[InlayId],
5252 to_insert: Vec<Inlay>,
5253 cx: &mut Context<Self>,
5254 ) {
5255 self.display_map.update(cx, |display_map, cx| {
5256 display_map.splice_inlays(to_remove, to_insert, cx)
5257 });
5258 cx.notify();
5259 }
5260
5261 fn trigger_on_type_formatting(
5262 &self,
5263 input: String,
5264 window: &mut Window,
5265 cx: &mut Context<Self>,
5266 ) -> Option<Task<Result<()>>> {
5267 if input.len() != 1 {
5268 return None;
5269 }
5270
5271 let project = self.project.as_ref()?;
5272 let position = self.selections.newest_anchor().head();
5273 let (buffer, buffer_position) = self
5274 .buffer
5275 .read(cx)
5276 .text_anchor_for_position(position, cx)?;
5277
5278 let settings = language_settings::language_settings(
5279 buffer
5280 .read(cx)
5281 .language_at(buffer_position)
5282 .map(|l| l.name()),
5283 buffer.read(cx).file(),
5284 cx,
5285 );
5286 if !settings.use_on_type_format {
5287 return None;
5288 }
5289
5290 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5291 // hence we do LSP request & edit on host side only — add formats to host's history.
5292 let push_to_lsp_host_history = true;
5293 // If this is not the host, append its history with new edits.
5294 let push_to_client_history = project.read(cx).is_via_collab();
5295
5296 let on_type_formatting = project.update(cx, |project, cx| {
5297 project.on_type_format(
5298 buffer.clone(),
5299 buffer_position,
5300 input,
5301 push_to_lsp_host_history,
5302 cx,
5303 )
5304 });
5305 Some(cx.spawn_in(window, async move |editor, cx| {
5306 if let Some(transaction) = on_type_formatting.await? {
5307 if push_to_client_history {
5308 buffer
5309 .update(cx, |buffer, _| {
5310 buffer.push_transaction(transaction, Instant::now());
5311 buffer.finalize_last_transaction();
5312 })
5313 .ok();
5314 }
5315 editor.update(cx, |editor, cx| {
5316 editor.refresh_document_highlights(cx);
5317 })?;
5318 }
5319 Ok(())
5320 }))
5321 }
5322
5323 pub fn show_word_completions(
5324 &mut self,
5325 _: &ShowWordCompletions,
5326 window: &mut Window,
5327 cx: &mut Context<Self>,
5328 ) {
5329 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5330 }
5331
5332 pub fn show_completions(
5333 &mut self,
5334 options: &ShowCompletions,
5335 window: &mut Window,
5336 cx: &mut Context<Self>,
5337 ) {
5338 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5339 }
5340
5341 fn open_or_update_completions_menu(
5342 &mut self,
5343 requested_source: Option<CompletionsMenuSource>,
5344 trigger: Option<&str>,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) {
5348 if self.pending_rename.is_some() {
5349 return;
5350 }
5351
5352 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5353
5354 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5355 // inserted and selected. To handle that case, the start of the selection is used so that
5356 // the menu starts with all choices.
5357 let position = self
5358 .selections
5359 .newest_anchor()
5360 .start
5361 .bias_right(&multibuffer_snapshot);
5362 if position.diff_base_anchor.is_some() {
5363 return;
5364 }
5365 let (buffer, buffer_position) =
5366 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5367 output
5368 } else {
5369 return;
5370 };
5371 let buffer_snapshot = buffer.read(cx).snapshot();
5372
5373 let query: Option<Arc<String>> =
5374 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5375
5376 drop(multibuffer_snapshot);
5377
5378 let provider = match requested_source {
5379 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5380 Some(CompletionsMenuSource::Words) => None,
5381 Some(CompletionsMenuSource::SnippetChoices) => {
5382 log::error!("bug: SnippetChoices requested_source is not handled");
5383 None
5384 }
5385 };
5386
5387 let sort_completions = provider
5388 .as_ref()
5389 .map_or(false, |provider| provider.sort_completions());
5390
5391 let filter_completions = provider
5392 .as_ref()
5393 .map_or(true, |provider| provider.filter_completions());
5394
5395 let trigger_kind = match trigger {
5396 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5397 CompletionTriggerKind::TRIGGER_CHARACTER
5398 }
5399 _ => CompletionTriggerKind::INVOKED,
5400 };
5401 let completion_context = CompletionContext {
5402 trigger_character: trigger.and_then(|trigger| {
5403 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5404 Some(String::from(trigger))
5405 } else {
5406 None
5407 }
5408 }),
5409 trigger_kind,
5410 };
5411
5412 // Hide the current completions menu when a trigger char is typed. Without this, cached
5413 // completions from before the trigger char may be reused (#32774). Snippet choices could
5414 // involve trigger chars, so this is skipped in that case.
5415 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5416 {
5417 let menu_is_open = matches!(
5418 self.context_menu.borrow().as_ref(),
5419 Some(CodeContextMenu::Completions(_))
5420 );
5421 if menu_is_open {
5422 self.hide_context_menu(window, cx);
5423 }
5424 }
5425
5426 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5427 if filter_completions {
5428 menu.filter(query.clone(), provider.clone(), window, cx);
5429 }
5430 // When `is_incomplete` is false, no need to re-query completions when the current query
5431 // is a suffix of the initial query.
5432 if !menu.is_incomplete {
5433 // If the new query is a suffix of the old query (typing more characters) and
5434 // the previous result was complete, the existing completions can be filtered.
5435 //
5436 // Note that this is always true for snippet completions.
5437 let query_matches = match (&menu.initial_query, &query) {
5438 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5439 (None, _) => true,
5440 _ => false,
5441 };
5442 if query_matches {
5443 let position_matches = if menu.initial_position == position {
5444 true
5445 } else {
5446 let snapshot = self.buffer.read(cx).read(cx);
5447 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5448 };
5449 if position_matches {
5450 return;
5451 }
5452 }
5453 }
5454 };
5455
5456 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5457 buffer_snapshot.surrounding_word(buffer_position, false)
5458 {
5459 let word_to_exclude = buffer_snapshot
5460 .text_for_range(word_range.clone())
5461 .collect::<String>();
5462 (
5463 buffer_snapshot.anchor_before(word_range.start)
5464 ..buffer_snapshot.anchor_after(buffer_position),
5465 Some(word_to_exclude),
5466 )
5467 } else {
5468 (buffer_position..buffer_position, None)
5469 };
5470
5471 let language = buffer_snapshot
5472 .language_at(buffer_position)
5473 .map(|language| language.name());
5474
5475 let completion_settings =
5476 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5477
5478 let show_completion_documentation = buffer_snapshot
5479 .settings_at(buffer_position, cx)
5480 .show_completion_documentation;
5481
5482 // The document can be large, so stay in reasonable bounds when searching for words,
5483 // otherwise completion pop-up might be slow to appear.
5484 const WORD_LOOKUP_ROWS: u32 = 5_000;
5485 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5486 let min_word_search = buffer_snapshot.clip_point(
5487 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5488 Bias::Left,
5489 );
5490 let max_word_search = buffer_snapshot.clip_point(
5491 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5492 Bias::Right,
5493 );
5494 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5495 ..buffer_snapshot.point_to_offset(max_word_search);
5496
5497 let skip_digits = query
5498 .as_ref()
5499 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5500
5501 let (mut words, provider_responses) = match &provider {
5502 Some(provider) => {
5503 let provider_responses = provider.completions(
5504 position.excerpt_id,
5505 &buffer,
5506 buffer_position,
5507 completion_context,
5508 window,
5509 cx,
5510 );
5511
5512 let words = match completion_settings.words {
5513 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5514 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5515 .background_spawn(async move {
5516 buffer_snapshot.words_in_range(WordsQuery {
5517 fuzzy_contents: None,
5518 range: word_search_range,
5519 skip_digits,
5520 })
5521 }),
5522 };
5523
5524 (words, provider_responses)
5525 }
5526 None => (
5527 cx.background_spawn(async move {
5528 buffer_snapshot.words_in_range(WordsQuery {
5529 fuzzy_contents: None,
5530 range: word_search_range,
5531 skip_digits,
5532 })
5533 }),
5534 Task::ready(Ok(Vec::new())),
5535 ),
5536 };
5537
5538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5539
5540 let id = post_inc(&mut self.next_completion_id);
5541 let task = cx.spawn_in(window, async move |editor, cx| {
5542 let Ok(()) = editor.update(cx, |this, _| {
5543 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5544 }) else {
5545 return;
5546 };
5547
5548 // TODO: Ideally completions from different sources would be selectively re-queried, so
5549 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5550 let mut completions = Vec::new();
5551 let mut is_incomplete = false;
5552 if let Some(provider_responses) = provider_responses.await.log_err() {
5553 if !provider_responses.is_empty() {
5554 for response in provider_responses {
5555 completions.extend(response.completions);
5556 is_incomplete = is_incomplete || response.is_incomplete;
5557 }
5558 if completion_settings.words == WordsCompletionMode::Fallback {
5559 words = Task::ready(BTreeMap::default());
5560 }
5561 }
5562 }
5563
5564 let mut words = words.await;
5565 if let Some(word_to_exclude) = &word_to_exclude {
5566 words.remove(word_to_exclude);
5567 }
5568 for lsp_completion in &completions {
5569 words.remove(&lsp_completion.new_text);
5570 }
5571 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5572 replace_range: word_replace_range.clone(),
5573 new_text: word.clone(),
5574 label: CodeLabel::plain(word, None),
5575 icon_path: None,
5576 documentation: None,
5577 source: CompletionSource::BufferWord {
5578 word_range,
5579 resolved: false,
5580 },
5581 insert_text_mode: Some(InsertTextMode::AS_IS),
5582 confirm: None,
5583 }));
5584
5585 let menu = if completions.is_empty() {
5586 None
5587 } else {
5588 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5589 let languages = editor
5590 .workspace
5591 .as_ref()
5592 .and_then(|(workspace, _)| workspace.upgrade())
5593 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5594 let menu = CompletionsMenu::new(
5595 id,
5596 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5597 sort_completions,
5598 show_completion_documentation,
5599 position,
5600 query.clone(),
5601 is_incomplete,
5602 buffer.clone(),
5603 completions.into(),
5604 snippet_sort_order,
5605 languages,
5606 language,
5607 cx,
5608 );
5609
5610 let query = if filter_completions { query } else { None };
5611 let matches_task = if let Some(query) = query {
5612 menu.do_async_filtering(query, cx)
5613 } else {
5614 Task::ready(menu.unfiltered_matches())
5615 };
5616 (menu, matches_task)
5617 }) else {
5618 return;
5619 };
5620
5621 let matches = matches_task.await;
5622
5623 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5624 // Newer menu already set, so exit.
5625 match editor.context_menu.borrow().as_ref() {
5626 Some(CodeContextMenu::Completions(prev_menu)) => {
5627 if prev_menu.id > id {
5628 return;
5629 }
5630 }
5631 _ => {}
5632 };
5633
5634 // Only valid to take prev_menu because it the new menu is immediately set
5635 // below, or the menu is hidden.
5636 match editor.context_menu.borrow_mut().take() {
5637 Some(CodeContextMenu::Completions(prev_menu)) => {
5638 let position_matches =
5639 if prev_menu.initial_position == menu.initial_position {
5640 true
5641 } else {
5642 let snapshot = editor.buffer.read(cx).read(cx);
5643 prev_menu.initial_position.to_offset(&snapshot)
5644 == menu.initial_position.to_offset(&snapshot)
5645 };
5646 if position_matches {
5647 // Preserve markdown cache before `set_filter_results` because it will
5648 // try to populate the documentation cache.
5649 menu.preserve_markdown_cache(prev_menu);
5650 }
5651 }
5652 _ => {}
5653 };
5654
5655 menu.set_filter_results(matches, provider, window, cx);
5656 }) else {
5657 return;
5658 };
5659
5660 menu.visible().then_some(menu)
5661 };
5662
5663 editor
5664 .update_in(cx, |editor, window, cx| {
5665 if editor.focus_handle.is_focused(window) {
5666 if let Some(menu) = menu {
5667 *editor.context_menu.borrow_mut() =
5668 Some(CodeContextMenu::Completions(menu));
5669
5670 crate::hover_popover::hide_hover(editor, cx);
5671 if editor.show_edit_predictions_in_menu() {
5672 editor.update_visible_inline_completion(window, cx);
5673 } else {
5674 editor.discard_inline_completion(false, cx);
5675 }
5676
5677 cx.notify();
5678 return;
5679 }
5680 }
5681
5682 if editor.completion_tasks.len() <= 1 {
5683 // If there are no more completion tasks and the last menu was empty, we should hide it.
5684 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5685 // If it was already hidden and we don't show inline completions in the menu, we should
5686 // also show the inline-completion when available.
5687 if was_hidden && editor.show_edit_predictions_in_menu() {
5688 editor.update_visible_inline_completion(window, cx);
5689 }
5690 }
5691 })
5692 .ok();
5693 });
5694
5695 self.completion_tasks.push((id, task));
5696 }
5697
5698 #[cfg(feature = "test-support")]
5699 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5700 let menu = self.context_menu.borrow();
5701 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5702 let completions = menu.completions.borrow();
5703 Some(completions.to_vec())
5704 } else {
5705 None
5706 }
5707 }
5708
5709 pub fn with_completions_menu_matching_id<R>(
5710 &self,
5711 id: CompletionId,
5712 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5713 ) -> R {
5714 let mut context_menu = self.context_menu.borrow_mut();
5715 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5716 return f(None);
5717 };
5718 if completions_menu.id != id {
5719 return f(None);
5720 }
5721 f(Some(completions_menu))
5722 }
5723
5724 pub fn confirm_completion(
5725 &mut self,
5726 action: &ConfirmCompletion,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) -> Option<Task<Result<()>>> {
5730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5731 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5732 }
5733
5734 pub fn confirm_completion_insert(
5735 &mut self,
5736 _: &ConfirmCompletionInsert,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) -> Option<Task<Result<()>>> {
5740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5741 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5742 }
5743
5744 pub fn confirm_completion_replace(
5745 &mut self,
5746 _: &ConfirmCompletionReplace,
5747 window: &mut Window,
5748 cx: &mut Context<Self>,
5749 ) -> Option<Task<Result<()>>> {
5750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5751 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5752 }
5753
5754 pub fn compose_completion(
5755 &mut self,
5756 action: &ComposeCompletion,
5757 window: &mut Window,
5758 cx: &mut Context<Self>,
5759 ) -> Option<Task<Result<()>>> {
5760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5761 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5762 }
5763
5764 fn do_completion(
5765 &mut self,
5766 item_ix: Option<usize>,
5767 intent: CompletionIntent,
5768 window: &mut Window,
5769 cx: &mut Context<Editor>,
5770 ) -> Option<Task<Result<()>>> {
5771 use language::ToOffset as _;
5772
5773 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5774 else {
5775 return None;
5776 };
5777
5778 let candidate_id = {
5779 let entries = completions_menu.entries.borrow();
5780 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5781 if self.show_edit_predictions_in_menu() {
5782 self.discard_inline_completion(true, cx);
5783 }
5784 mat.candidate_id
5785 };
5786
5787 let completion = completions_menu
5788 .completions
5789 .borrow()
5790 .get(candidate_id)?
5791 .clone();
5792 cx.stop_propagation();
5793
5794 let buffer_handle = completions_menu.buffer.clone();
5795
5796 let CompletionEdit {
5797 new_text,
5798 snippet,
5799 replace_range,
5800 } = process_completion_for_edit(
5801 &completion,
5802 intent,
5803 &buffer_handle,
5804 &completions_menu.initial_position.text_anchor,
5805 cx,
5806 );
5807
5808 let buffer = buffer_handle.read(cx);
5809 let snapshot = self.buffer.read(cx).snapshot(cx);
5810 let newest_anchor = self.selections.newest_anchor();
5811 let replace_range_multibuffer = {
5812 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5813 let multibuffer_anchor = snapshot
5814 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5815 .unwrap()
5816 ..snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5818 .unwrap();
5819 multibuffer_anchor.start.to_offset(&snapshot)
5820 ..multibuffer_anchor.end.to_offset(&snapshot)
5821 };
5822 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5823 return None;
5824 }
5825
5826 let old_text = buffer
5827 .text_for_range(replace_range.clone())
5828 .collect::<String>();
5829 let lookbehind = newest_anchor
5830 .start
5831 .text_anchor
5832 .to_offset(buffer)
5833 .saturating_sub(replace_range.start);
5834 let lookahead = replace_range
5835 .end
5836 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5837 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5838 let suffix = &old_text[lookbehind.min(old_text.len())..];
5839
5840 let selections = self.selections.all::<usize>(cx);
5841 let mut ranges = Vec::new();
5842 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5843
5844 for selection in &selections {
5845 let range = if selection.id == newest_anchor.id {
5846 replace_range_multibuffer.clone()
5847 } else {
5848 let mut range = selection.range();
5849
5850 // if prefix is present, don't duplicate it
5851 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5852 range.start = range.start.saturating_sub(lookbehind);
5853
5854 // if suffix is also present, mimic the newest cursor and replace it
5855 if selection.id != newest_anchor.id
5856 && snapshot.contains_str_at(range.end, suffix)
5857 {
5858 range.end += lookahead;
5859 }
5860 }
5861 range
5862 };
5863
5864 ranges.push(range.clone());
5865
5866 if !self.linked_edit_ranges.is_empty() {
5867 let start_anchor = snapshot.anchor_before(range.start);
5868 let end_anchor = snapshot.anchor_after(range.end);
5869 if let Some(ranges) = self
5870 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5871 {
5872 for (buffer, edits) in ranges {
5873 linked_edits
5874 .entry(buffer.clone())
5875 .or_default()
5876 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5877 }
5878 }
5879 }
5880 }
5881
5882 let common_prefix_len = old_text
5883 .chars()
5884 .zip(new_text.chars())
5885 .take_while(|(a, b)| a == b)
5886 .map(|(a, _)| a.len_utf8())
5887 .sum::<usize>();
5888
5889 cx.emit(EditorEvent::InputHandled {
5890 utf16_range_to_replace: None,
5891 text: new_text[common_prefix_len..].into(),
5892 });
5893
5894 self.transact(window, cx, |this, window, cx| {
5895 if let Some(mut snippet) = snippet {
5896 snippet.text = new_text.to_string();
5897 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5898 } else {
5899 this.buffer.update(cx, |buffer, cx| {
5900 let auto_indent = match completion.insert_text_mode {
5901 Some(InsertTextMode::AS_IS) => None,
5902 _ => this.autoindent_mode.clone(),
5903 };
5904 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5905 buffer.edit(edits, auto_indent, cx);
5906 });
5907 }
5908 for (buffer, edits) in linked_edits {
5909 buffer.update(cx, |buffer, cx| {
5910 let snapshot = buffer.snapshot();
5911 let edits = edits
5912 .into_iter()
5913 .map(|(range, text)| {
5914 use text::ToPoint as TP;
5915 let end_point = TP::to_point(&range.end, &snapshot);
5916 let start_point = TP::to_point(&range.start, &snapshot);
5917 (start_point..end_point, text)
5918 })
5919 .sorted_by_key(|(range, _)| range.start);
5920 buffer.edit(edits, None, cx);
5921 })
5922 }
5923
5924 this.refresh_inline_completion(true, false, window, cx);
5925 });
5926
5927 let show_new_completions_on_confirm = completion
5928 .confirm
5929 .as_ref()
5930 .map_or(false, |confirm| confirm(intent, window, cx));
5931 if show_new_completions_on_confirm {
5932 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5933 }
5934
5935 let provider = self.completion_provider.as_ref()?;
5936 drop(completion);
5937 let apply_edits = provider.apply_additional_edits_for_completion(
5938 buffer_handle,
5939 completions_menu.completions.clone(),
5940 candidate_id,
5941 true,
5942 cx,
5943 );
5944
5945 let editor_settings = EditorSettings::get_global(cx);
5946 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5947 // After the code completion is finished, users often want to know what signatures are needed.
5948 // so we should automatically call signature_help
5949 self.show_signature_help(&ShowSignatureHelp, window, cx);
5950 }
5951
5952 Some(cx.foreground_executor().spawn(async move {
5953 apply_edits.await?;
5954 Ok(())
5955 }))
5956 }
5957
5958 pub fn toggle_code_actions(
5959 &mut self,
5960 action: &ToggleCodeActions,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) {
5964 let quick_launch = action.quick_launch;
5965 let mut context_menu = self.context_menu.borrow_mut();
5966 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5967 if code_actions.deployed_from == action.deployed_from {
5968 // Toggle if we're selecting the same one
5969 *context_menu = None;
5970 cx.notify();
5971 return;
5972 } else {
5973 // Otherwise, clear it and start a new one
5974 *context_menu = None;
5975 cx.notify();
5976 }
5977 }
5978 drop(context_menu);
5979 let snapshot = self.snapshot(window, cx);
5980 let deployed_from = action.deployed_from.clone();
5981 let action = action.clone();
5982 self.completion_tasks.clear();
5983 self.discard_inline_completion(false, cx);
5984
5985 let multibuffer_point = match &action.deployed_from {
5986 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5987 DisplayPoint::new(*row, 0).to_point(&snapshot)
5988 }
5989 _ => self.selections.newest::<Point>(cx).head(),
5990 };
5991 let Some((buffer, buffer_row)) = snapshot
5992 .buffer_snapshot
5993 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5994 .and_then(|(buffer_snapshot, range)| {
5995 self.buffer()
5996 .read(cx)
5997 .buffer(buffer_snapshot.remote_id())
5998 .map(|buffer| (buffer, range.start.row))
5999 })
6000 else {
6001 return;
6002 };
6003 let buffer_id = buffer.read(cx).remote_id();
6004 let tasks = self
6005 .tasks
6006 .get(&(buffer_id, buffer_row))
6007 .map(|t| Arc::new(t.to_owned()));
6008
6009 if !self.focus_handle.is_focused(window) {
6010 return;
6011 }
6012 let project = self.project.clone();
6013
6014 let code_actions_task = match deployed_from {
6015 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6016 _ => self.code_actions(buffer_row, window, cx),
6017 };
6018
6019 let runnable_task = match deployed_from {
6020 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6021 _ => {
6022 let mut task_context_task = Task::ready(None);
6023 if let Some(tasks) = &tasks {
6024 if let Some(project) = project {
6025 task_context_task =
6026 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6027 }
6028 }
6029
6030 cx.spawn_in(window, {
6031 let buffer = buffer.clone();
6032 async move |editor, cx| {
6033 let task_context = task_context_task.await;
6034
6035 let resolved_tasks =
6036 tasks
6037 .zip(task_context.clone())
6038 .map(|(tasks, task_context)| ResolvedTasks {
6039 templates: tasks.resolve(&task_context).collect(),
6040 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6041 multibuffer_point.row,
6042 tasks.column,
6043 )),
6044 });
6045 let debug_scenarios = editor
6046 .update(cx, |editor, cx| {
6047 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6048 })?
6049 .await;
6050 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6051 }
6052 })
6053 }
6054 };
6055
6056 cx.spawn_in(window, async move |editor, cx| {
6057 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6058 let code_actions = code_actions_task.await;
6059 let spawn_straight_away = quick_launch
6060 && resolved_tasks
6061 .as_ref()
6062 .map_or(false, |tasks| tasks.templates.len() == 1)
6063 && code_actions
6064 .as_ref()
6065 .map_or(true, |actions| actions.is_empty())
6066 && debug_scenarios.is_empty();
6067
6068 editor.update_in(cx, |editor, window, cx| {
6069 crate::hover_popover::hide_hover(editor, cx);
6070 let actions = CodeActionContents::new(
6071 resolved_tasks,
6072 code_actions,
6073 debug_scenarios,
6074 task_context.unwrap_or_default(),
6075 );
6076
6077 // Don't show the menu if there are no actions available
6078 if actions.is_empty() {
6079 cx.notify();
6080 return Task::ready(Ok(()));
6081 }
6082
6083 *editor.context_menu.borrow_mut() =
6084 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6085 buffer,
6086 actions,
6087 selected_item: Default::default(),
6088 scroll_handle: UniformListScrollHandle::default(),
6089 deployed_from,
6090 }));
6091 cx.notify();
6092 if spawn_straight_away {
6093 if let Some(task) = editor.confirm_code_action(
6094 &ConfirmCodeAction { item_ix: Some(0) },
6095 window,
6096 cx,
6097 ) {
6098 return task;
6099 }
6100 }
6101
6102 Task::ready(Ok(()))
6103 })
6104 })
6105 .detach_and_log_err(cx);
6106 }
6107
6108 fn debug_scenarios(
6109 &mut self,
6110 resolved_tasks: &Option<ResolvedTasks>,
6111 buffer: &Entity<Buffer>,
6112 cx: &mut App,
6113 ) -> Task<Vec<task::DebugScenario>> {
6114 maybe!({
6115 let project = self.project.as_ref()?;
6116 let dap_store = project.read(cx).dap_store();
6117 let mut scenarios = vec![];
6118 let resolved_tasks = resolved_tasks.as_ref()?;
6119 let buffer = buffer.read(cx);
6120 let language = buffer.language()?;
6121 let file = buffer.file();
6122 let debug_adapter = language_settings(language.name().into(), file, cx)
6123 .debuggers
6124 .first()
6125 .map(SharedString::from)
6126 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6127
6128 dap_store.update(cx, |dap_store, cx| {
6129 for (_, task) in &resolved_tasks.templates {
6130 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6131 task.original_task().clone(),
6132 debug_adapter.clone().into(),
6133 task.display_label().to_owned().into(),
6134 cx,
6135 );
6136 scenarios.push(maybe_scenario);
6137 }
6138 });
6139 Some(cx.background_spawn(async move {
6140 let scenarios = futures::future::join_all(scenarios)
6141 .await
6142 .into_iter()
6143 .flatten()
6144 .collect::<Vec<_>>();
6145 scenarios
6146 }))
6147 })
6148 .unwrap_or_else(|| Task::ready(vec![]))
6149 }
6150
6151 fn code_actions(
6152 &mut self,
6153 buffer_row: u32,
6154 window: &mut Window,
6155 cx: &mut Context<Self>,
6156 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6157 let mut task = self.code_actions_task.take();
6158 cx.spawn_in(window, async move |editor, cx| {
6159 while let Some(prev_task) = task {
6160 prev_task.await.log_err();
6161 task = editor
6162 .update(cx, |this, _| this.code_actions_task.take())
6163 .ok()?;
6164 }
6165
6166 editor
6167 .update(cx, |editor, cx| {
6168 editor
6169 .available_code_actions
6170 .clone()
6171 .and_then(|(location, code_actions)| {
6172 let snapshot = location.buffer.read(cx).snapshot();
6173 let point_range = location.range.to_point(&snapshot);
6174 let point_range = point_range.start.row..=point_range.end.row;
6175 if point_range.contains(&buffer_row) {
6176 Some(code_actions)
6177 } else {
6178 None
6179 }
6180 })
6181 })
6182 .ok()
6183 .flatten()
6184 })
6185 }
6186
6187 pub fn confirm_code_action(
6188 &mut self,
6189 action: &ConfirmCodeAction,
6190 window: &mut Window,
6191 cx: &mut Context<Self>,
6192 ) -> Option<Task<Result<()>>> {
6193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6194
6195 let actions_menu =
6196 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6197 menu
6198 } else {
6199 return None;
6200 };
6201
6202 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6203 let action = actions_menu.actions.get(action_ix)?;
6204 let title = action.label();
6205 let buffer = actions_menu.buffer;
6206 let workspace = self.workspace()?;
6207
6208 match action {
6209 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6210 workspace.update(cx, |workspace, cx| {
6211 workspace.schedule_resolved_task(
6212 task_source_kind,
6213 resolved_task,
6214 false,
6215 window,
6216 cx,
6217 );
6218
6219 Some(Task::ready(Ok(())))
6220 })
6221 }
6222 CodeActionsItem::CodeAction {
6223 excerpt_id,
6224 action,
6225 provider,
6226 } => {
6227 let apply_code_action =
6228 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6229 let workspace = workspace.downgrade();
6230 Some(cx.spawn_in(window, async move |editor, cx| {
6231 let project_transaction = apply_code_action.await?;
6232 Self::open_project_transaction(
6233 &editor,
6234 workspace,
6235 project_transaction,
6236 title,
6237 cx,
6238 )
6239 .await
6240 }))
6241 }
6242 CodeActionsItem::DebugScenario(scenario) => {
6243 let context = actions_menu.actions.context.clone();
6244
6245 workspace.update(cx, |workspace, cx| {
6246 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6247 workspace.start_debug_session(
6248 scenario,
6249 context,
6250 Some(buffer),
6251 None,
6252 window,
6253 cx,
6254 );
6255 });
6256 Some(Task::ready(Ok(())))
6257 }
6258 }
6259 }
6260
6261 pub async fn open_project_transaction(
6262 this: &WeakEntity<Editor>,
6263 workspace: WeakEntity<Workspace>,
6264 transaction: ProjectTransaction,
6265 title: String,
6266 cx: &mut AsyncWindowContext,
6267 ) -> Result<()> {
6268 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6269 cx.update(|_, cx| {
6270 entries.sort_unstable_by_key(|(buffer, _)| {
6271 buffer.read(cx).file().map(|f| f.path().clone())
6272 });
6273 })?;
6274
6275 // If the project transaction's edits are all contained within this editor, then
6276 // avoid opening a new editor to display them.
6277
6278 if let Some((buffer, transaction)) = entries.first() {
6279 if entries.len() == 1 {
6280 let excerpt = this.update(cx, |editor, cx| {
6281 editor
6282 .buffer()
6283 .read(cx)
6284 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6285 })?;
6286 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6287 if excerpted_buffer == *buffer {
6288 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6289 let excerpt_range = excerpt_range.to_offset(buffer);
6290 buffer
6291 .edited_ranges_for_transaction::<usize>(transaction)
6292 .all(|range| {
6293 excerpt_range.start <= range.start
6294 && excerpt_range.end >= range.end
6295 })
6296 })?;
6297
6298 if all_edits_within_excerpt {
6299 return Ok(());
6300 }
6301 }
6302 }
6303 }
6304 } else {
6305 return Ok(());
6306 }
6307
6308 let mut ranges_to_highlight = Vec::new();
6309 let excerpt_buffer = cx.new(|cx| {
6310 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6311 for (buffer_handle, transaction) in &entries {
6312 let edited_ranges = buffer_handle
6313 .read(cx)
6314 .edited_ranges_for_transaction::<Point>(transaction)
6315 .collect::<Vec<_>>();
6316 let (ranges, _) = multibuffer.set_excerpts_for_path(
6317 PathKey::for_buffer(buffer_handle, cx),
6318 buffer_handle.clone(),
6319 edited_ranges,
6320 DEFAULT_MULTIBUFFER_CONTEXT,
6321 cx,
6322 );
6323
6324 ranges_to_highlight.extend(ranges);
6325 }
6326 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6327 multibuffer
6328 })?;
6329
6330 workspace.update_in(cx, |workspace, window, cx| {
6331 let project = workspace.project().clone();
6332 let editor =
6333 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6334 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6335 editor.update(cx, |editor, cx| {
6336 editor.highlight_background::<Self>(
6337 &ranges_to_highlight,
6338 |theme| theme.colors().editor_highlighted_line_background,
6339 cx,
6340 );
6341 });
6342 })?;
6343
6344 Ok(())
6345 }
6346
6347 pub fn clear_code_action_providers(&mut self) {
6348 self.code_action_providers.clear();
6349 self.available_code_actions.take();
6350 }
6351
6352 pub fn add_code_action_provider(
6353 &mut self,
6354 provider: Rc<dyn CodeActionProvider>,
6355 window: &mut Window,
6356 cx: &mut Context<Self>,
6357 ) {
6358 if self
6359 .code_action_providers
6360 .iter()
6361 .any(|existing_provider| existing_provider.id() == provider.id())
6362 {
6363 return;
6364 }
6365
6366 self.code_action_providers.push(provider);
6367 self.refresh_code_actions(window, cx);
6368 }
6369
6370 pub fn remove_code_action_provider(
6371 &mut self,
6372 id: Arc<str>,
6373 window: &mut Window,
6374 cx: &mut Context<Self>,
6375 ) {
6376 self.code_action_providers
6377 .retain(|provider| provider.id() != id);
6378 self.refresh_code_actions(window, cx);
6379 }
6380
6381 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6382 !self.code_action_providers.is_empty()
6383 && EditorSettings::get_global(cx).toolbar.code_actions
6384 }
6385
6386 pub fn has_available_code_actions(&self) -> bool {
6387 self.available_code_actions
6388 .as_ref()
6389 .is_some_and(|(_, actions)| !actions.is_empty())
6390 }
6391
6392 fn render_inline_code_actions(
6393 &self,
6394 icon_size: ui::IconSize,
6395 display_row: DisplayRow,
6396 is_active: bool,
6397 cx: &mut Context<Self>,
6398 ) -> AnyElement {
6399 let show_tooltip = !self.context_menu_visible();
6400 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6401 .icon_size(icon_size)
6402 .shape(ui::IconButtonShape::Square)
6403 .style(ButtonStyle::Transparent)
6404 .icon_color(ui::Color::Hidden)
6405 .toggle_state(is_active)
6406 .when(show_tooltip, |this| {
6407 this.tooltip({
6408 let focus_handle = self.focus_handle.clone();
6409 move |window, cx| {
6410 Tooltip::for_action_in(
6411 "Toggle Code Actions",
6412 &ToggleCodeActions {
6413 deployed_from: None,
6414 quick_launch: false,
6415 },
6416 &focus_handle,
6417 window,
6418 cx,
6419 )
6420 }
6421 })
6422 })
6423 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6424 window.focus(&editor.focus_handle(cx));
6425 editor.toggle_code_actions(
6426 &crate::actions::ToggleCodeActions {
6427 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6428 display_row,
6429 )),
6430 quick_launch: false,
6431 },
6432 window,
6433 cx,
6434 );
6435 }))
6436 .into_any_element()
6437 }
6438
6439 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6440 &self.context_menu
6441 }
6442
6443 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6444 let newest_selection = self.selections.newest_anchor().clone();
6445 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6446 let buffer = self.buffer.read(cx);
6447 if newest_selection.head().diff_base_anchor.is_some() {
6448 return None;
6449 }
6450 let (start_buffer, start) =
6451 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6452 let (end_buffer, end) =
6453 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6454 if start_buffer != end_buffer {
6455 return None;
6456 }
6457
6458 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6459 cx.background_executor()
6460 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6461 .await;
6462
6463 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6464 let providers = this.code_action_providers.clone();
6465 let tasks = this
6466 .code_action_providers
6467 .iter()
6468 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6469 .collect::<Vec<_>>();
6470 (providers, tasks)
6471 })?;
6472
6473 let mut actions = Vec::new();
6474 for (provider, provider_actions) in
6475 providers.into_iter().zip(future::join_all(tasks).await)
6476 {
6477 if let Some(provider_actions) = provider_actions.log_err() {
6478 actions.extend(provider_actions.into_iter().map(|action| {
6479 AvailableCodeAction {
6480 excerpt_id: newest_selection.start.excerpt_id,
6481 action,
6482 provider: provider.clone(),
6483 }
6484 }));
6485 }
6486 }
6487
6488 this.update(cx, |this, cx| {
6489 this.available_code_actions = if actions.is_empty() {
6490 None
6491 } else {
6492 Some((
6493 Location {
6494 buffer: start_buffer,
6495 range: start..end,
6496 },
6497 actions.into(),
6498 ))
6499 };
6500 cx.notify();
6501 })
6502 }));
6503 None
6504 }
6505
6506 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6507 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6508 self.show_git_blame_inline = false;
6509
6510 self.show_git_blame_inline_delay_task =
6511 Some(cx.spawn_in(window, async move |this, cx| {
6512 cx.background_executor().timer(delay).await;
6513
6514 this.update(cx, |this, cx| {
6515 this.show_git_blame_inline = true;
6516 cx.notify();
6517 })
6518 .log_err();
6519 }));
6520 }
6521 }
6522
6523 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6524 let snapshot = self.snapshot(window, cx);
6525 let cursor = self.selections.newest::<Point>(cx).head();
6526 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6527 else {
6528 return;
6529 };
6530
6531 let Some(blame) = self.blame.as_ref() else {
6532 return;
6533 };
6534
6535 let row_info = RowInfo {
6536 buffer_id: Some(buffer.remote_id()),
6537 buffer_row: Some(point.row),
6538 ..Default::default()
6539 };
6540 let Some(blame_entry) = blame
6541 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6542 .flatten()
6543 else {
6544 return;
6545 };
6546
6547 let anchor = self.selections.newest_anchor().head();
6548 let position = self.to_pixel_point(anchor, &snapshot, window);
6549 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6550 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6551 };
6552 }
6553
6554 fn show_blame_popover(
6555 &mut self,
6556 blame_entry: &BlameEntry,
6557 position: gpui::Point<Pixels>,
6558 ignore_timeout: bool,
6559 cx: &mut Context<Self>,
6560 ) {
6561 if let Some(state) = &mut self.inline_blame_popover {
6562 state.hide_task.take();
6563 } else {
6564 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6565 let blame_entry = blame_entry.clone();
6566 let show_task = cx.spawn(async move |editor, cx| {
6567 if !ignore_timeout {
6568 cx.background_executor()
6569 .timer(std::time::Duration::from_millis(blame_popover_delay))
6570 .await;
6571 }
6572 editor
6573 .update(cx, |editor, cx| {
6574 editor.inline_blame_popover_show_task.take();
6575 let Some(blame) = editor.blame.as_ref() else {
6576 return;
6577 };
6578 let blame = blame.read(cx);
6579 let details = blame.details_for_entry(&blame_entry);
6580 let markdown = cx.new(|cx| {
6581 Markdown::new(
6582 details
6583 .as_ref()
6584 .map(|message| message.message.clone())
6585 .unwrap_or_default(),
6586 None,
6587 None,
6588 cx,
6589 )
6590 });
6591 editor.inline_blame_popover = Some(InlineBlamePopover {
6592 position,
6593 hide_task: None,
6594 popover_bounds: None,
6595 popover_state: InlineBlamePopoverState {
6596 scroll_handle: ScrollHandle::new(),
6597 commit_message: details,
6598 markdown,
6599 },
6600 keyboard_grace: ignore_timeout,
6601 });
6602 cx.notify();
6603 })
6604 .ok();
6605 });
6606 self.inline_blame_popover_show_task = Some(show_task);
6607 }
6608 }
6609
6610 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6611 self.inline_blame_popover_show_task.take();
6612 if let Some(state) = &mut self.inline_blame_popover {
6613 let hide_task = cx.spawn(async move |editor, cx| {
6614 cx.background_executor()
6615 .timer(std::time::Duration::from_millis(100))
6616 .await;
6617 editor
6618 .update(cx, |editor, cx| {
6619 editor.inline_blame_popover.take();
6620 cx.notify();
6621 })
6622 .ok();
6623 });
6624 state.hide_task = Some(hide_task);
6625 }
6626 }
6627
6628 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6629 if self.pending_rename.is_some() {
6630 return None;
6631 }
6632
6633 let provider = self.semantics_provider.clone()?;
6634 let buffer = self.buffer.read(cx);
6635 let newest_selection = self.selections.newest_anchor().clone();
6636 let cursor_position = newest_selection.head();
6637 let (cursor_buffer, cursor_buffer_position) =
6638 buffer.text_anchor_for_position(cursor_position, cx)?;
6639 let (tail_buffer, tail_buffer_position) =
6640 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6641 if cursor_buffer != tail_buffer {
6642 return None;
6643 }
6644
6645 let snapshot = cursor_buffer.read(cx).snapshot();
6646 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6647 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6648 if start_word_range != end_word_range {
6649 self.document_highlights_task.take();
6650 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6651 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6652 return None;
6653 }
6654
6655 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6656 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6657 cx.background_executor()
6658 .timer(Duration::from_millis(debounce))
6659 .await;
6660
6661 let highlights = if let Some(highlights) = cx
6662 .update(|cx| {
6663 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6664 })
6665 .ok()
6666 .flatten()
6667 {
6668 highlights.await.log_err()
6669 } else {
6670 None
6671 };
6672
6673 if let Some(highlights) = highlights {
6674 this.update(cx, |this, cx| {
6675 if this.pending_rename.is_some() {
6676 return;
6677 }
6678
6679 let buffer_id = cursor_position.buffer_id;
6680 let buffer = this.buffer.read(cx);
6681 if !buffer
6682 .text_anchor_for_position(cursor_position, cx)
6683 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6684 {
6685 return;
6686 }
6687
6688 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6689 let mut write_ranges = Vec::new();
6690 let mut read_ranges = Vec::new();
6691 for highlight in highlights {
6692 for (excerpt_id, excerpt_range) in
6693 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6694 {
6695 let start = highlight
6696 .range
6697 .start
6698 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6699 let end = highlight
6700 .range
6701 .end
6702 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6703 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6704 continue;
6705 }
6706
6707 let range = Anchor {
6708 buffer_id,
6709 excerpt_id,
6710 text_anchor: start,
6711 diff_base_anchor: None,
6712 }..Anchor {
6713 buffer_id,
6714 excerpt_id,
6715 text_anchor: end,
6716 diff_base_anchor: None,
6717 };
6718 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6719 write_ranges.push(range);
6720 } else {
6721 read_ranges.push(range);
6722 }
6723 }
6724 }
6725
6726 this.highlight_background::<DocumentHighlightRead>(
6727 &read_ranges,
6728 |theme| theme.colors().editor_document_highlight_read_background,
6729 cx,
6730 );
6731 this.highlight_background::<DocumentHighlightWrite>(
6732 &write_ranges,
6733 |theme| theme.colors().editor_document_highlight_write_background,
6734 cx,
6735 );
6736 cx.notify();
6737 })
6738 .log_err();
6739 }
6740 }));
6741 None
6742 }
6743
6744 fn prepare_highlight_query_from_selection(
6745 &mut self,
6746 cx: &mut Context<Editor>,
6747 ) -> Option<(String, Range<Anchor>)> {
6748 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6749 return None;
6750 }
6751 if !EditorSettings::get_global(cx).selection_highlight {
6752 return None;
6753 }
6754 if self.selections.count() != 1 || self.selections.line_mode {
6755 return None;
6756 }
6757 let selection = self.selections.newest::<Point>(cx);
6758 if selection.is_empty() || selection.start.row != selection.end.row {
6759 return None;
6760 }
6761 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6762 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6763 let query = multi_buffer_snapshot
6764 .text_for_range(selection_anchor_range.clone())
6765 .collect::<String>();
6766 if query.trim().is_empty() {
6767 return None;
6768 }
6769 Some((query, selection_anchor_range))
6770 }
6771
6772 fn update_selection_occurrence_highlights(
6773 &mut self,
6774 query_text: String,
6775 query_range: Range<Anchor>,
6776 multi_buffer_range_to_query: Range<Point>,
6777 use_debounce: bool,
6778 window: &mut Window,
6779 cx: &mut Context<Editor>,
6780 ) -> Task<()> {
6781 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6782 cx.spawn_in(window, async move |editor, cx| {
6783 if use_debounce {
6784 cx.background_executor()
6785 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6786 .await;
6787 }
6788 let match_task = cx.background_spawn(async move {
6789 let buffer_ranges = multi_buffer_snapshot
6790 .range_to_buffer_ranges(multi_buffer_range_to_query)
6791 .into_iter()
6792 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6793 let mut match_ranges = Vec::new();
6794 let Ok(regex) = project::search::SearchQuery::text(
6795 query_text.clone(),
6796 false,
6797 false,
6798 false,
6799 Default::default(),
6800 Default::default(),
6801 false,
6802 None,
6803 ) else {
6804 return Vec::default();
6805 };
6806 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6807 match_ranges.extend(
6808 regex
6809 .search(&buffer_snapshot, Some(search_range.clone()))
6810 .await
6811 .into_iter()
6812 .filter_map(|match_range| {
6813 let match_start = buffer_snapshot
6814 .anchor_after(search_range.start + match_range.start);
6815 let match_end = buffer_snapshot
6816 .anchor_before(search_range.start + match_range.end);
6817 let match_anchor_range = Anchor::range_in_buffer(
6818 excerpt_id,
6819 buffer_snapshot.remote_id(),
6820 match_start..match_end,
6821 );
6822 (match_anchor_range != query_range).then_some(match_anchor_range)
6823 }),
6824 );
6825 }
6826 match_ranges
6827 });
6828 let match_ranges = match_task.await;
6829 editor
6830 .update_in(cx, |editor, _, cx| {
6831 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6832 if !match_ranges.is_empty() {
6833 editor.highlight_background::<SelectedTextHighlight>(
6834 &match_ranges,
6835 |theme| theme.colors().editor_document_highlight_bracket_background,
6836 cx,
6837 )
6838 }
6839 })
6840 .log_err();
6841 })
6842 }
6843
6844 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6845 struct NewlineFold;
6846 let type_id = std::any::TypeId::of::<NewlineFold>();
6847 if !self.mode.is_single_line() {
6848 return;
6849 }
6850 let snapshot = self.snapshot(window, cx);
6851 if snapshot.buffer_snapshot.max_point().row == 0 {
6852 return;
6853 }
6854 let task = cx.background_spawn(async move {
6855 let new_newlines = snapshot
6856 .buffer_chars_at(0)
6857 .filter_map(|(c, i)| {
6858 if c == '\n' {
6859 Some(
6860 snapshot.buffer_snapshot.anchor_after(i)
6861 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6862 )
6863 } else {
6864 None
6865 }
6866 })
6867 .collect::<Vec<_>>();
6868 let existing_newlines = snapshot
6869 .folds_in_range(0..snapshot.buffer_snapshot.len())
6870 .filter_map(|fold| {
6871 if fold.placeholder.type_tag == Some(type_id) {
6872 Some(fold.range.start..fold.range.end)
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878
6879 (new_newlines, existing_newlines)
6880 });
6881 self.folding_newlines = cx.spawn(async move |this, cx| {
6882 let (new_newlines, existing_newlines) = task.await;
6883 if new_newlines == existing_newlines {
6884 return;
6885 }
6886 let placeholder = FoldPlaceholder {
6887 render: Arc::new(move |_, _, cx| {
6888 div()
6889 .bg(cx.theme().status().hint_background)
6890 .border_b_1()
6891 .size_full()
6892 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6893 .border_color(cx.theme().status().hint)
6894 .child("\\n")
6895 .into_any()
6896 }),
6897 constrain_width: false,
6898 merge_adjacent: false,
6899 type_tag: Some(type_id),
6900 };
6901 let creases = new_newlines
6902 .into_iter()
6903 .map(|range| Crease::simple(range, placeholder.clone()))
6904 .collect();
6905 this.update(cx, |this, cx| {
6906 this.display_map.update(cx, |display_map, cx| {
6907 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6908 display_map.fold(creases, cx);
6909 });
6910 })
6911 .ok();
6912 });
6913 }
6914
6915 fn refresh_selected_text_highlights(
6916 &mut self,
6917 on_buffer_edit: bool,
6918 window: &mut Window,
6919 cx: &mut Context<Editor>,
6920 ) {
6921 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6922 else {
6923 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6924 self.quick_selection_highlight_task.take();
6925 self.debounced_selection_highlight_task.take();
6926 return;
6927 };
6928 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6929 if on_buffer_edit
6930 || self
6931 .quick_selection_highlight_task
6932 .as_ref()
6933 .map_or(true, |(prev_anchor_range, _)| {
6934 prev_anchor_range != &query_range
6935 })
6936 {
6937 let multi_buffer_visible_start = self
6938 .scroll_manager
6939 .anchor()
6940 .anchor
6941 .to_point(&multi_buffer_snapshot);
6942 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6943 multi_buffer_visible_start
6944 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6945 Bias::Left,
6946 );
6947 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6948 self.quick_selection_highlight_task = Some((
6949 query_range.clone(),
6950 self.update_selection_occurrence_highlights(
6951 query_text.clone(),
6952 query_range.clone(),
6953 multi_buffer_visible_range,
6954 false,
6955 window,
6956 cx,
6957 ),
6958 ));
6959 }
6960 if on_buffer_edit
6961 || self
6962 .debounced_selection_highlight_task
6963 .as_ref()
6964 .map_or(true, |(prev_anchor_range, _)| {
6965 prev_anchor_range != &query_range
6966 })
6967 {
6968 let multi_buffer_start = multi_buffer_snapshot
6969 .anchor_before(0)
6970 .to_point(&multi_buffer_snapshot);
6971 let multi_buffer_end = multi_buffer_snapshot
6972 .anchor_after(multi_buffer_snapshot.len())
6973 .to_point(&multi_buffer_snapshot);
6974 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6975 self.debounced_selection_highlight_task = Some((
6976 query_range.clone(),
6977 self.update_selection_occurrence_highlights(
6978 query_text,
6979 query_range,
6980 multi_buffer_full_range,
6981 true,
6982 window,
6983 cx,
6984 ),
6985 ));
6986 }
6987 }
6988
6989 pub fn refresh_inline_completion(
6990 &mut self,
6991 debounce: bool,
6992 user_requested: bool,
6993 window: &mut Window,
6994 cx: &mut Context<Self>,
6995 ) -> Option<()> {
6996 let provider = self.edit_prediction_provider()?;
6997 let cursor = self.selections.newest_anchor().head();
6998 let (buffer, cursor_buffer_position) =
6999 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7000
7001 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7002 self.discard_inline_completion(false, cx);
7003 return None;
7004 }
7005
7006 if !user_requested
7007 && (!self.should_show_edit_predictions()
7008 || !self.is_focused(window)
7009 || buffer.read(cx).is_empty())
7010 {
7011 self.discard_inline_completion(false, cx);
7012 return None;
7013 }
7014
7015 self.update_visible_inline_completion(window, cx);
7016 provider.refresh(
7017 self.project.clone(),
7018 buffer,
7019 cursor_buffer_position,
7020 debounce,
7021 cx,
7022 );
7023 Some(())
7024 }
7025
7026 fn show_edit_predictions_in_menu(&self) -> bool {
7027 match self.edit_prediction_settings {
7028 EditPredictionSettings::Disabled => false,
7029 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7030 }
7031 }
7032
7033 pub fn edit_predictions_enabled(&self) -> bool {
7034 match self.edit_prediction_settings {
7035 EditPredictionSettings::Disabled => false,
7036 EditPredictionSettings::Enabled { .. } => true,
7037 }
7038 }
7039
7040 fn edit_prediction_requires_modifier(&self) -> bool {
7041 match self.edit_prediction_settings {
7042 EditPredictionSettings::Disabled => false,
7043 EditPredictionSettings::Enabled {
7044 preview_requires_modifier,
7045 ..
7046 } => preview_requires_modifier,
7047 }
7048 }
7049
7050 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7051 if self.edit_prediction_provider.is_none() {
7052 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7053 } else {
7054 let selection = self.selections.newest_anchor();
7055 let cursor = selection.head();
7056
7057 if let Some((buffer, cursor_buffer_position)) =
7058 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7059 {
7060 self.edit_prediction_settings =
7061 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7062 }
7063 }
7064 }
7065
7066 fn edit_prediction_settings_at_position(
7067 &self,
7068 buffer: &Entity<Buffer>,
7069 buffer_position: language::Anchor,
7070 cx: &App,
7071 ) -> EditPredictionSettings {
7072 if !self.mode.is_full()
7073 || !self.show_inline_completions_override.unwrap_or(true)
7074 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7075 {
7076 return EditPredictionSettings::Disabled;
7077 }
7078
7079 let buffer = buffer.read(cx);
7080
7081 let file = buffer.file();
7082
7083 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7084 return EditPredictionSettings::Disabled;
7085 };
7086
7087 let by_provider = matches!(
7088 self.menu_inline_completions_policy,
7089 MenuInlineCompletionsPolicy::ByProvider
7090 );
7091
7092 let show_in_menu = by_provider
7093 && self
7094 .edit_prediction_provider
7095 .as_ref()
7096 .map_or(false, |provider| {
7097 provider.provider.show_completions_in_menu()
7098 });
7099
7100 let preview_requires_modifier =
7101 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7102
7103 EditPredictionSettings::Enabled {
7104 show_in_menu,
7105 preview_requires_modifier,
7106 }
7107 }
7108
7109 fn should_show_edit_predictions(&self) -> bool {
7110 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7111 }
7112
7113 pub fn edit_prediction_preview_is_active(&self) -> bool {
7114 matches!(
7115 self.edit_prediction_preview,
7116 EditPredictionPreview::Active { .. }
7117 )
7118 }
7119
7120 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7121 let cursor = self.selections.newest_anchor().head();
7122 if let Some((buffer, cursor_position)) =
7123 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7124 {
7125 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7126 } else {
7127 false
7128 }
7129 }
7130
7131 pub fn supports_minimap(&self, cx: &App) -> bool {
7132 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7133 }
7134
7135 fn edit_predictions_enabled_in_buffer(
7136 &self,
7137 buffer: &Entity<Buffer>,
7138 buffer_position: language::Anchor,
7139 cx: &App,
7140 ) -> bool {
7141 maybe!({
7142 if self.read_only(cx) {
7143 return Some(false);
7144 }
7145 let provider = self.edit_prediction_provider()?;
7146 if !provider.is_enabled(&buffer, buffer_position, cx) {
7147 return Some(false);
7148 }
7149 let buffer = buffer.read(cx);
7150 let Some(file) = buffer.file() else {
7151 return Some(true);
7152 };
7153 let settings = all_language_settings(Some(file), cx);
7154 Some(settings.edit_predictions_enabled_for_file(file, cx))
7155 })
7156 .unwrap_or(false)
7157 }
7158
7159 fn cycle_inline_completion(
7160 &mut self,
7161 direction: Direction,
7162 window: &mut Window,
7163 cx: &mut Context<Self>,
7164 ) -> Option<()> {
7165 let provider = self.edit_prediction_provider()?;
7166 let cursor = self.selections.newest_anchor().head();
7167 let (buffer, cursor_buffer_position) =
7168 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7169 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7170 return None;
7171 }
7172
7173 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7174 self.update_visible_inline_completion(window, cx);
7175
7176 Some(())
7177 }
7178
7179 pub fn show_inline_completion(
7180 &mut self,
7181 _: &ShowEditPrediction,
7182 window: &mut Window,
7183 cx: &mut Context<Self>,
7184 ) {
7185 if !self.has_active_inline_completion() {
7186 self.refresh_inline_completion(false, true, window, cx);
7187 return;
7188 }
7189
7190 self.update_visible_inline_completion(window, cx);
7191 }
7192
7193 pub fn display_cursor_names(
7194 &mut self,
7195 _: &DisplayCursorNames,
7196 window: &mut Window,
7197 cx: &mut Context<Self>,
7198 ) {
7199 self.show_cursor_names(window, cx);
7200 }
7201
7202 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7203 self.show_cursor_names = true;
7204 cx.notify();
7205 cx.spawn_in(window, async move |this, cx| {
7206 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7207 this.update(cx, |this, cx| {
7208 this.show_cursor_names = false;
7209 cx.notify()
7210 })
7211 .ok()
7212 })
7213 .detach();
7214 }
7215
7216 pub fn next_edit_prediction(
7217 &mut self,
7218 _: &NextEditPrediction,
7219 window: &mut Window,
7220 cx: &mut Context<Self>,
7221 ) {
7222 if self.has_active_inline_completion() {
7223 self.cycle_inline_completion(Direction::Next, window, cx);
7224 } else {
7225 let is_copilot_disabled = self
7226 .refresh_inline_completion(false, true, window, cx)
7227 .is_none();
7228 if is_copilot_disabled {
7229 cx.propagate();
7230 }
7231 }
7232 }
7233
7234 pub fn previous_edit_prediction(
7235 &mut self,
7236 _: &PreviousEditPrediction,
7237 window: &mut Window,
7238 cx: &mut Context<Self>,
7239 ) {
7240 if self.has_active_inline_completion() {
7241 self.cycle_inline_completion(Direction::Prev, window, cx);
7242 } else {
7243 let is_copilot_disabled = self
7244 .refresh_inline_completion(false, true, window, cx)
7245 .is_none();
7246 if is_copilot_disabled {
7247 cx.propagate();
7248 }
7249 }
7250 }
7251
7252 pub fn accept_edit_prediction(
7253 &mut self,
7254 _: &AcceptEditPrediction,
7255 window: &mut Window,
7256 cx: &mut Context<Self>,
7257 ) {
7258 if self.show_edit_predictions_in_menu() {
7259 self.hide_context_menu(window, cx);
7260 }
7261
7262 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7263 return;
7264 };
7265
7266 self.report_inline_completion_event(
7267 active_inline_completion.completion_id.clone(),
7268 true,
7269 cx,
7270 );
7271
7272 match &active_inline_completion.completion {
7273 InlineCompletion::Move { target, .. } => {
7274 let target = *target;
7275
7276 if let Some(position_map) = &self.last_position_map {
7277 if position_map
7278 .visible_row_range
7279 .contains(&target.to_display_point(&position_map.snapshot).row())
7280 || !self.edit_prediction_requires_modifier()
7281 {
7282 self.unfold_ranges(&[target..target], true, false, cx);
7283 // Note that this is also done in vim's handler of the Tab action.
7284 self.change_selections(
7285 SelectionEffects::scroll(Autoscroll::newest()),
7286 window,
7287 cx,
7288 |selections| {
7289 selections.select_anchor_ranges([target..target]);
7290 },
7291 );
7292 self.clear_row_highlights::<EditPredictionPreview>();
7293
7294 self.edit_prediction_preview
7295 .set_previous_scroll_position(None);
7296 } else {
7297 self.edit_prediction_preview
7298 .set_previous_scroll_position(Some(
7299 position_map.snapshot.scroll_anchor,
7300 ));
7301
7302 self.highlight_rows::<EditPredictionPreview>(
7303 target..target,
7304 cx.theme().colors().editor_highlighted_line_background,
7305 RowHighlightOptions {
7306 autoscroll: true,
7307 ..Default::default()
7308 },
7309 cx,
7310 );
7311 self.request_autoscroll(Autoscroll::fit(), cx);
7312 }
7313 }
7314 }
7315 InlineCompletion::Edit { edits, .. } => {
7316 if let Some(provider) = self.edit_prediction_provider() {
7317 provider.accept(cx);
7318 }
7319
7320 // Store the transaction ID and selections before applying the edit
7321 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7322
7323 let snapshot = self.buffer.read(cx).snapshot(cx);
7324 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7325
7326 self.buffer.update(cx, |buffer, cx| {
7327 buffer.edit(edits.iter().cloned(), None, cx)
7328 });
7329
7330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7331 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7332 });
7333
7334 let selections = self.selections.disjoint_anchors();
7335 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7336 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7337 if has_new_transaction {
7338 self.selection_history
7339 .insert_transaction(transaction_id_now, selections);
7340 }
7341 }
7342
7343 self.update_visible_inline_completion(window, cx);
7344 if self.active_inline_completion.is_none() {
7345 self.refresh_inline_completion(true, true, window, cx);
7346 }
7347
7348 cx.notify();
7349 }
7350 }
7351
7352 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7353 }
7354
7355 pub fn accept_partial_inline_completion(
7356 &mut self,
7357 _: &AcceptPartialEditPrediction,
7358 window: &mut Window,
7359 cx: &mut Context<Self>,
7360 ) {
7361 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7362 return;
7363 };
7364 if self.selections.count() != 1 {
7365 return;
7366 }
7367
7368 self.report_inline_completion_event(
7369 active_inline_completion.completion_id.clone(),
7370 true,
7371 cx,
7372 );
7373
7374 match &active_inline_completion.completion {
7375 InlineCompletion::Move { target, .. } => {
7376 let target = *target;
7377 self.change_selections(
7378 SelectionEffects::scroll(Autoscroll::newest()),
7379 window,
7380 cx,
7381 |selections| {
7382 selections.select_anchor_ranges([target..target]);
7383 },
7384 );
7385 }
7386 InlineCompletion::Edit { edits, .. } => {
7387 // Find an insertion that starts at the cursor position.
7388 let snapshot = self.buffer.read(cx).snapshot(cx);
7389 let cursor_offset = self.selections.newest::<usize>(cx).head();
7390 let insertion = edits.iter().find_map(|(range, text)| {
7391 let range = range.to_offset(&snapshot);
7392 if range.is_empty() && range.start == cursor_offset {
7393 Some(text)
7394 } else {
7395 None
7396 }
7397 });
7398
7399 if let Some(text) = insertion {
7400 let mut partial_completion = text
7401 .chars()
7402 .by_ref()
7403 .take_while(|c| c.is_alphabetic())
7404 .collect::<String>();
7405 if partial_completion.is_empty() {
7406 partial_completion = text
7407 .chars()
7408 .by_ref()
7409 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7410 .collect::<String>();
7411 }
7412
7413 cx.emit(EditorEvent::InputHandled {
7414 utf16_range_to_replace: None,
7415 text: partial_completion.clone().into(),
7416 });
7417
7418 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7419
7420 self.refresh_inline_completion(true, true, window, cx);
7421 cx.notify();
7422 } else {
7423 self.accept_edit_prediction(&Default::default(), window, cx);
7424 }
7425 }
7426 }
7427 }
7428
7429 fn discard_inline_completion(
7430 &mut self,
7431 should_report_inline_completion_event: bool,
7432 cx: &mut Context<Self>,
7433 ) -> bool {
7434 if should_report_inline_completion_event {
7435 let completion_id = self
7436 .active_inline_completion
7437 .as_ref()
7438 .and_then(|active_completion| active_completion.completion_id.clone());
7439
7440 self.report_inline_completion_event(completion_id, false, cx);
7441 }
7442
7443 if let Some(provider) = self.edit_prediction_provider() {
7444 provider.discard(cx);
7445 }
7446
7447 self.take_active_inline_completion(cx)
7448 }
7449
7450 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7451 let Some(provider) = self.edit_prediction_provider() else {
7452 return;
7453 };
7454
7455 let Some((_, buffer, _)) = self
7456 .buffer
7457 .read(cx)
7458 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7459 else {
7460 return;
7461 };
7462
7463 let extension = buffer
7464 .read(cx)
7465 .file()
7466 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7467
7468 let event_type = match accepted {
7469 true => "Edit Prediction Accepted",
7470 false => "Edit Prediction Discarded",
7471 };
7472 telemetry::event!(
7473 event_type,
7474 provider = provider.name(),
7475 prediction_id = id,
7476 suggestion_accepted = accepted,
7477 file_extension = extension,
7478 );
7479 }
7480
7481 pub fn has_active_inline_completion(&self) -> bool {
7482 self.active_inline_completion.is_some()
7483 }
7484
7485 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7486 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7487 return false;
7488 };
7489
7490 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7491 self.clear_highlights::<InlineCompletionHighlight>(cx);
7492 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7493 true
7494 }
7495
7496 /// Returns true when we're displaying the edit prediction popover below the cursor
7497 /// like we are not previewing and the LSP autocomplete menu is visible
7498 /// or we are in `when_holding_modifier` mode.
7499 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7500 if self.edit_prediction_preview_is_active()
7501 || !self.show_edit_predictions_in_menu()
7502 || !self.edit_predictions_enabled()
7503 {
7504 return false;
7505 }
7506
7507 if self.has_visible_completions_menu() {
7508 return true;
7509 }
7510
7511 has_completion && self.edit_prediction_requires_modifier()
7512 }
7513
7514 fn handle_modifiers_changed(
7515 &mut self,
7516 modifiers: Modifiers,
7517 position_map: &PositionMap,
7518 window: &mut Window,
7519 cx: &mut Context<Self>,
7520 ) {
7521 if self.show_edit_predictions_in_menu() {
7522 self.update_edit_prediction_preview(&modifiers, window, cx);
7523 }
7524
7525 self.update_selection_mode(&modifiers, position_map, window, cx);
7526
7527 let mouse_position = window.mouse_position();
7528 if !position_map.text_hitbox.is_hovered(window) {
7529 return;
7530 }
7531
7532 self.update_hovered_link(
7533 position_map.point_for_position(mouse_position),
7534 &position_map.snapshot,
7535 modifiers,
7536 window,
7537 cx,
7538 )
7539 }
7540
7541 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7542 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7543 if invert {
7544 match multi_cursor_setting {
7545 MultiCursorModifier::Alt => modifiers.alt,
7546 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7547 }
7548 } else {
7549 match multi_cursor_setting {
7550 MultiCursorModifier::Alt => modifiers.secondary(),
7551 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7552 }
7553 }
7554 }
7555
7556 fn columnar_selection_mode(
7557 modifiers: &Modifiers,
7558 cx: &mut Context<Self>,
7559 ) -> Option<ColumnarMode> {
7560 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7561 if Self::multi_cursor_modifier(false, modifiers, cx) {
7562 Some(ColumnarMode::FromMouse)
7563 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7564 Some(ColumnarMode::FromSelection)
7565 } else {
7566 None
7567 }
7568 } else {
7569 None
7570 }
7571 }
7572
7573 fn update_selection_mode(
7574 &mut self,
7575 modifiers: &Modifiers,
7576 position_map: &PositionMap,
7577 window: &mut Window,
7578 cx: &mut Context<Self>,
7579 ) {
7580 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7581 return;
7582 };
7583 if self.selections.pending.is_none() {
7584 return;
7585 }
7586
7587 let mouse_position = window.mouse_position();
7588 let point_for_position = position_map.point_for_position(mouse_position);
7589 let position = point_for_position.previous_valid;
7590
7591 self.select(
7592 SelectPhase::BeginColumnar {
7593 position,
7594 reset: false,
7595 mode,
7596 goal_column: point_for_position.exact_unclipped.column(),
7597 },
7598 window,
7599 cx,
7600 );
7601 }
7602
7603 fn update_edit_prediction_preview(
7604 &mut self,
7605 modifiers: &Modifiers,
7606 window: &mut Window,
7607 cx: &mut Context<Self>,
7608 ) {
7609 let mut modifiers_held = false;
7610 if let Some(accept_keystroke) = self
7611 .accept_edit_prediction_keybind(false, window, cx)
7612 .keystroke()
7613 {
7614 modifiers_held = modifiers_held
7615 || (&accept_keystroke.modifiers == modifiers
7616 && accept_keystroke.modifiers.modified());
7617 };
7618 if let Some(accept_partial_keystroke) = self
7619 .accept_edit_prediction_keybind(true, window, cx)
7620 .keystroke()
7621 {
7622 modifiers_held = modifiers_held
7623 || (&accept_partial_keystroke.modifiers == modifiers
7624 && accept_partial_keystroke.modifiers.modified());
7625 }
7626
7627 if modifiers_held {
7628 if matches!(
7629 self.edit_prediction_preview,
7630 EditPredictionPreview::Inactive { .. }
7631 ) {
7632 self.edit_prediction_preview = EditPredictionPreview::Active {
7633 previous_scroll_position: None,
7634 since: Instant::now(),
7635 };
7636
7637 self.update_visible_inline_completion(window, cx);
7638 cx.notify();
7639 }
7640 } else if let EditPredictionPreview::Active {
7641 previous_scroll_position,
7642 since,
7643 } = self.edit_prediction_preview
7644 {
7645 if let (Some(previous_scroll_position), Some(position_map)) =
7646 (previous_scroll_position, self.last_position_map.as_ref())
7647 {
7648 self.set_scroll_position(
7649 previous_scroll_position
7650 .scroll_position(&position_map.snapshot.display_snapshot),
7651 window,
7652 cx,
7653 );
7654 }
7655
7656 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7657 released_too_fast: since.elapsed() < Duration::from_millis(200),
7658 };
7659 self.clear_row_highlights::<EditPredictionPreview>();
7660 self.update_visible_inline_completion(window, cx);
7661 cx.notify();
7662 }
7663 }
7664
7665 fn update_visible_inline_completion(
7666 &mut self,
7667 _window: &mut Window,
7668 cx: &mut Context<Self>,
7669 ) -> Option<()> {
7670 let selection = self.selections.newest_anchor();
7671 let cursor = selection.head();
7672 let multibuffer = self.buffer.read(cx).snapshot(cx);
7673 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7674 let excerpt_id = cursor.excerpt_id;
7675
7676 let show_in_menu = self.show_edit_predictions_in_menu();
7677 let completions_menu_has_precedence = !show_in_menu
7678 && (self.context_menu.borrow().is_some()
7679 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7680
7681 if completions_menu_has_precedence
7682 || !offset_selection.is_empty()
7683 || self
7684 .active_inline_completion
7685 .as_ref()
7686 .map_or(false, |completion| {
7687 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7688 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7689 !invalidation_range.contains(&offset_selection.head())
7690 })
7691 {
7692 self.discard_inline_completion(false, cx);
7693 return None;
7694 }
7695
7696 self.take_active_inline_completion(cx);
7697 let Some(provider) = self.edit_prediction_provider() else {
7698 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7699 return None;
7700 };
7701
7702 let (buffer, cursor_buffer_position) =
7703 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7704
7705 self.edit_prediction_settings =
7706 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7707
7708 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7709
7710 if self.edit_prediction_indent_conflict {
7711 let cursor_point = cursor.to_point(&multibuffer);
7712
7713 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7714
7715 if let Some((_, indent)) = indents.iter().next() {
7716 if indent.len == cursor_point.column {
7717 self.edit_prediction_indent_conflict = false;
7718 }
7719 }
7720 }
7721
7722 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7723 let edits = inline_completion
7724 .edits
7725 .into_iter()
7726 .flat_map(|(range, new_text)| {
7727 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7728 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7729 Some((start..end, new_text))
7730 })
7731 .collect::<Vec<_>>();
7732 if edits.is_empty() {
7733 return None;
7734 }
7735
7736 let first_edit_start = edits.first().unwrap().0.start;
7737 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7738 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7739
7740 let last_edit_end = edits.last().unwrap().0.end;
7741 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7742 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7743
7744 let cursor_row = cursor.to_point(&multibuffer).row;
7745
7746 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7747
7748 let mut inlay_ids = Vec::new();
7749 let invalidation_row_range;
7750 let move_invalidation_row_range = if cursor_row < edit_start_row {
7751 Some(cursor_row..edit_end_row)
7752 } else if cursor_row > edit_end_row {
7753 Some(edit_start_row..cursor_row)
7754 } else {
7755 None
7756 };
7757 let is_move =
7758 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7759 let completion = if is_move {
7760 invalidation_row_range =
7761 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7762 let target = first_edit_start;
7763 InlineCompletion::Move { target, snapshot }
7764 } else {
7765 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7766 && !self.inline_completions_hidden_for_vim_mode;
7767
7768 if show_completions_in_buffer {
7769 if edits
7770 .iter()
7771 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7772 {
7773 let mut inlays = Vec::new();
7774 for (range, new_text) in &edits {
7775 let inlay = Inlay::inline_completion(
7776 post_inc(&mut self.next_inlay_id),
7777 range.start,
7778 new_text.as_str(),
7779 );
7780 inlay_ids.push(inlay.id);
7781 inlays.push(inlay);
7782 }
7783
7784 self.splice_inlays(&[], inlays, cx);
7785 } else {
7786 let background_color = cx.theme().status().deleted_background;
7787 self.highlight_text::<InlineCompletionHighlight>(
7788 edits.iter().map(|(range, _)| range.clone()).collect(),
7789 HighlightStyle {
7790 background_color: Some(background_color),
7791 ..Default::default()
7792 },
7793 cx,
7794 );
7795 }
7796 }
7797
7798 invalidation_row_range = edit_start_row..edit_end_row;
7799
7800 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7801 if provider.show_tab_accept_marker() {
7802 EditDisplayMode::TabAccept
7803 } else {
7804 EditDisplayMode::Inline
7805 }
7806 } else {
7807 EditDisplayMode::DiffPopover
7808 };
7809
7810 InlineCompletion::Edit {
7811 edits,
7812 edit_preview: inline_completion.edit_preview,
7813 display_mode,
7814 snapshot,
7815 }
7816 };
7817
7818 let invalidation_range = multibuffer
7819 .anchor_before(Point::new(invalidation_row_range.start, 0))
7820 ..multibuffer.anchor_after(Point::new(
7821 invalidation_row_range.end,
7822 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7823 ));
7824
7825 self.stale_inline_completion_in_menu = None;
7826 self.active_inline_completion = Some(InlineCompletionState {
7827 inlay_ids,
7828 completion,
7829 completion_id: inline_completion.id,
7830 invalidation_range,
7831 });
7832
7833 cx.notify();
7834
7835 Some(())
7836 }
7837
7838 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7839 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7840 }
7841
7842 fn clear_tasks(&mut self) {
7843 self.tasks.clear()
7844 }
7845
7846 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7847 if self.tasks.insert(key, value).is_some() {
7848 // This case should hopefully be rare, but just in case...
7849 log::error!(
7850 "multiple different run targets found on a single line, only the last target will be rendered"
7851 )
7852 }
7853 }
7854
7855 /// Get all display points of breakpoints that will be rendered within editor
7856 ///
7857 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7858 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7859 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7860 fn active_breakpoints(
7861 &self,
7862 range: Range<DisplayRow>,
7863 window: &mut Window,
7864 cx: &mut Context<Self>,
7865 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7866 let mut breakpoint_display_points = HashMap::default();
7867
7868 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7869 return breakpoint_display_points;
7870 };
7871
7872 let snapshot = self.snapshot(window, cx);
7873
7874 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7875 let Some(project) = self.project.as_ref() else {
7876 return breakpoint_display_points;
7877 };
7878
7879 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7880 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7881
7882 for (buffer_snapshot, range, excerpt_id) in
7883 multi_buffer_snapshot.range_to_buffer_ranges(range)
7884 {
7885 let Some(buffer) = project
7886 .read(cx)
7887 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7888 else {
7889 continue;
7890 };
7891 let breakpoints = breakpoint_store.read(cx).breakpoints(
7892 &buffer,
7893 Some(
7894 buffer_snapshot.anchor_before(range.start)
7895 ..buffer_snapshot.anchor_after(range.end),
7896 ),
7897 buffer_snapshot,
7898 cx,
7899 );
7900 for (breakpoint, state) in breakpoints {
7901 let multi_buffer_anchor =
7902 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7903 let position = multi_buffer_anchor
7904 .to_point(&multi_buffer_snapshot)
7905 .to_display_point(&snapshot);
7906
7907 breakpoint_display_points.insert(
7908 position.row(),
7909 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7910 );
7911 }
7912 }
7913
7914 breakpoint_display_points
7915 }
7916
7917 fn breakpoint_context_menu(
7918 &self,
7919 anchor: Anchor,
7920 window: &mut Window,
7921 cx: &mut Context<Self>,
7922 ) -> Entity<ui::ContextMenu> {
7923 let weak_editor = cx.weak_entity();
7924 let focus_handle = self.focus_handle(cx);
7925
7926 let row = self
7927 .buffer
7928 .read(cx)
7929 .snapshot(cx)
7930 .summary_for_anchor::<Point>(&anchor)
7931 .row;
7932
7933 let breakpoint = self
7934 .breakpoint_at_row(row, window, cx)
7935 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7936
7937 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7938 "Edit Log Breakpoint"
7939 } else {
7940 "Set Log Breakpoint"
7941 };
7942
7943 let condition_breakpoint_msg = if breakpoint
7944 .as_ref()
7945 .is_some_and(|bp| bp.1.condition.is_some())
7946 {
7947 "Edit Condition Breakpoint"
7948 } else {
7949 "Set Condition Breakpoint"
7950 };
7951
7952 let hit_condition_breakpoint_msg = if breakpoint
7953 .as_ref()
7954 .is_some_and(|bp| bp.1.hit_condition.is_some())
7955 {
7956 "Edit Hit Condition Breakpoint"
7957 } else {
7958 "Set Hit Condition Breakpoint"
7959 };
7960
7961 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7962 "Unset Breakpoint"
7963 } else {
7964 "Set Breakpoint"
7965 };
7966
7967 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7968
7969 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7970 BreakpointState::Enabled => Some("Disable"),
7971 BreakpointState::Disabled => Some("Enable"),
7972 });
7973
7974 let (anchor, breakpoint) =
7975 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7976
7977 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7978 menu.on_blur_subscription(Subscription::new(|| {}))
7979 .context(focus_handle)
7980 .when(run_to_cursor, |this| {
7981 let weak_editor = weak_editor.clone();
7982 this.entry("Run to cursor", None, move |window, cx| {
7983 weak_editor
7984 .update(cx, |editor, cx| {
7985 editor.change_selections(
7986 SelectionEffects::no_scroll(),
7987 window,
7988 cx,
7989 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7990 );
7991 })
7992 .ok();
7993
7994 window.dispatch_action(Box::new(RunToCursor), cx);
7995 })
7996 .separator()
7997 })
7998 .when_some(toggle_state_msg, |this, msg| {
7999 this.entry(msg, None, {
8000 let weak_editor = weak_editor.clone();
8001 let breakpoint = breakpoint.clone();
8002 move |_window, cx| {
8003 weak_editor
8004 .update(cx, |this, cx| {
8005 this.edit_breakpoint_at_anchor(
8006 anchor,
8007 breakpoint.as_ref().clone(),
8008 BreakpointEditAction::InvertState,
8009 cx,
8010 );
8011 })
8012 .log_err();
8013 }
8014 })
8015 })
8016 .entry(set_breakpoint_msg, None, {
8017 let weak_editor = weak_editor.clone();
8018 let breakpoint = breakpoint.clone();
8019 move |_window, cx| {
8020 weak_editor
8021 .update(cx, |this, cx| {
8022 this.edit_breakpoint_at_anchor(
8023 anchor,
8024 breakpoint.as_ref().clone(),
8025 BreakpointEditAction::Toggle,
8026 cx,
8027 );
8028 })
8029 .log_err();
8030 }
8031 })
8032 .entry(log_breakpoint_msg, None, {
8033 let breakpoint = breakpoint.clone();
8034 let weak_editor = weak_editor.clone();
8035 move |window, cx| {
8036 weak_editor
8037 .update(cx, |this, cx| {
8038 this.add_edit_breakpoint_block(
8039 anchor,
8040 breakpoint.as_ref(),
8041 BreakpointPromptEditAction::Log,
8042 window,
8043 cx,
8044 );
8045 })
8046 .log_err();
8047 }
8048 })
8049 .entry(condition_breakpoint_msg, None, {
8050 let breakpoint = breakpoint.clone();
8051 let weak_editor = weak_editor.clone();
8052 move |window, cx| {
8053 weak_editor
8054 .update(cx, |this, cx| {
8055 this.add_edit_breakpoint_block(
8056 anchor,
8057 breakpoint.as_ref(),
8058 BreakpointPromptEditAction::Condition,
8059 window,
8060 cx,
8061 );
8062 })
8063 .log_err();
8064 }
8065 })
8066 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8067 weak_editor
8068 .update(cx, |this, cx| {
8069 this.add_edit_breakpoint_block(
8070 anchor,
8071 breakpoint.as_ref(),
8072 BreakpointPromptEditAction::HitCondition,
8073 window,
8074 cx,
8075 );
8076 })
8077 .log_err();
8078 })
8079 })
8080 }
8081
8082 fn render_breakpoint(
8083 &self,
8084 position: Anchor,
8085 row: DisplayRow,
8086 breakpoint: &Breakpoint,
8087 state: Option<BreakpointSessionState>,
8088 cx: &mut Context<Self>,
8089 ) -> IconButton {
8090 let is_rejected = state.is_some_and(|s| !s.verified);
8091 // Is it a breakpoint that shows up when hovering over gutter?
8092 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8093 (false, false),
8094 |PhantomBreakpointIndicator {
8095 is_active,
8096 display_row,
8097 collides_with_existing_breakpoint,
8098 }| {
8099 (
8100 is_active && display_row == row,
8101 collides_with_existing_breakpoint,
8102 )
8103 },
8104 );
8105
8106 let (color, icon) = {
8107 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8108 (false, false) => ui::IconName::DebugBreakpoint,
8109 (true, false) => ui::IconName::DebugLogBreakpoint,
8110 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8111 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8112 };
8113
8114 let color = if is_phantom {
8115 Color::Hint
8116 } else if is_rejected {
8117 Color::Disabled
8118 } else {
8119 Color::Debugger
8120 };
8121
8122 (color, icon)
8123 };
8124
8125 let breakpoint = Arc::from(breakpoint.clone());
8126
8127 let alt_as_text = gpui::Keystroke {
8128 modifiers: Modifiers::secondary_key(),
8129 ..Default::default()
8130 };
8131 let primary_action_text = if breakpoint.is_disabled() {
8132 "Enable breakpoint"
8133 } else if is_phantom && !collides_with_existing {
8134 "Set breakpoint"
8135 } else {
8136 "Unset breakpoint"
8137 };
8138 let focus_handle = self.focus_handle.clone();
8139
8140 let meta = if is_rejected {
8141 SharedString::from("No executable code is associated with this line.")
8142 } else if collides_with_existing && !breakpoint.is_disabled() {
8143 SharedString::from(format!(
8144 "{alt_as_text}-click to disable,\nright-click for more options."
8145 ))
8146 } else {
8147 SharedString::from("Right-click for more options.")
8148 };
8149 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8150 .icon_size(IconSize::XSmall)
8151 .size(ui::ButtonSize::None)
8152 .when(is_rejected, |this| {
8153 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8154 })
8155 .icon_color(color)
8156 .style(ButtonStyle::Transparent)
8157 .on_click(cx.listener({
8158 let breakpoint = breakpoint.clone();
8159
8160 move |editor, event: &ClickEvent, window, cx| {
8161 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8162 BreakpointEditAction::InvertState
8163 } else {
8164 BreakpointEditAction::Toggle
8165 };
8166
8167 window.focus(&editor.focus_handle(cx));
8168 editor.edit_breakpoint_at_anchor(
8169 position,
8170 breakpoint.as_ref().clone(),
8171 edit_action,
8172 cx,
8173 );
8174 }
8175 }))
8176 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8177 editor.set_breakpoint_context_menu(
8178 row,
8179 Some(position),
8180 event.down.position,
8181 window,
8182 cx,
8183 );
8184 }))
8185 .tooltip(move |window, cx| {
8186 Tooltip::with_meta_in(
8187 primary_action_text,
8188 Some(&ToggleBreakpoint),
8189 meta.clone(),
8190 &focus_handle,
8191 window,
8192 cx,
8193 )
8194 })
8195 }
8196
8197 fn build_tasks_context(
8198 project: &Entity<Project>,
8199 buffer: &Entity<Buffer>,
8200 buffer_row: u32,
8201 tasks: &Arc<RunnableTasks>,
8202 cx: &mut Context<Self>,
8203 ) -> Task<Option<task::TaskContext>> {
8204 let position = Point::new(buffer_row, tasks.column);
8205 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8206 let location = Location {
8207 buffer: buffer.clone(),
8208 range: range_start..range_start,
8209 };
8210 // Fill in the environmental variables from the tree-sitter captures
8211 let mut captured_task_variables = TaskVariables::default();
8212 for (capture_name, value) in tasks.extra_variables.clone() {
8213 captured_task_variables.insert(
8214 task::VariableName::Custom(capture_name.into()),
8215 value.clone(),
8216 );
8217 }
8218 project.update(cx, |project, cx| {
8219 project.task_store().update(cx, |task_store, cx| {
8220 task_store.task_context_for_location(captured_task_variables, location, cx)
8221 })
8222 })
8223 }
8224
8225 pub fn spawn_nearest_task(
8226 &mut self,
8227 action: &SpawnNearestTask,
8228 window: &mut Window,
8229 cx: &mut Context<Self>,
8230 ) {
8231 let Some((workspace, _)) = self.workspace.clone() else {
8232 return;
8233 };
8234 let Some(project) = self.project.clone() else {
8235 return;
8236 };
8237
8238 // Try to find a closest, enclosing node using tree-sitter that has a
8239 // task
8240 let Some((buffer, buffer_row, tasks)) = self
8241 .find_enclosing_node_task(cx)
8242 // Or find the task that's closest in row-distance.
8243 .or_else(|| self.find_closest_task(cx))
8244 else {
8245 return;
8246 };
8247
8248 let reveal_strategy = action.reveal;
8249 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8250 cx.spawn_in(window, async move |_, cx| {
8251 let context = task_context.await?;
8252 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8253
8254 let resolved = &mut resolved_task.resolved;
8255 resolved.reveal = reveal_strategy;
8256
8257 workspace
8258 .update_in(cx, |workspace, window, cx| {
8259 workspace.schedule_resolved_task(
8260 task_source_kind,
8261 resolved_task,
8262 false,
8263 window,
8264 cx,
8265 );
8266 })
8267 .ok()
8268 })
8269 .detach();
8270 }
8271
8272 fn find_closest_task(
8273 &mut self,
8274 cx: &mut Context<Self>,
8275 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8276 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8277
8278 let ((buffer_id, row), tasks) = self
8279 .tasks
8280 .iter()
8281 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8282
8283 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8284 let tasks = Arc::new(tasks.to_owned());
8285 Some((buffer, *row, tasks))
8286 }
8287
8288 fn find_enclosing_node_task(
8289 &mut self,
8290 cx: &mut Context<Self>,
8291 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8292 let snapshot = self.buffer.read(cx).snapshot(cx);
8293 let offset = self.selections.newest::<usize>(cx).head();
8294 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8295 let buffer_id = excerpt.buffer().remote_id();
8296
8297 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8298 let mut cursor = layer.node().walk();
8299
8300 while cursor.goto_first_child_for_byte(offset).is_some() {
8301 if cursor.node().end_byte() == offset {
8302 cursor.goto_next_sibling();
8303 }
8304 }
8305
8306 // Ascend to the smallest ancestor that contains the range and has a task.
8307 loop {
8308 let node = cursor.node();
8309 let node_range = node.byte_range();
8310 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8311
8312 // Check if this node contains our offset
8313 if node_range.start <= offset && node_range.end >= offset {
8314 // If it contains offset, check for task
8315 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8316 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8317 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8318 }
8319 }
8320
8321 if !cursor.goto_parent() {
8322 break;
8323 }
8324 }
8325 None
8326 }
8327
8328 fn render_run_indicator(
8329 &self,
8330 _style: &EditorStyle,
8331 is_active: bool,
8332 row: DisplayRow,
8333 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8334 cx: &mut Context<Self>,
8335 ) -> IconButton {
8336 let color = Color::Muted;
8337 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8338
8339 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8340 .shape(ui::IconButtonShape::Square)
8341 .icon_size(IconSize::XSmall)
8342 .icon_color(color)
8343 .toggle_state(is_active)
8344 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8345 let quick_launch = e.down.button == MouseButton::Left;
8346 window.focus(&editor.focus_handle(cx));
8347 editor.toggle_code_actions(
8348 &ToggleCodeActions {
8349 deployed_from: Some(CodeActionSource::RunMenu(row)),
8350 quick_launch,
8351 },
8352 window,
8353 cx,
8354 );
8355 }))
8356 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8357 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8358 }))
8359 }
8360
8361 pub fn context_menu_visible(&self) -> bool {
8362 !self.edit_prediction_preview_is_active()
8363 && self
8364 .context_menu
8365 .borrow()
8366 .as_ref()
8367 .map_or(false, |menu| menu.visible())
8368 }
8369
8370 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8371 self.context_menu
8372 .borrow()
8373 .as_ref()
8374 .map(|menu| menu.origin())
8375 }
8376
8377 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8378 self.context_menu_options = Some(options);
8379 }
8380
8381 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8382 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8383
8384 fn render_edit_prediction_popover(
8385 &mut self,
8386 text_bounds: &Bounds<Pixels>,
8387 content_origin: gpui::Point<Pixels>,
8388 right_margin: Pixels,
8389 editor_snapshot: &EditorSnapshot,
8390 visible_row_range: Range<DisplayRow>,
8391 scroll_top: f32,
8392 scroll_bottom: f32,
8393 line_layouts: &[LineWithInvisibles],
8394 line_height: Pixels,
8395 scroll_pixel_position: gpui::Point<Pixels>,
8396 newest_selection_head: Option<DisplayPoint>,
8397 editor_width: Pixels,
8398 style: &EditorStyle,
8399 window: &mut Window,
8400 cx: &mut App,
8401 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8402 if self.mode().is_minimap() {
8403 return None;
8404 }
8405 let active_inline_completion = self.active_inline_completion.as_ref()?;
8406
8407 if self.edit_prediction_visible_in_cursor_popover(true) {
8408 return None;
8409 }
8410
8411 match &active_inline_completion.completion {
8412 InlineCompletion::Move { target, .. } => {
8413 let target_display_point = target.to_display_point(editor_snapshot);
8414
8415 if self.edit_prediction_requires_modifier() {
8416 if !self.edit_prediction_preview_is_active() {
8417 return None;
8418 }
8419
8420 self.render_edit_prediction_modifier_jump_popover(
8421 text_bounds,
8422 content_origin,
8423 visible_row_range,
8424 line_layouts,
8425 line_height,
8426 scroll_pixel_position,
8427 newest_selection_head,
8428 target_display_point,
8429 window,
8430 cx,
8431 )
8432 } else {
8433 self.render_edit_prediction_eager_jump_popover(
8434 text_bounds,
8435 content_origin,
8436 editor_snapshot,
8437 visible_row_range,
8438 scroll_top,
8439 scroll_bottom,
8440 line_height,
8441 scroll_pixel_position,
8442 target_display_point,
8443 editor_width,
8444 window,
8445 cx,
8446 )
8447 }
8448 }
8449 InlineCompletion::Edit {
8450 display_mode: EditDisplayMode::Inline,
8451 ..
8452 } => None,
8453 InlineCompletion::Edit {
8454 display_mode: EditDisplayMode::TabAccept,
8455 edits,
8456 ..
8457 } => {
8458 let range = &edits.first()?.0;
8459 let target_display_point = range.end.to_display_point(editor_snapshot);
8460
8461 self.render_edit_prediction_end_of_line_popover(
8462 "Accept",
8463 editor_snapshot,
8464 visible_row_range,
8465 target_display_point,
8466 line_height,
8467 scroll_pixel_position,
8468 content_origin,
8469 editor_width,
8470 window,
8471 cx,
8472 )
8473 }
8474 InlineCompletion::Edit {
8475 edits,
8476 edit_preview,
8477 display_mode: EditDisplayMode::DiffPopover,
8478 snapshot,
8479 } => self.render_edit_prediction_diff_popover(
8480 text_bounds,
8481 content_origin,
8482 right_margin,
8483 editor_snapshot,
8484 visible_row_range,
8485 line_layouts,
8486 line_height,
8487 scroll_pixel_position,
8488 newest_selection_head,
8489 editor_width,
8490 style,
8491 edits,
8492 edit_preview,
8493 snapshot,
8494 window,
8495 cx,
8496 ),
8497 }
8498 }
8499
8500 fn render_edit_prediction_modifier_jump_popover(
8501 &mut self,
8502 text_bounds: &Bounds<Pixels>,
8503 content_origin: gpui::Point<Pixels>,
8504 visible_row_range: Range<DisplayRow>,
8505 line_layouts: &[LineWithInvisibles],
8506 line_height: Pixels,
8507 scroll_pixel_position: gpui::Point<Pixels>,
8508 newest_selection_head: Option<DisplayPoint>,
8509 target_display_point: DisplayPoint,
8510 window: &mut Window,
8511 cx: &mut App,
8512 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8513 let scrolled_content_origin =
8514 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8515
8516 const SCROLL_PADDING_Y: Pixels = px(12.);
8517
8518 if target_display_point.row() < visible_row_range.start {
8519 return self.render_edit_prediction_scroll_popover(
8520 |_| SCROLL_PADDING_Y,
8521 IconName::ArrowUp,
8522 visible_row_range,
8523 line_layouts,
8524 newest_selection_head,
8525 scrolled_content_origin,
8526 window,
8527 cx,
8528 );
8529 } else if target_display_point.row() >= visible_row_range.end {
8530 return self.render_edit_prediction_scroll_popover(
8531 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8532 IconName::ArrowDown,
8533 visible_row_range,
8534 line_layouts,
8535 newest_selection_head,
8536 scrolled_content_origin,
8537 window,
8538 cx,
8539 );
8540 }
8541
8542 const POLE_WIDTH: Pixels = px(2.);
8543
8544 let line_layout =
8545 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8546 let target_column = target_display_point.column() as usize;
8547
8548 let target_x = line_layout.x_for_index(target_column);
8549 let target_y =
8550 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8551
8552 let flag_on_right = target_x < text_bounds.size.width / 2.;
8553
8554 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8555 border_color.l += 0.001;
8556
8557 let mut element = v_flex()
8558 .items_end()
8559 .when(flag_on_right, |el| el.items_start())
8560 .child(if flag_on_right {
8561 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8562 .rounded_bl(px(0.))
8563 .rounded_tl(px(0.))
8564 .border_l_2()
8565 .border_color(border_color)
8566 } else {
8567 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8568 .rounded_br(px(0.))
8569 .rounded_tr(px(0.))
8570 .border_r_2()
8571 .border_color(border_color)
8572 })
8573 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8574 .into_any();
8575
8576 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8577
8578 let mut origin = scrolled_content_origin + point(target_x, target_y)
8579 - point(
8580 if flag_on_right {
8581 POLE_WIDTH
8582 } else {
8583 size.width - POLE_WIDTH
8584 },
8585 size.height - line_height,
8586 );
8587
8588 origin.x = origin.x.max(content_origin.x);
8589
8590 element.prepaint_at(origin, window, cx);
8591
8592 Some((element, origin))
8593 }
8594
8595 fn render_edit_prediction_scroll_popover(
8596 &mut self,
8597 to_y: impl Fn(Size<Pixels>) -> Pixels,
8598 scroll_icon: IconName,
8599 visible_row_range: Range<DisplayRow>,
8600 line_layouts: &[LineWithInvisibles],
8601 newest_selection_head: Option<DisplayPoint>,
8602 scrolled_content_origin: gpui::Point<Pixels>,
8603 window: &mut Window,
8604 cx: &mut App,
8605 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8606 let mut element = self
8607 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8608 .into_any();
8609
8610 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8611
8612 let cursor = newest_selection_head?;
8613 let cursor_row_layout =
8614 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8615 let cursor_column = cursor.column() as usize;
8616
8617 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8618
8619 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8620
8621 element.prepaint_at(origin, window, cx);
8622 Some((element, origin))
8623 }
8624
8625 fn render_edit_prediction_eager_jump_popover(
8626 &mut self,
8627 text_bounds: &Bounds<Pixels>,
8628 content_origin: gpui::Point<Pixels>,
8629 editor_snapshot: &EditorSnapshot,
8630 visible_row_range: Range<DisplayRow>,
8631 scroll_top: f32,
8632 scroll_bottom: f32,
8633 line_height: Pixels,
8634 scroll_pixel_position: gpui::Point<Pixels>,
8635 target_display_point: DisplayPoint,
8636 editor_width: Pixels,
8637 window: &mut Window,
8638 cx: &mut App,
8639 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8640 if target_display_point.row().as_f32() < scroll_top {
8641 let mut element = self
8642 .render_edit_prediction_line_popover(
8643 "Jump to Edit",
8644 Some(IconName::ArrowUp),
8645 window,
8646 cx,
8647 )?
8648 .into_any();
8649
8650 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8651 let offset = point(
8652 (text_bounds.size.width - size.width) / 2.,
8653 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8654 );
8655
8656 let origin = text_bounds.origin + offset;
8657 element.prepaint_at(origin, window, cx);
8658 Some((element, origin))
8659 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8660 let mut element = self
8661 .render_edit_prediction_line_popover(
8662 "Jump to Edit",
8663 Some(IconName::ArrowDown),
8664 window,
8665 cx,
8666 )?
8667 .into_any();
8668
8669 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8670 let offset = point(
8671 (text_bounds.size.width - size.width) / 2.,
8672 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8673 );
8674
8675 let origin = text_bounds.origin + offset;
8676 element.prepaint_at(origin, window, cx);
8677 Some((element, origin))
8678 } else {
8679 self.render_edit_prediction_end_of_line_popover(
8680 "Jump to Edit",
8681 editor_snapshot,
8682 visible_row_range,
8683 target_display_point,
8684 line_height,
8685 scroll_pixel_position,
8686 content_origin,
8687 editor_width,
8688 window,
8689 cx,
8690 )
8691 }
8692 }
8693
8694 fn render_edit_prediction_end_of_line_popover(
8695 self: &mut Editor,
8696 label: &'static str,
8697 editor_snapshot: &EditorSnapshot,
8698 visible_row_range: Range<DisplayRow>,
8699 target_display_point: DisplayPoint,
8700 line_height: Pixels,
8701 scroll_pixel_position: gpui::Point<Pixels>,
8702 content_origin: gpui::Point<Pixels>,
8703 editor_width: Pixels,
8704 window: &mut Window,
8705 cx: &mut App,
8706 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8707 let target_line_end = DisplayPoint::new(
8708 target_display_point.row(),
8709 editor_snapshot.line_len(target_display_point.row()),
8710 );
8711
8712 let mut element = self
8713 .render_edit_prediction_line_popover(label, None, window, cx)?
8714 .into_any();
8715
8716 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8717
8718 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8719
8720 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8721 let mut origin = start_point
8722 + line_origin
8723 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8724 origin.x = origin.x.max(content_origin.x);
8725
8726 let max_x = content_origin.x + editor_width - size.width;
8727
8728 if origin.x > max_x {
8729 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8730
8731 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8732 origin.y += offset;
8733 IconName::ArrowUp
8734 } else {
8735 origin.y -= offset;
8736 IconName::ArrowDown
8737 };
8738
8739 element = self
8740 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8741 .into_any();
8742
8743 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8744
8745 origin.x = content_origin.x + editor_width - size.width - px(2.);
8746 }
8747
8748 element.prepaint_at(origin, window, cx);
8749 Some((element, origin))
8750 }
8751
8752 fn render_edit_prediction_diff_popover(
8753 self: &Editor,
8754 text_bounds: &Bounds<Pixels>,
8755 content_origin: gpui::Point<Pixels>,
8756 right_margin: Pixels,
8757 editor_snapshot: &EditorSnapshot,
8758 visible_row_range: Range<DisplayRow>,
8759 line_layouts: &[LineWithInvisibles],
8760 line_height: Pixels,
8761 scroll_pixel_position: gpui::Point<Pixels>,
8762 newest_selection_head: Option<DisplayPoint>,
8763 editor_width: Pixels,
8764 style: &EditorStyle,
8765 edits: &Vec<(Range<Anchor>, String)>,
8766 edit_preview: &Option<language::EditPreview>,
8767 snapshot: &language::BufferSnapshot,
8768 window: &mut Window,
8769 cx: &mut App,
8770 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8771 let edit_start = edits
8772 .first()
8773 .unwrap()
8774 .0
8775 .start
8776 .to_display_point(editor_snapshot);
8777 let edit_end = edits
8778 .last()
8779 .unwrap()
8780 .0
8781 .end
8782 .to_display_point(editor_snapshot);
8783
8784 let is_visible = visible_row_range.contains(&edit_start.row())
8785 || visible_row_range.contains(&edit_end.row());
8786 if !is_visible {
8787 return None;
8788 }
8789
8790 let highlighted_edits =
8791 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8792
8793 let styled_text = highlighted_edits.to_styled_text(&style.text);
8794 let line_count = highlighted_edits.text.lines().count();
8795
8796 const BORDER_WIDTH: Pixels = px(1.);
8797
8798 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8799 let has_keybind = keybind.is_some();
8800
8801 let mut element = h_flex()
8802 .items_start()
8803 .child(
8804 h_flex()
8805 .bg(cx.theme().colors().editor_background)
8806 .border(BORDER_WIDTH)
8807 .shadow_xs()
8808 .border_color(cx.theme().colors().border)
8809 .rounded_l_lg()
8810 .when(line_count > 1, |el| el.rounded_br_lg())
8811 .pr_1()
8812 .child(styled_text),
8813 )
8814 .child(
8815 h_flex()
8816 .h(line_height + BORDER_WIDTH * 2.)
8817 .px_1p5()
8818 .gap_1()
8819 // Workaround: For some reason, there's a gap if we don't do this
8820 .ml(-BORDER_WIDTH)
8821 .shadow(vec![gpui::BoxShadow {
8822 color: gpui::black().opacity(0.05),
8823 offset: point(px(1.), px(1.)),
8824 blur_radius: px(2.),
8825 spread_radius: px(0.),
8826 }])
8827 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8828 .border(BORDER_WIDTH)
8829 .border_color(cx.theme().colors().border)
8830 .rounded_r_lg()
8831 .id("edit_prediction_diff_popover_keybind")
8832 .when(!has_keybind, |el| {
8833 let status_colors = cx.theme().status();
8834
8835 el.bg(status_colors.error_background)
8836 .border_color(status_colors.error.opacity(0.6))
8837 .child(Icon::new(IconName::Info).color(Color::Error))
8838 .cursor_default()
8839 .hoverable_tooltip(move |_window, cx| {
8840 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8841 })
8842 })
8843 .children(keybind),
8844 )
8845 .into_any();
8846
8847 let longest_row =
8848 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8849 let longest_line_width = if visible_row_range.contains(&longest_row) {
8850 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8851 } else {
8852 layout_line(
8853 longest_row,
8854 editor_snapshot,
8855 style,
8856 editor_width,
8857 |_| false,
8858 window,
8859 cx,
8860 )
8861 .width
8862 };
8863
8864 let viewport_bounds =
8865 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8866 right: -right_margin,
8867 ..Default::default()
8868 });
8869
8870 let x_after_longest =
8871 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8872 - scroll_pixel_position.x;
8873
8874 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8875
8876 // Fully visible if it can be displayed within the window (allow overlapping other
8877 // panes). However, this is only allowed if the popover starts within text_bounds.
8878 let can_position_to_the_right = x_after_longest < text_bounds.right()
8879 && x_after_longest + element_bounds.width < viewport_bounds.right();
8880
8881 let mut origin = if can_position_to_the_right {
8882 point(
8883 x_after_longest,
8884 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8885 - scroll_pixel_position.y,
8886 )
8887 } else {
8888 let cursor_row = newest_selection_head.map(|head| head.row());
8889 let above_edit = edit_start
8890 .row()
8891 .0
8892 .checked_sub(line_count as u32)
8893 .map(DisplayRow);
8894 let below_edit = Some(edit_end.row() + 1);
8895 let above_cursor =
8896 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8897 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8898
8899 // Place the edit popover adjacent to the edit if there is a location
8900 // available that is onscreen and does not obscure the cursor. Otherwise,
8901 // place it adjacent to the cursor.
8902 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8903 .into_iter()
8904 .flatten()
8905 .find(|&start_row| {
8906 let end_row = start_row + line_count as u32;
8907 visible_row_range.contains(&start_row)
8908 && visible_row_range.contains(&end_row)
8909 && cursor_row.map_or(true, |cursor_row| {
8910 !((start_row..end_row).contains(&cursor_row))
8911 })
8912 })?;
8913
8914 content_origin
8915 + point(
8916 -scroll_pixel_position.x,
8917 row_target.as_f32() * line_height - scroll_pixel_position.y,
8918 )
8919 };
8920
8921 origin.x -= BORDER_WIDTH;
8922
8923 window.defer_draw(element, origin, 1);
8924
8925 // Do not return an element, since it will already be drawn due to defer_draw.
8926 None
8927 }
8928
8929 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8930 px(30.)
8931 }
8932
8933 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8934 if self.read_only(cx) {
8935 cx.theme().players().read_only()
8936 } else {
8937 self.style.as_ref().unwrap().local_player
8938 }
8939 }
8940
8941 fn render_edit_prediction_accept_keybind(
8942 &self,
8943 window: &mut Window,
8944 cx: &App,
8945 ) -> Option<AnyElement> {
8946 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8947 let accept_keystroke = accept_binding.keystroke()?;
8948
8949 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8950
8951 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8952 Color::Accent
8953 } else {
8954 Color::Muted
8955 };
8956
8957 h_flex()
8958 .px_0p5()
8959 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8960 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8961 .text_size(TextSize::XSmall.rems(cx))
8962 .child(h_flex().children(ui::render_modifiers(
8963 &accept_keystroke.modifiers,
8964 PlatformStyle::platform(),
8965 Some(modifiers_color),
8966 Some(IconSize::XSmall.rems().into()),
8967 true,
8968 )))
8969 .when(is_platform_style_mac, |parent| {
8970 parent.child(accept_keystroke.key.clone())
8971 })
8972 .when(!is_platform_style_mac, |parent| {
8973 parent.child(
8974 Key::new(
8975 util::capitalize(&accept_keystroke.key),
8976 Some(Color::Default),
8977 )
8978 .size(Some(IconSize::XSmall.rems().into())),
8979 )
8980 })
8981 .into_any()
8982 .into()
8983 }
8984
8985 fn render_edit_prediction_line_popover(
8986 &self,
8987 label: impl Into<SharedString>,
8988 icon: Option<IconName>,
8989 window: &mut Window,
8990 cx: &App,
8991 ) -> Option<Stateful<Div>> {
8992 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8993
8994 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8995 let has_keybind = keybind.is_some();
8996
8997 let result = h_flex()
8998 .id("ep-line-popover")
8999 .py_0p5()
9000 .pl_1()
9001 .pr(padding_right)
9002 .gap_1()
9003 .rounded_md()
9004 .border_1()
9005 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9006 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9007 .shadow_xs()
9008 .when(!has_keybind, |el| {
9009 let status_colors = cx.theme().status();
9010
9011 el.bg(status_colors.error_background)
9012 .border_color(status_colors.error.opacity(0.6))
9013 .pl_2()
9014 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9015 .cursor_default()
9016 .hoverable_tooltip(move |_window, cx| {
9017 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9018 })
9019 })
9020 .children(keybind)
9021 .child(
9022 Label::new(label)
9023 .size(LabelSize::Small)
9024 .when(!has_keybind, |el| {
9025 el.color(cx.theme().status().error.into()).strikethrough()
9026 }),
9027 )
9028 .when(!has_keybind, |el| {
9029 el.child(
9030 h_flex().ml_1().child(
9031 Icon::new(IconName::Info)
9032 .size(IconSize::Small)
9033 .color(cx.theme().status().error.into()),
9034 ),
9035 )
9036 })
9037 .when_some(icon, |element, icon| {
9038 element.child(
9039 div()
9040 .mt(px(1.5))
9041 .child(Icon::new(icon).size(IconSize::Small)),
9042 )
9043 });
9044
9045 Some(result)
9046 }
9047
9048 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9049 let accent_color = cx.theme().colors().text_accent;
9050 let editor_bg_color = cx.theme().colors().editor_background;
9051 editor_bg_color.blend(accent_color.opacity(0.1))
9052 }
9053
9054 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9055 let accent_color = cx.theme().colors().text_accent;
9056 let editor_bg_color = cx.theme().colors().editor_background;
9057 editor_bg_color.blend(accent_color.opacity(0.6))
9058 }
9059
9060 fn render_edit_prediction_cursor_popover(
9061 &self,
9062 min_width: Pixels,
9063 max_width: Pixels,
9064 cursor_point: Point,
9065 style: &EditorStyle,
9066 accept_keystroke: Option<&gpui::Keystroke>,
9067 _window: &Window,
9068 cx: &mut Context<Editor>,
9069 ) -> Option<AnyElement> {
9070 let provider = self.edit_prediction_provider.as_ref()?;
9071
9072 if provider.provider.needs_terms_acceptance(cx) {
9073 return Some(
9074 h_flex()
9075 .min_w(min_width)
9076 .flex_1()
9077 .px_2()
9078 .py_1()
9079 .gap_3()
9080 .elevation_2(cx)
9081 .hover(|style| style.bg(cx.theme().colors().element_hover))
9082 .id("accept-terms")
9083 .cursor_pointer()
9084 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9085 .on_click(cx.listener(|this, _event, window, cx| {
9086 cx.stop_propagation();
9087 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9088 window.dispatch_action(
9089 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9090 cx,
9091 );
9092 }))
9093 .child(
9094 h_flex()
9095 .flex_1()
9096 .gap_2()
9097 .child(Icon::new(IconName::ZedPredict))
9098 .child(Label::new("Accept Terms of Service"))
9099 .child(div().w_full())
9100 .child(
9101 Icon::new(IconName::ArrowUpRight)
9102 .color(Color::Muted)
9103 .size(IconSize::Small),
9104 )
9105 .into_any_element(),
9106 )
9107 .into_any(),
9108 );
9109 }
9110
9111 let is_refreshing = provider.provider.is_refreshing(cx);
9112
9113 fn pending_completion_container() -> Div {
9114 h_flex()
9115 .h_full()
9116 .flex_1()
9117 .gap_2()
9118 .child(Icon::new(IconName::ZedPredict))
9119 }
9120
9121 let completion = match &self.active_inline_completion {
9122 Some(prediction) => {
9123 if !self.has_visible_completions_menu() {
9124 const RADIUS: Pixels = px(6.);
9125 const BORDER_WIDTH: Pixels = px(1.);
9126
9127 return Some(
9128 h_flex()
9129 .elevation_2(cx)
9130 .border(BORDER_WIDTH)
9131 .border_color(cx.theme().colors().border)
9132 .when(accept_keystroke.is_none(), |el| {
9133 el.border_color(cx.theme().status().error)
9134 })
9135 .rounded(RADIUS)
9136 .rounded_tl(px(0.))
9137 .overflow_hidden()
9138 .child(div().px_1p5().child(match &prediction.completion {
9139 InlineCompletion::Move { target, snapshot } => {
9140 use text::ToPoint as _;
9141 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9142 {
9143 Icon::new(IconName::ZedPredictDown)
9144 } else {
9145 Icon::new(IconName::ZedPredictUp)
9146 }
9147 }
9148 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9149 }))
9150 .child(
9151 h_flex()
9152 .gap_1()
9153 .py_1()
9154 .px_2()
9155 .rounded_r(RADIUS - BORDER_WIDTH)
9156 .border_l_1()
9157 .border_color(cx.theme().colors().border)
9158 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9159 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9160 el.child(
9161 Label::new("Hold")
9162 .size(LabelSize::Small)
9163 .when(accept_keystroke.is_none(), |el| {
9164 el.strikethrough()
9165 })
9166 .line_height_style(LineHeightStyle::UiLabel),
9167 )
9168 })
9169 .id("edit_prediction_cursor_popover_keybind")
9170 .when(accept_keystroke.is_none(), |el| {
9171 let status_colors = cx.theme().status();
9172
9173 el.bg(status_colors.error_background)
9174 .border_color(status_colors.error.opacity(0.6))
9175 .child(Icon::new(IconName::Info).color(Color::Error))
9176 .cursor_default()
9177 .hoverable_tooltip(move |_window, cx| {
9178 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9179 .into()
9180 })
9181 })
9182 .when_some(
9183 accept_keystroke.as_ref(),
9184 |el, accept_keystroke| {
9185 el.child(h_flex().children(ui::render_modifiers(
9186 &accept_keystroke.modifiers,
9187 PlatformStyle::platform(),
9188 Some(Color::Default),
9189 Some(IconSize::XSmall.rems().into()),
9190 false,
9191 )))
9192 },
9193 ),
9194 )
9195 .into_any(),
9196 );
9197 }
9198
9199 self.render_edit_prediction_cursor_popover_preview(
9200 prediction,
9201 cursor_point,
9202 style,
9203 cx,
9204 )?
9205 }
9206
9207 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9208 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9209 stale_completion,
9210 cursor_point,
9211 style,
9212 cx,
9213 )?,
9214
9215 None => {
9216 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9217 }
9218 },
9219
9220 None => pending_completion_container().child(Label::new("No Prediction")),
9221 };
9222
9223 let completion = if is_refreshing {
9224 completion
9225 .with_animation(
9226 "loading-completion",
9227 Animation::new(Duration::from_secs(2))
9228 .repeat()
9229 .with_easing(pulsating_between(0.4, 0.8)),
9230 |label, delta| label.opacity(delta),
9231 )
9232 .into_any_element()
9233 } else {
9234 completion.into_any_element()
9235 };
9236
9237 let has_completion = self.active_inline_completion.is_some();
9238
9239 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9240 Some(
9241 h_flex()
9242 .min_w(min_width)
9243 .max_w(max_width)
9244 .flex_1()
9245 .elevation_2(cx)
9246 .border_color(cx.theme().colors().border)
9247 .child(
9248 div()
9249 .flex_1()
9250 .py_1()
9251 .px_2()
9252 .overflow_hidden()
9253 .child(completion),
9254 )
9255 .when_some(accept_keystroke, |el, accept_keystroke| {
9256 if !accept_keystroke.modifiers.modified() {
9257 return el;
9258 }
9259
9260 el.child(
9261 h_flex()
9262 .h_full()
9263 .border_l_1()
9264 .rounded_r_lg()
9265 .border_color(cx.theme().colors().border)
9266 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9267 .gap_1()
9268 .py_1()
9269 .px_2()
9270 .child(
9271 h_flex()
9272 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9273 .when(is_platform_style_mac, |parent| parent.gap_1())
9274 .child(h_flex().children(ui::render_modifiers(
9275 &accept_keystroke.modifiers,
9276 PlatformStyle::platform(),
9277 Some(if !has_completion {
9278 Color::Muted
9279 } else {
9280 Color::Default
9281 }),
9282 None,
9283 false,
9284 ))),
9285 )
9286 .child(Label::new("Preview").into_any_element())
9287 .opacity(if has_completion { 1.0 } else { 0.4 }),
9288 )
9289 })
9290 .into_any(),
9291 )
9292 }
9293
9294 fn render_edit_prediction_cursor_popover_preview(
9295 &self,
9296 completion: &InlineCompletionState,
9297 cursor_point: Point,
9298 style: &EditorStyle,
9299 cx: &mut Context<Editor>,
9300 ) -> Option<Div> {
9301 use text::ToPoint as _;
9302
9303 fn render_relative_row_jump(
9304 prefix: impl Into<String>,
9305 current_row: u32,
9306 target_row: u32,
9307 ) -> Div {
9308 let (row_diff, arrow) = if target_row < current_row {
9309 (current_row - target_row, IconName::ArrowUp)
9310 } else {
9311 (target_row - current_row, IconName::ArrowDown)
9312 };
9313
9314 h_flex()
9315 .child(
9316 Label::new(format!("{}{}", prefix.into(), row_diff))
9317 .color(Color::Muted)
9318 .size(LabelSize::Small),
9319 )
9320 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9321 }
9322
9323 match &completion.completion {
9324 InlineCompletion::Move {
9325 target, snapshot, ..
9326 } => Some(
9327 h_flex()
9328 .px_2()
9329 .gap_2()
9330 .flex_1()
9331 .child(
9332 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9333 Icon::new(IconName::ZedPredictDown)
9334 } else {
9335 Icon::new(IconName::ZedPredictUp)
9336 },
9337 )
9338 .child(Label::new("Jump to Edit")),
9339 ),
9340
9341 InlineCompletion::Edit {
9342 edits,
9343 edit_preview,
9344 snapshot,
9345 display_mode: _,
9346 } => {
9347 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9348
9349 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9350 &snapshot,
9351 &edits,
9352 edit_preview.as_ref()?,
9353 true,
9354 cx,
9355 )
9356 .first_line_preview();
9357
9358 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9359 .with_default_highlights(&style.text, highlighted_edits.highlights);
9360
9361 let preview = h_flex()
9362 .gap_1()
9363 .min_w_16()
9364 .child(styled_text)
9365 .when(has_more_lines, |parent| parent.child("…"));
9366
9367 let left = if first_edit_row != cursor_point.row {
9368 render_relative_row_jump("", cursor_point.row, first_edit_row)
9369 .into_any_element()
9370 } else {
9371 Icon::new(IconName::ZedPredict).into_any_element()
9372 };
9373
9374 Some(
9375 h_flex()
9376 .h_full()
9377 .flex_1()
9378 .gap_2()
9379 .pr_1()
9380 .overflow_x_hidden()
9381 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9382 .child(left)
9383 .child(preview),
9384 )
9385 }
9386 }
9387 }
9388
9389 pub fn render_context_menu(
9390 &self,
9391 style: &EditorStyle,
9392 max_height_in_lines: u32,
9393 window: &mut Window,
9394 cx: &mut Context<Editor>,
9395 ) -> Option<AnyElement> {
9396 let menu = self.context_menu.borrow();
9397 let menu = menu.as_ref()?;
9398 if !menu.visible() {
9399 return None;
9400 };
9401 Some(menu.render(style, max_height_in_lines, window, cx))
9402 }
9403
9404 fn render_context_menu_aside(
9405 &mut self,
9406 max_size: Size<Pixels>,
9407 window: &mut Window,
9408 cx: &mut Context<Editor>,
9409 ) -> Option<AnyElement> {
9410 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9411 if menu.visible() {
9412 menu.render_aside(max_size, window, cx)
9413 } else {
9414 None
9415 }
9416 })
9417 }
9418
9419 fn hide_context_menu(
9420 &mut self,
9421 window: &mut Window,
9422 cx: &mut Context<Self>,
9423 ) -> Option<CodeContextMenu> {
9424 cx.notify();
9425 self.completion_tasks.clear();
9426 let context_menu = self.context_menu.borrow_mut().take();
9427 self.stale_inline_completion_in_menu.take();
9428 self.update_visible_inline_completion(window, cx);
9429 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9430 if let Some(completion_provider) = &self.completion_provider {
9431 completion_provider.selection_changed(None, window, cx);
9432 }
9433 }
9434 context_menu
9435 }
9436
9437 fn show_snippet_choices(
9438 &mut self,
9439 choices: &Vec<String>,
9440 selection: Range<Anchor>,
9441 cx: &mut Context<Self>,
9442 ) {
9443 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9444 (Some(a), Some(b)) if a == b => a,
9445 _ => {
9446 log::error!("expected anchor range to have matching buffer IDs");
9447 return;
9448 }
9449 };
9450 let multi_buffer = self.buffer().read(cx);
9451 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9452 return;
9453 };
9454
9455 let id = post_inc(&mut self.next_completion_id);
9456 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9457 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9458 CompletionsMenu::new_snippet_choices(
9459 id,
9460 true,
9461 choices,
9462 selection,
9463 buffer,
9464 snippet_sort_order,
9465 ),
9466 ));
9467 }
9468
9469 pub fn insert_snippet(
9470 &mut self,
9471 insertion_ranges: &[Range<usize>],
9472 snippet: Snippet,
9473 window: &mut Window,
9474 cx: &mut Context<Self>,
9475 ) -> Result<()> {
9476 struct Tabstop<T> {
9477 is_end_tabstop: bool,
9478 ranges: Vec<Range<T>>,
9479 choices: Option<Vec<String>>,
9480 }
9481
9482 let tabstops = self.buffer.update(cx, |buffer, cx| {
9483 let snippet_text: Arc<str> = snippet.text.clone().into();
9484 let edits = insertion_ranges
9485 .iter()
9486 .cloned()
9487 .map(|range| (range, snippet_text.clone()));
9488 let autoindent_mode = AutoindentMode::Block {
9489 original_indent_columns: Vec::new(),
9490 };
9491 buffer.edit(edits, Some(autoindent_mode), cx);
9492
9493 let snapshot = &*buffer.read(cx);
9494 let snippet = &snippet;
9495 snippet
9496 .tabstops
9497 .iter()
9498 .map(|tabstop| {
9499 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9500 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9501 });
9502 let mut tabstop_ranges = tabstop
9503 .ranges
9504 .iter()
9505 .flat_map(|tabstop_range| {
9506 let mut delta = 0_isize;
9507 insertion_ranges.iter().map(move |insertion_range| {
9508 let insertion_start = insertion_range.start as isize + delta;
9509 delta +=
9510 snippet.text.len() as isize - insertion_range.len() as isize;
9511
9512 let start = ((insertion_start + tabstop_range.start) as usize)
9513 .min(snapshot.len());
9514 let end = ((insertion_start + tabstop_range.end) as usize)
9515 .min(snapshot.len());
9516 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9517 })
9518 })
9519 .collect::<Vec<_>>();
9520 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9521
9522 Tabstop {
9523 is_end_tabstop,
9524 ranges: tabstop_ranges,
9525 choices: tabstop.choices.clone(),
9526 }
9527 })
9528 .collect::<Vec<_>>()
9529 });
9530 if let Some(tabstop) = tabstops.first() {
9531 self.change_selections(Default::default(), window, cx, |s| {
9532 // Reverse order so that the first range is the newest created selection.
9533 // Completions will use it and autoscroll will prioritize it.
9534 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9535 });
9536
9537 if let Some(choices) = &tabstop.choices {
9538 if let Some(selection) = tabstop.ranges.first() {
9539 self.show_snippet_choices(choices, selection.clone(), cx)
9540 }
9541 }
9542
9543 // If we're already at the last tabstop and it's at the end of the snippet,
9544 // we're done, we don't need to keep the state around.
9545 if !tabstop.is_end_tabstop {
9546 let choices = tabstops
9547 .iter()
9548 .map(|tabstop| tabstop.choices.clone())
9549 .collect();
9550
9551 let ranges = tabstops
9552 .into_iter()
9553 .map(|tabstop| tabstop.ranges)
9554 .collect::<Vec<_>>();
9555
9556 self.snippet_stack.push(SnippetState {
9557 active_index: 0,
9558 ranges,
9559 choices,
9560 });
9561 }
9562
9563 // Check whether the just-entered snippet ends with an auto-closable bracket.
9564 if self.autoclose_regions.is_empty() {
9565 let snapshot = self.buffer.read(cx).snapshot(cx);
9566 for selection in &mut self.selections.all::<Point>(cx) {
9567 let selection_head = selection.head();
9568 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9569 continue;
9570 };
9571
9572 let mut bracket_pair = None;
9573 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9574 let prev_chars = snapshot
9575 .reversed_chars_at(selection_head)
9576 .collect::<String>();
9577 for (pair, enabled) in scope.brackets() {
9578 if enabled
9579 && pair.close
9580 && prev_chars.starts_with(pair.start.as_str())
9581 && next_chars.starts_with(pair.end.as_str())
9582 {
9583 bracket_pair = Some(pair.clone());
9584 break;
9585 }
9586 }
9587 if let Some(pair) = bracket_pair {
9588 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9589 let autoclose_enabled =
9590 self.use_autoclose && snapshot_settings.use_autoclose;
9591 if autoclose_enabled {
9592 let start = snapshot.anchor_after(selection_head);
9593 let end = snapshot.anchor_after(selection_head);
9594 self.autoclose_regions.push(AutocloseRegion {
9595 selection_id: selection.id,
9596 range: start..end,
9597 pair,
9598 });
9599 }
9600 }
9601 }
9602 }
9603 }
9604 Ok(())
9605 }
9606
9607 pub fn move_to_next_snippet_tabstop(
9608 &mut self,
9609 window: &mut Window,
9610 cx: &mut Context<Self>,
9611 ) -> bool {
9612 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9613 }
9614
9615 pub fn move_to_prev_snippet_tabstop(
9616 &mut self,
9617 window: &mut Window,
9618 cx: &mut Context<Self>,
9619 ) -> bool {
9620 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9621 }
9622
9623 pub fn move_to_snippet_tabstop(
9624 &mut self,
9625 bias: Bias,
9626 window: &mut Window,
9627 cx: &mut Context<Self>,
9628 ) -> bool {
9629 if let Some(mut snippet) = self.snippet_stack.pop() {
9630 match bias {
9631 Bias::Left => {
9632 if snippet.active_index > 0 {
9633 snippet.active_index -= 1;
9634 } else {
9635 self.snippet_stack.push(snippet);
9636 return false;
9637 }
9638 }
9639 Bias::Right => {
9640 if snippet.active_index + 1 < snippet.ranges.len() {
9641 snippet.active_index += 1;
9642 } else {
9643 self.snippet_stack.push(snippet);
9644 return false;
9645 }
9646 }
9647 }
9648 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9649 self.change_selections(Default::default(), window, cx, |s| {
9650 // Reverse order so that the first range is the newest created selection.
9651 // Completions will use it and autoscroll will prioritize it.
9652 s.select_ranges(current_ranges.iter().rev().cloned())
9653 });
9654
9655 if let Some(choices) = &snippet.choices[snippet.active_index] {
9656 if let Some(selection) = current_ranges.first() {
9657 self.show_snippet_choices(&choices, selection.clone(), cx);
9658 }
9659 }
9660
9661 // If snippet state is not at the last tabstop, push it back on the stack
9662 if snippet.active_index + 1 < snippet.ranges.len() {
9663 self.snippet_stack.push(snippet);
9664 }
9665 return true;
9666 }
9667 }
9668
9669 false
9670 }
9671
9672 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9673 self.transact(window, cx, |this, window, cx| {
9674 this.select_all(&SelectAll, window, cx);
9675 this.insert("", window, cx);
9676 });
9677 }
9678
9679 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9681 self.transact(window, cx, |this, window, cx| {
9682 this.select_autoclose_pair(window, cx);
9683 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9684 if !this.linked_edit_ranges.is_empty() {
9685 let selections = this.selections.all::<MultiBufferPoint>(cx);
9686 let snapshot = this.buffer.read(cx).snapshot(cx);
9687
9688 for selection in selections.iter() {
9689 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9690 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9691 if selection_start.buffer_id != selection_end.buffer_id {
9692 continue;
9693 }
9694 if let Some(ranges) =
9695 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9696 {
9697 for (buffer, entries) in ranges {
9698 linked_ranges.entry(buffer).or_default().extend(entries);
9699 }
9700 }
9701 }
9702 }
9703
9704 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9705 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9706 for selection in &mut selections {
9707 if selection.is_empty() {
9708 let old_head = selection.head();
9709 let mut new_head =
9710 movement::left(&display_map, old_head.to_display_point(&display_map))
9711 .to_point(&display_map);
9712 if let Some((buffer, line_buffer_range)) = display_map
9713 .buffer_snapshot
9714 .buffer_line_for_row(MultiBufferRow(old_head.row))
9715 {
9716 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9717 let indent_len = match indent_size.kind {
9718 IndentKind::Space => {
9719 buffer.settings_at(line_buffer_range.start, cx).tab_size
9720 }
9721 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9722 };
9723 if old_head.column <= indent_size.len && old_head.column > 0 {
9724 let indent_len = indent_len.get();
9725 new_head = cmp::min(
9726 new_head,
9727 MultiBufferPoint::new(
9728 old_head.row,
9729 ((old_head.column - 1) / indent_len) * indent_len,
9730 ),
9731 );
9732 }
9733 }
9734
9735 selection.set_head(new_head, SelectionGoal::None);
9736 }
9737 }
9738
9739 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9740 this.insert("", window, cx);
9741 let empty_str: Arc<str> = Arc::from("");
9742 for (buffer, edits) in linked_ranges {
9743 let snapshot = buffer.read(cx).snapshot();
9744 use text::ToPoint as TP;
9745
9746 let edits = edits
9747 .into_iter()
9748 .map(|range| {
9749 let end_point = TP::to_point(&range.end, &snapshot);
9750 let mut start_point = TP::to_point(&range.start, &snapshot);
9751
9752 if end_point == start_point {
9753 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9754 .saturating_sub(1);
9755 start_point =
9756 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9757 };
9758
9759 (start_point..end_point, empty_str.clone())
9760 })
9761 .sorted_by_key(|(range, _)| range.start)
9762 .collect::<Vec<_>>();
9763 buffer.update(cx, |this, cx| {
9764 this.edit(edits, None, cx);
9765 })
9766 }
9767 this.refresh_inline_completion(true, false, window, cx);
9768 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9769 });
9770 }
9771
9772 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9774 self.transact(window, cx, |this, window, cx| {
9775 this.change_selections(Default::default(), window, cx, |s| {
9776 s.move_with(|map, selection| {
9777 if selection.is_empty() {
9778 let cursor = movement::right(map, selection.head());
9779 selection.end = cursor;
9780 selection.reversed = true;
9781 selection.goal = SelectionGoal::None;
9782 }
9783 })
9784 });
9785 this.insert("", window, cx);
9786 this.refresh_inline_completion(true, false, window, cx);
9787 });
9788 }
9789
9790 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9791 if self.mode.is_single_line() {
9792 cx.propagate();
9793 return;
9794 }
9795
9796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9797 if self.move_to_prev_snippet_tabstop(window, cx) {
9798 return;
9799 }
9800 self.outdent(&Outdent, window, cx);
9801 }
9802
9803 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9804 if self.mode.is_single_line() {
9805 cx.propagate();
9806 return;
9807 }
9808
9809 if self.move_to_next_snippet_tabstop(window, cx) {
9810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9811 return;
9812 }
9813 if self.read_only(cx) {
9814 return;
9815 }
9816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9817 let mut selections = self.selections.all_adjusted(cx);
9818 let buffer = self.buffer.read(cx);
9819 let snapshot = buffer.snapshot(cx);
9820 let rows_iter = selections.iter().map(|s| s.head().row);
9821 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9822
9823 let has_some_cursor_in_whitespace = selections
9824 .iter()
9825 .filter(|selection| selection.is_empty())
9826 .any(|selection| {
9827 let cursor = selection.head();
9828 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9829 cursor.column < current_indent.len
9830 });
9831
9832 let mut edits = Vec::new();
9833 let mut prev_edited_row = 0;
9834 let mut row_delta = 0;
9835 for selection in &mut selections {
9836 if selection.start.row != prev_edited_row {
9837 row_delta = 0;
9838 }
9839 prev_edited_row = selection.end.row;
9840
9841 // If the selection is non-empty, then increase the indentation of the selected lines.
9842 if !selection.is_empty() {
9843 row_delta =
9844 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9845 continue;
9846 }
9847
9848 let cursor = selection.head();
9849 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9850 if let Some(suggested_indent) =
9851 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9852 {
9853 // Don't do anything if already at suggested indent
9854 // and there is any other cursor which is not
9855 if has_some_cursor_in_whitespace
9856 && cursor.column == current_indent.len
9857 && current_indent.len == suggested_indent.len
9858 {
9859 continue;
9860 }
9861
9862 // Adjust line and move cursor to suggested indent
9863 // if cursor is not at suggested indent
9864 if cursor.column < suggested_indent.len
9865 && cursor.column <= current_indent.len
9866 && current_indent.len <= suggested_indent.len
9867 {
9868 selection.start = Point::new(cursor.row, suggested_indent.len);
9869 selection.end = selection.start;
9870 if row_delta == 0 {
9871 edits.extend(Buffer::edit_for_indent_size_adjustment(
9872 cursor.row,
9873 current_indent,
9874 suggested_indent,
9875 ));
9876 row_delta = suggested_indent.len - current_indent.len;
9877 }
9878 continue;
9879 }
9880
9881 // If current indent is more than suggested indent
9882 // only move cursor to current indent and skip indent
9883 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9884 selection.start = Point::new(cursor.row, current_indent.len);
9885 selection.end = selection.start;
9886 continue;
9887 }
9888 }
9889
9890 // Otherwise, insert a hard or soft tab.
9891 let settings = buffer.language_settings_at(cursor, cx);
9892 let tab_size = if settings.hard_tabs {
9893 IndentSize::tab()
9894 } else {
9895 let tab_size = settings.tab_size.get();
9896 let indent_remainder = snapshot
9897 .text_for_range(Point::new(cursor.row, 0)..cursor)
9898 .flat_map(str::chars)
9899 .fold(row_delta % tab_size, |counter: u32, c| {
9900 if c == '\t' {
9901 0
9902 } else {
9903 (counter + 1) % tab_size
9904 }
9905 });
9906
9907 let chars_to_next_tab_stop = tab_size - indent_remainder;
9908 IndentSize::spaces(chars_to_next_tab_stop)
9909 };
9910 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9911 selection.end = selection.start;
9912 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9913 row_delta += tab_size.len;
9914 }
9915
9916 self.transact(window, cx, |this, window, cx| {
9917 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9918 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9919 this.refresh_inline_completion(true, false, window, cx);
9920 });
9921 }
9922
9923 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9924 if self.read_only(cx) {
9925 return;
9926 }
9927 if self.mode.is_single_line() {
9928 cx.propagate();
9929 return;
9930 }
9931
9932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9933 let mut selections = self.selections.all::<Point>(cx);
9934 let mut prev_edited_row = 0;
9935 let mut row_delta = 0;
9936 let mut edits = Vec::new();
9937 let buffer = self.buffer.read(cx);
9938 let snapshot = buffer.snapshot(cx);
9939 for selection in &mut selections {
9940 if selection.start.row != prev_edited_row {
9941 row_delta = 0;
9942 }
9943 prev_edited_row = selection.end.row;
9944
9945 row_delta =
9946 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9947 }
9948
9949 self.transact(window, cx, |this, window, cx| {
9950 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9951 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9952 });
9953 }
9954
9955 fn indent_selection(
9956 buffer: &MultiBuffer,
9957 snapshot: &MultiBufferSnapshot,
9958 selection: &mut Selection<Point>,
9959 edits: &mut Vec<(Range<Point>, String)>,
9960 delta_for_start_row: u32,
9961 cx: &App,
9962 ) -> u32 {
9963 let settings = buffer.language_settings_at(selection.start, cx);
9964 let tab_size = settings.tab_size.get();
9965 let indent_kind = if settings.hard_tabs {
9966 IndentKind::Tab
9967 } else {
9968 IndentKind::Space
9969 };
9970 let mut start_row = selection.start.row;
9971 let mut end_row = selection.end.row + 1;
9972
9973 // If a selection ends at the beginning of a line, don't indent
9974 // that last line.
9975 if selection.end.column == 0 && selection.end.row > selection.start.row {
9976 end_row -= 1;
9977 }
9978
9979 // Avoid re-indenting a row that has already been indented by a
9980 // previous selection, but still update this selection's column
9981 // to reflect that indentation.
9982 if delta_for_start_row > 0 {
9983 start_row += 1;
9984 selection.start.column += delta_for_start_row;
9985 if selection.end.row == selection.start.row {
9986 selection.end.column += delta_for_start_row;
9987 }
9988 }
9989
9990 let mut delta_for_end_row = 0;
9991 let has_multiple_rows = start_row + 1 != end_row;
9992 for row in start_row..end_row {
9993 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9994 let indent_delta = match (current_indent.kind, indent_kind) {
9995 (IndentKind::Space, IndentKind::Space) => {
9996 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9997 IndentSize::spaces(columns_to_next_tab_stop)
9998 }
9999 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10000 (_, IndentKind::Tab) => IndentSize::tab(),
10001 };
10002
10003 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10004 0
10005 } else {
10006 selection.start.column
10007 };
10008 let row_start = Point::new(row, start);
10009 edits.push((
10010 row_start..row_start,
10011 indent_delta.chars().collect::<String>(),
10012 ));
10013
10014 // Update this selection's endpoints to reflect the indentation.
10015 if row == selection.start.row {
10016 selection.start.column += indent_delta.len;
10017 }
10018 if row == selection.end.row {
10019 selection.end.column += indent_delta.len;
10020 delta_for_end_row = indent_delta.len;
10021 }
10022 }
10023
10024 if selection.start.row == selection.end.row {
10025 delta_for_start_row + delta_for_end_row
10026 } else {
10027 delta_for_end_row
10028 }
10029 }
10030
10031 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 if self.mode.is_single_line() {
10036 cx.propagate();
10037 return;
10038 }
10039
10040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10042 let selections = self.selections.all::<Point>(cx);
10043 let mut deletion_ranges = Vec::new();
10044 let mut last_outdent = None;
10045 {
10046 let buffer = self.buffer.read(cx);
10047 let snapshot = buffer.snapshot(cx);
10048 for selection in &selections {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let mut rows = selection.spanned_rows(false, &display_map);
10052
10053 // Avoid re-outdenting a row that has already been outdented by a
10054 // previous selection.
10055 if let Some(last_row) = last_outdent {
10056 if last_row == rows.start {
10057 rows.start = rows.start.next_row();
10058 }
10059 }
10060 let has_multiple_rows = rows.len() > 1;
10061 for row in rows.iter_rows() {
10062 let indent_size = snapshot.indent_size_for_line(row);
10063 if indent_size.len > 0 {
10064 let deletion_len = match indent_size.kind {
10065 IndentKind::Space => {
10066 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10067 if columns_to_prev_tab_stop == 0 {
10068 tab_size
10069 } else {
10070 columns_to_prev_tab_stop
10071 }
10072 }
10073 IndentKind::Tab => 1,
10074 };
10075 let start = if has_multiple_rows
10076 || deletion_len > selection.start.column
10077 || indent_size.len < selection.start.column
10078 {
10079 0
10080 } else {
10081 selection.start.column - deletion_len
10082 };
10083 deletion_ranges.push(
10084 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10085 );
10086 last_outdent = Some(row);
10087 }
10088 }
10089 }
10090 }
10091
10092 self.transact(window, cx, |this, window, cx| {
10093 this.buffer.update(cx, |buffer, cx| {
10094 let empty_str: Arc<str> = Arc::default();
10095 buffer.edit(
10096 deletion_ranges
10097 .into_iter()
10098 .map(|range| (range, empty_str.clone())),
10099 None,
10100 cx,
10101 );
10102 });
10103 let selections = this.selections.all::<usize>(cx);
10104 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10105 });
10106 }
10107
10108 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10109 if self.read_only(cx) {
10110 return;
10111 }
10112 if self.mode.is_single_line() {
10113 cx.propagate();
10114 return;
10115 }
10116
10117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10118 let selections = self
10119 .selections
10120 .all::<usize>(cx)
10121 .into_iter()
10122 .map(|s| s.range());
10123
10124 self.transact(window, cx, |this, window, cx| {
10125 this.buffer.update(cx, |buffer, cx| {
10126 buffer.autoindent_ranges(selections, cx);
10127 });
10128 let selections = this.selections.all::<usize>(cx);
10129 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10130 });
10131 }
10132
10133 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10135 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10136 let selections = self.selections.all::<Point>(cx);
10137
10138 let mut new_cursors = Vec::new();
10139 let mut edit_ranges = Vec::new();
10140 let mut selections = selections.iter().peekable();
10141 while let Some(selection) = selections.next() {
10142 let mut rows = selection.spanned_rows(false, &display_map);
10143 let goal_display_column = selection.head().to_display_point(&display_map).column();
10144
10145 // Accumulate contiguous regions of rows that we want to delete.
10146 while let Some(next_selection) = selections.peek() {
10147 let next_rows = next_selection.spanned_rows(false, &display_map);
10148 if next_rows.start <= rows.end {
10149 rows.end = next_rows.end;
10150 selections.next().unwrap();
10151 } else {
10152 break;
10153 }
10154 }
10155
10156 let buffer = &display_map.buffer_snapshot;
10157 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10158 let edit_end;
10159 let cursor_buffer_row;
10160 if buffer.max_point().row >= rows.end.0 {
10161 // If there's a line after the range, delete the \n from the end of the row range
10162 // and position the cursor on the next line.
10163 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10164 cursor_buffer_row = rows.end;
10165 } else {
10166 // If there isn't a line after the range, delete the \n from the line before the
10167 // start of the row range and position the cursor there.
10168 edit_start = edit_start.saturating_sub(1);
10169 edit_end = buffer.len();
10170 cursor_buffer_row = rows.start.previous_row();
10171 }
10172
10173 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10174 *cursor.column_mut() =
10175 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10176
10177 new_cursors.push((
10178 selection.id,
10179 buffer.anchor_after(cursor.to_point(&display_map)),
10180 ));
10181 edit_ranges.push(edit_start..edit_end);
10182 }
10183
10184 self.transact(window, cx, |this, window, cx| {
10185 let buffer = this.buffer.update(cx, |buffer, cx| {
10186 let empty_str: Arc<str> = Arc::default();
10187 buffer.edit(
10188 edit_ranges
10189 .into_iter()
10190 .map(|range| (range, empty_str.clone())),
10191 None,
10192 cx,
10193 );
10194 buffer.snapshot(cx)
10195 });
10196 let new_selections = new_cursors
10197 .into_iter()
10198 .map(|(id, cursor)| {
10199 let cursor = cursor.to_point(&buffer);
10200 Selection {
10201 id,
10202 start: cursor,
10203 end: cursor,
10204 reversed: false,
10205 goal: SelectionGoal::None,
10206 }
10207 })
10208 .collect();
10209
10210 this.change_selections(Default::default(), window, cx, |s| {
10211 s.select(new_selections);
10212 });
10213 });
10214 }
10215
10216 pub fn join_lines_impl(
10217 &mut self,
10218 insert_whitespace: bool,
10219 window: &mut Window,
10220 cx: &mut Context<Self>,
10221 ) {
10222 if self.read_only(cx) {
10223 return;
10224 }
10225 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10226 for selection in self.selections.all::<Point>(cx) {
10227 let start = MultiBufferRow(selection.start.row);
10228 // Treat single line selections as if they include the next line. Otherwise this action
10229 // would do nothing for single line selections individual cursors.
10230 let end = if selection.start.row == selection.end.row {
10231 MultiBufferRow(selection.start.row + 1)
10232 } else {
10233 MultiBufferRow(selection.end.row)
10234 };
10235
10236 if let Some(last_row_range) = row_ranges.last_mut() {
10237 if start <= last_row_range.end {
10238 last_row_range.end = end;
10239 continue;
10240 }
10241 }
10242 row_ranges.push(start..end);
10243 }
10244
10245 let snapshot = self.buffer.read(cx).snapshot(cx);
10246 let mut cursor_positions = Vec::new();
10247 for row_range in &row_ranges {
10248 let anchor = snapshot.anchor_before(Point::new(
10249 row_range.end.previous_row().0,
10250 snapshot.line_len(row_range.end.previous_row()),
10251 ));
10252 cursor_positions.push(anchor..anchor);
10253 }
10254
10255 self.transact(window, cx, |this, window, cx| {
10256 for row_range in row_ranges.into_iter().rev() {
10257 for row in row_range.iter_rows().rev() {
10258 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10259 let next_line_row = row.next_row();
10260 let indent = snapshot.indent_size_for_line(next_line_row);
10261 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10262
10263 let replace =
10264 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10265 " "
10266 } else {
10267 ""
10268 };
10269
10270 this.buffer.update(cx, |buffer, cx| {
10271 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10272 });
10273 }
10274 }
10275
10276 this.change_selections(Default::default(), window, cx, |s| {
10277 s.select_anchor_ranges(cursor_positions)
10278 });
10279 });
10280 }
10281
10282 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10283 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10284 self.join_lines_impl(true, window, cx);
10285 }
10286
10287 pub fn sort_lines_case_sensitive(
10288 &mut self,
10289 _: &SortLinesCaseSensitive,
10290 window: &mut Window,
10291 cx: &mut Context<Self>,
10292 ) {
10293 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10294 }
10295
10296 pub fn sort_lines_by_length(
10297 &mut self,
10298 _: &SortLinesByLength,
10299 window: &mut Window,
10300 cx: &mut Context<Self>,
10301 ) {
10302 self.manipulate_immutable_lines(window, cx, |lines| {
10303 lines.sort_by_key(|&line| line.chars().count())
10304 })
10305 }
10306
10307 pub fn sort_lines_case_insensitive(
10308 &mut self,
10309 _: &SortLinesCaseInsensitive,
10310 window: &mut Window,
10311 cx: &mut Context<Self>,
10312 ) {
10313 self.manipulate_immutable_lines(window, cx, |lines| {
10314 lines.sort_by_key(|line| line.to_lowercase())
10315 })
10316 }
10317
10318 pub fn unique_lines_case_insensitive(
10319 &mut self,
10320 _: &UniqueLinesCaseInsensitive,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 ) {
10324 self.manipulate_immutable_lines(window, cx, |lines| {
10325 let mut seen = HashSet::default();
10326 lines.retain(|line| seen.insert(line.to_lowercase()));
10327 })
10328 }
10329
10330 pub fn unique_lines_case_sensitive(
10331 &mut self,
10332 _: &UniqueLinesCaseSensitive,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 self.manipulate_immutable_lines(window, cx, |lines| {
10337 let mut seen = HashSet::default();
10338 lines.retain(|line| seen.insert(*line));
10339 })
10340 }
10341
10342 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10343 let Some(project) = self.project.clone() else {
10344 return;
10345 };
10346 self.reload(project, window, cx)
10347 .detach_and_notify_err(window, cx);
10348 }
10349
10350 pub fn restore_file(
10351 &mut self,
10352 _: &::git::RestoreFile,
10353 window: &mut Window,
10354 cx: &mut Context<Self>,
10355 ) {
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 let mut buffer_ids = HashSet::default();
10358 let snapshot = self.buffer().read(cx).snapshot(cx);
10359 for selection in self.selections.all::<usize>(cx) {
10360 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10361 }
10362
10363 let buffer = self.buffer().read(cx);
10364 let ranges = buffer_ids
10365 .into_iter()
10366 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10367 .collect::<Vec<_>>();
10368
10369 self.restore_hunks_in_ranges(ranges, window, cx);
10370 }
10371
10372 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10374 let selections = self
10375 .selections
10376 .all(cx)
10377 .into_iter()
10378 .map(|s| s.range())
10379 .collect();
10380 self.restore_hunks_in_ranges(selections, window, cx);
10381 }
10382
10383 pub fn restore_hunks_in_ranges(
10384 &mut self,
10385 ranges: Vec<Range<Point>>,
10386 window: &mut Window,
10387 cx: &mut Context<Editor>,
10388 ) {
10389 let mut revert_changes = HashMap::default();
10390 let chunk_by = self
10391 .snapshot(window, cx)
10392 .hunks_for_ranges(ranges)
10393 .into_iter()
10394 .chunk_by(|hunk| hunk.buffer_id);
10395 for (buffer_id, hunks) in &chunk_by {
10396 let hunks = hunks.collect::<Vec<_>>();
10397 for hunk in &hunks {
10398 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10399 }
10400 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10401 }
10402 drop(chunk_by);
10403 if !revert_changes.is_empty() {
10404 self.transact(window, cx, |editor, window, cx| {
10405 editor.restore(revert_changes, window, cx);
10406 });
10407 }
10408 }
10409
10410 pub fn open_active_item_in_terminal(
10411 &mut self,
10412 _: &OpenInTerminal,
10413 window: &mut Window,
10414 cx: &mut Context<Self>,
10415 ) {
10416 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10417 let project_path = buffer.read(cx).project_path(cx)?;
10418 let project = self.project.as_ref()?.read(cx);
10419 let entry = project.entry_for_path(&project_path, cx)?;
10420 let parent = match &entry.canonical_path {
10421 Some(canonical_path) => canonical_path.to_path_buf(),
10422 None => project.absolute_path(&project_path, cx)?,
10423 }
10424 .parent()?
10425 .to_path_buf();
10426 Some(parent)
10427 }) {
10428 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10429 }
10430 }
10431
10432 fn set_breakpoint_context_menu(
10433 &mut self,
10434 display_row: DisplayRow,
10435 position: Option<Anchor>,
10436 clicked_point: gpui::Point<Pixels>,
10437 window: &mut Window,
10438 cx: &mut Context<Self>,
10439 ) {
10440 let source = self
10441 .buffer
10442 .read(cx)
10443 .snapshot(cx)
10444 .anchor_before(Point::new(display_row.0, 0u32));
10445
10446 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10447
10448 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10449 self,
10450 source,
10451 clicked_point,
10452 context_menu,
10453 window,
10454 cx,
10455 );
10456 }
10457
10458 fn add_edit_breakpoint_block(
10459 &mut self,
10460 anchor: Anchor,
10461 breakpoint: &Breakpoint,
10462 edit_action: BreakpointPromptEditAction,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) {
10466 let weak_editor = cx.weak_entity();
10467 let bp_prompt = cx.new(|cx| {
10468 BreakpointPromptEditor::new(
10469 weak_editor,
10470 anchor,
10471 breakpoint.clone(),
10472 edit_action,
10473 window,
10474 cx,
10475 )
10476 });
10477
10478 let height = bp_prompt.update(cx, |this, cx| {
10479 this.prompt
10480 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10481 });
10482 let cloned_prompt = bp_prompt.clone();
10483 let blocks = vec![BlockProperties {
10484 style: BlockStyle::Sticky,
10485 placement: BlockPlacement::Above(anchor),
10486 height: Some(height),
10487 render: Arc::new(move |cx| {
10488 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10489 cloned_prompt.clone().into_any_element()
10490 }),
10491 priority: 0,
10492 }];
10493
10494 let focus_handle = bp_prompt.focus_handle(cx);
10495 window.focus(&focus_handle);
10496
10497 let block_ids = self.insert_blocks(blocks, None, cx);
10498 bp_prompt.update(cx, |prompt, _| {
10499 prompt.add_block_ids(block_ids);
10500 });
10501 }
10502
10503 pub(crate) fn breakpoint_at_row(
10504 &self,
10505 row: u32,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) -> Option<(Anchor, Breakpoint)> {
10509 let snapshot = self.snapshot(window, cx);
10510 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10511
10512 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10513 }
10514
10515 pub(crate) fn breakpoint_at_anchor(
10516 &self,
10517 breakpoint_position: Anchor,
10518 snapshot: &EditorSnapshot,
10519 cx: &mut Context<Self>,
10520 ) -> Option<(Anchor, Breakpoint)> {
10521 let project = self.project.clone()?;
10522
10523 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10524 snapshot
10525 .buffer_snapshot
10526 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10527 })?;
10528
10529 let enclosing_excerpt = breakpoint_position.excerpt_id;
10530 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10531 let buffer_snapshot = buffer.read(cx).snapshot();
10532
10533 let row = buffer_snapshot
10534 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10535 .row;
10536
10537 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10538 let anchor_end = snapshot
10539 .buffer_snapshot
10540 .anchor_after(Point::new(row, line_len));
10541
10542 let bp = self
10543 .breakpoint_store
10544 .as_ref()?
10545 .read_with(cx, |breakpoint_store, cx| {
10546 breakpoint_store
10547 .breakpoints(
10548 &buffer,
10549 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10550 &buffer_snapshot,
10551 cx,
10552 )
10553 .next()
10554 .and_then(|(bp, _)| {
10555 let breakpoint_row = buffer_snapshot
10556 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10557 .row;
10558
10559 if breakpoint_row == row {
10560 snapshot
10561 .buffer_snapshot
10562 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10563 .map(|position| (position, bp.bp.clone()))
10564 } else {
10565 None
10566 }
10567 })
10568 });
10569 bp
10570 }
10571
10572 pub fn edit_log_breakpoint(
10573 &mut self,
10574 _: &EditLogBreakpoint,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10579 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10580 message: None,
10581 state: BreakpointState::Enabled,
10582 condition: None,
10583 hit_condition: None,
10584 });
10585
10586 self.add_edit_breakpoint_block(
10587 anchor,
10588 &breakpoint,
10589 BreakpointPromptEditAction::Log,
10590 window,
10591 cx,
10592 );
10593 }
10594 }
10595
10596 fn breakpoints_at_cursors(
10597 &self,
10598 window: &mut Window,
10599 cx: &mut Context<Self>,
10600 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10601 let snapshot = self.snapshot(window, cx);
10602 let cursors = self
10603 .selections
10604 .disjoint_anchors()
10605 .into_iter()
10606 .map(|selection| {
10607 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10608
10609 let breakpoint_position = self
10610 .breakpoint_at_row(cursor_position.row, window, cx)
10611 .map(|bp| bp.0)
10612 .unwrap_or_else(|| {
10613 snapshot
10614 .display_snapshot
10615 .buffer_snapshot
10616 .anchor_after(Point::new(cursor_position.row, 0))
10617 });
10618
10619 let breakpoint = self
10620 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10621 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10622
10623 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10624 })
10625 // 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.
10626 .collect::<HashMap<Anchor, _>>();
10627
10628 cursors.into_iter().collect()
10629 }
10630
10631 pub fn enable_breakpoint(
10632 &mut self,
10633 _: &crate::actions::EnableBreakpoint,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) {
10637 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10638 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10639 continue;
10640 };
10641 self.edit_breakpoint_at_anchor(
10642 anchor,
10643 breakpoint,
10644 BreakpointEditAction::InvertState,
10645 cx,
10646 );
10647 }
10648 }
10649
10650 pub fn disable_breakpoint(
10651 &mut self,
10652 _: &crate::actions::DisableBreakpoint,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10657 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10658 continue;
10659 };
10660 self.edit_breakpoint_at_anchor(
10661 anchor,
10662 breakpoint,
10663 BreakpointEditAction::InvertState,
10664 cx,
10665 );
10666 }
10667 }
10668
10669 pub fn toggle_breakpoint(
10670 &mut self,
10671 _: &crate::actions::ToggleBreakpoint,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10676 if let Some(breakpoint) = breakpoint {
10677 self.edit_breakpoint_at_anchor(
10678 anchor,
10679 breakpoint,
10680 BreakpointEditAction::Toggle,
10681 cx,
10682 );
10683 } else {
10684 self.edit_breakpoint_at_anchor(
10685 anchor,
10686 Breakpoint::new_standard(),
10687 BreakpointEditAction::Toggle,
10688 cx,
10689 );
10690 }
10691 }
10692 }
10693
10694 pub fn edit_breakpoint_at_anchor(
10695 &mut self,
10696 breakpoint_position: Anchor,
10697 breakpoint: Breakpoint,
10698 edit_action: BreakpointEditAction,
10699 cx: &mut Context<Self>,
10700 ) {
10701 let Some(breakpoint_store) = &self.breakpoint_store else {
10702 return;
10703 };
10704
10705 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10706 if breakpoint_position == Anchor::min() {
10707 self.buffer()
10708 .read(cx)
10709 .excerpt_buffer_ids()
10710 .into_iter()
10711 .next()
10712 } else {
10713 None
10714 }
10715 }) else {
10716 return;
10717 };
10718
10719 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10720 return;
10721 };
10722
10723 breakpoint_store.update(cx, |breakpoint_store, cx| {
10724 breakpoint_store.toggle_breakpoint(
10725 buffer,
10726 BreakpointWithPosition {
10727 position: breakpoint_position.text_anchor,
10728 bp: breakpoint,
10729 },
10730 edit_action,
10731 cx,
10732 );
10733 });
10734
10735 cx.notify();
10736 }
10737
10738 #[cfg(any(test, feature = "test-support"))]
10739 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10740 self.breakpoint_store.clone()
10741 }
10742
10743 pub fn prepare_restore_change(
10744 &self,
10745 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10746 hunk: &MultiBufferDiffHunk,
10747 cx: &mut App,
10748 ) -> Option<()> {
10749 if hunk.is_created_file() {
10750 return None;
10751 }
10752 let buffer = self.buffer.read(cx);
10753 let diff = buffer.diff_for(hunk.buffer_id)?;
10754 let buffer = buffer.buffer(hunk.buffer_id)?;
10755 let buffer = buffer.read(cx);
10756 let original_text = diff
10757 .read(cx)
10758 .base_text()
10759 .as_rope()
10760 .slice(hunk.diff_base_byte_range.clone());
10761 let buffer_snapshot = buffer.snapshot();
10762 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10763 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10764 probe
10765 .0
10766 .start
10767 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10768 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10769 }) {
10770 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10771 Some(())
10772 } else {
10773 None
10774 }
10775 }
10776
10777 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10778 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10779 }
10780
10781 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10782 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10783 }
10784
10785 fn manipulate_lines<M>(
10786 &mut self,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 mut manipulate: M,
10790 ) where
10791 M: FnMut(&str) -> LineManipulationResult,
10792 {
10793 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10794
10795 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10796 let buffer = self.buffer.read(cx).snapshot(cx);
10797
10798 let mut edits = Vec::new();
10799
10800 let selections = self.selections.all::<Point>(cx);
10801 let mut selections = selections.iter().peekable();
10802 let mut contiguous_row_selections = Vec::new();
10803 let mut new_selections = Vec::new();
10804 let mut added_lines = 0;
10805 let mut removed_lines = 0;
10806
10807 while let Some(selection) = selections.next() {
10808 let (start_row, end_row) = consume_contiguous_rows(
10809 &mut contiguous_row_selections,
10810 selection,
10811 &display_map,
10812 &mut selections,
10813 );
10814
10815 let start_point = Point::new(start_row.0, 0);
10816 let end_point = Point::new(
10817 end_row.previous_row().0,
10818 buffer.line_len(end_row.previous_row()),
10819 );
10820 let text = buffer
10821 .text_for_range(start_point..end_point)
10822 .collect::<String>();
10823
10824 let LineManipulationResult {
10825 new_text,
10826 line_count_before,
10827 line_count_after,
10828 } = manipulate(&text);
10829
10830 edits.push((start_point..end_point, new_text));
10831
10832 // Selections must change based on added and removed line count
10833 let start_row =
10834 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10835 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10836 new_selections.push(Selection {
10837 id: selection.id,
10838 start: start_row,
10839 end: end_row,
10840 goal: SelectionGoal::None,
10841 reversed: selection.reversed,
10842 });
10843
10844 if line_count_after > line_count_before {
10845 added_lines += line_count_after - line_count_before;
10846 } else if line_count_before > line_count_after {
10847 removed_lines += line_count_before - line_count_after;
10848 }
10849 }
10850
10851 self.transact(window, cx, |this, window, cx| {
10852 let buffer = this.buffer.update(cx, |buffer, cx| {
10853 buffer.edit(edits, None, cx);
10854 buffer.snapshot(cx)
10855 });
10856
10857 // Recalculate offsets on newly edited buffer
10858 let new_selections = new_selections
10859 .iter()
10860 .map(|s| {
10861 let start_point = Point::new(s.start.0, 0);
10862 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10863 Selection {
10864 id: s.id,
10865 start: buffer.point_to_offset(start_point),
10866 end: buffer.point_to_offset(end_point),
10867 goal: s.goal,
10868 reversed: s.reversed,
10869 }
10870 })
10871 .collect();
10872
10873 this.change_selections(Default::default(), window, cx, |s| {
10874 s.select(new_selections);
10875 });
10876
10877 this.request_autoscroll(Autoscroll::fit(), cx);
10878 });
10879 }
10880
10881 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10882 self.manipulate_text(window, cx, |text| {
10883 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10884 if has_upper_case_characters {
10885 text.to_lowercase()
10886 } else {
10887 text.to_uppercase()
10888 }
10889 })
10890 }
10891
10892 fn manipulate_immutable_lines<Fn>(
10893 &mut self,
10894 window: &mut Window,
10895 cx: &mut Context<Self>,
10896 mut callback: Fn,
10897 ) where
10898 Fn: FnMut(&mut Vec<&str>),
10899 {
10900 self.manipulate_lines(window, cx, |text| {
10901 let mut lines: Vec<&str> = text.split('\n').collect();
10902 let line_count_before = lines.len();
10903
10904 callback(&mut lines);
10905
10906 LineManipulationResult {
10907 new_text: lines.join("\n"),
10908 line_count_before,
10909 line_count_after: lines.len(),
10910 }
10911 });
10912 }
10913
10914 fn manipulate_mutable_lines<Fn>(
10915 &mut self,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 mut callback: Fn,
10919 ) where
10920 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10921 {
10922 self.manipulate_lines(window, cx, |text| {
10923 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10924 let line_count_before = lines.len();
10925
10926 callback(&mut lines);
10927
10928 LineManipulationResult {
10929 new_text: lines.join("\n"),
10930 line_count_before,
10931 line_count_after: lines.len(),
10932 }
10933 });
10934 }
10935
10936 pub fn convert_indentation_to_spaces(
10937 &mut self,
10938 _: &ConvertIndentationToSpaces,
10939 window: &mut Window,
10940 cx: &mut Context<Self>,
10941 ) {
10942 let settings = self.buffer.read(cx).language_settings(cx);
10943 let tab_size = settings.tab_size.get() as usize;
10944
10945 self.manipulate_mutable_lines(window, cx, |lines| {
10946 // Allocates a reasonably sized scratch buffer once for the whole loop
10947 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10948 // Avoids recomputing spaces that could be inserted many times
10949 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10950 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10951 .collect();
10952
10953 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10954 let mut chars = line.as_ref().chars();
10955 let mut col = 0;
10956 let mut changed = false;
10957
10958 while let Some(ch) = chars.next() {
10959 match ch {
10960 ' ' => {
10961 reindented_line.push(' ');
10962 col += 1;
10963 }
10964 '\t' => {
10965 // \t are converted to spaces depending on the current column
10966 let spaces_len = tab_size - (col % tab_size);
10967 reindented_line.extend(&space_cache[spaces_len - 1]);
10968 col += spaces_len;
10969 changed = true;
10970 }
10971 _ => {
10972 // If we dont append before break, the character is consumed
10973 reindented_line.push(ch);
10974 break;
10975 }
10976 }
10977 }
10978
10979 if !changed {
10980 reindented_line.clear();
10981 continue;
10982 }
10983 // Append the rest of the line and replace old reference with new one
10984 reindented_line.extend(chars);
10985 *line = Cow::Owned(reindented_line.clone());
10986 reindented_line.clear();
10987 }
10988 });
10989 }
10990
10991 pub fn convert_indentation_to_tabs(
10992 &mut self,
10993 _: &ConvertIndentationToTabs,
10994 window: &mut Window,
10995 cx: &mut Context<Self>,
10996 ) {
10997 let settings = self.buffer.read(cx).language_settings(cx);
10998 let tab_size = settings.tab_size.get() as usize;
10999
11000 self.manipulate_mutable_lines(window, cx, |lines| {
11001 // Allocates a reasonably sized buffer once for the whole loop
11002 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11003 // Avoids recomputing spaces that could be inserted many times
11004 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11005 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11006 .collect();
11007
11008 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11009 let mut chars = line.chars();
11010 let mut spaces_count = 0;
11011 let mut first_non_indent_char = None;
11012 let mut changed = false;
11013
11014 while let Some(ch) = chars.next() {
11015 match ch {
11016 ' ' => {
11017 // Keep track of spaces. Append \t when we reach tab_size
11018 spaces_count += 1;
11019 changed = true;
11020 if spaces_count == tab_size {
11021 reindented_line.push('\t');
11022 spaces_count = 0;
11023 }
11024 }
11025 '\t' => {
11026 reindented_line.push('\t');
11027 spaces_count = 0;
11028 }
11029 _ => {
11030 // Dont append it yet, we might have remaining spaces
11031 first_non_indent_char = Some(ch);
11032 break;
11033 }
11034 }
11035 }
11036
11037 if !changed {
11038 reindented_line.clear();
11039 continue;
11040 }
11041 // Remaining spaces that didn't make a full tab stop
11042 if spaces_count > 0 {
11043 reindented_line.extend(&space_cache[spaces_count - 1]);
11044 }
11045 // If we consume an extra character that was not indentation, add it back
11046 if let Some(extra_char) = first_non_indent_char {
11047 reindented_line.push(extra_char);
11048 }
11049 // Append the rest of the line and replace old reference with new one
11050 reindented_line.extend(chars);
11051 *line = Cow::Owned(reindented_line.clone());
11052 reindented_line.clear();
11053 }
11054 });
11055 }
11056
11057 pub fn convert_to_upper_case(
11058 &mut self,
11059 _: &ConvertToUpperCase,
11060 window: &mut Window,
11061 cx: &mut Context<Self>,
11062 ) {
11063 self.manipulate_text(window, cx, |text| text.to_uppercase())
11064 }
11065
11066 pub fn convert_to_lower_case(
11067 &mut self,
11068 _: &ConvertToLowerCase,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 self.manipulate_text(window, cx, |text| text.to_lowercase())
11073 }
11074
11075 pub fn convert_to_title_case(
11076 &mut self,
11077 _: &ConvertToTitleCase,
11078 window: &mut Window,
11079 cx: &mut Context<Self>,
11080 ) {
11081 self.manipulate_text(window, cx, |text| {
11082 text.split('\n')
11083 .map(|line| line.to_case(Case::Title))
11084 .join("\n")
11085 })
11086 }
11087
11088 pub fn convert_to_snake_case(
11089 &mut self,
11090 _: &ConvertToSnakeCase,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11095 }
11096
11097 pub fn convert_to_kebab_case(
11098 &mut self,
11099 _: &ConvertToKebabCase,
11100 window: &mut Window,
11101 cx: &mut Context<Self>,
11102 ) {
11103 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11104 }
11105
11106 pub fn convert_to_upper_camel_case(
11107 &mut self,
11108 _: &ConvertToUpperCamelCase,
11109 window: &mut Window,
11110 cx: &mut Context<Self>,
11111 ) {
11112 self.manipulate_text(window, cx, |text| {
11113 text.split('\n')
11114 .map(|line| line.to_case(Case::UpperCamel))
11115 .join("\n")
11116 })
11117 }
11118
11119 pub fn convert_to_lower_camel_case(
11120 &mut self,
11121 _: &ConvertToLowerCamelCase,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 ) {
11125 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11126 }
11127
11128 pub fn convert_to_opposite_case(
11129 &mut self,
11130 _: &ConvertToOppositeCase,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) {
11134 self.manipulate_text(window, cx, |text| {
11135 text.chars()
11136 .fold(String::with_capacity(text.len()), |mut t, c| {
11137 if c.is_uppercase() {
11138 t.extend(c.to_lowercase());
11139 } else {
11140 t.extend(c.to_uppercase());
11141 }
11142 t
11143 })
11144 })
11145 }
11146
11147 pub fn convert_to_rot13(
11148 &mut self,
11149 _: &ConvertToRot13,
11150 window: &mut Window,
11151 cx: &mut Context<Self>,
11152 ) {
11153 self.manipulate_text(window, cx, |text| {
11154 text.chars()
11155 .map(|c| match c {
11156 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11157 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11158 _ => c,
11159 })
11160 .collect()
11161 })
11162 }
11163
11164 pub fn convert_to_rot47(
11165 &mut self,
11166 _: &ConvertToRot47,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 self.manipulate_text(window, cx, |text| {
11171 text.chars()
11172 .map(|c| {
11173 let code_point = c as u32;
11174 if code_point >= 33 && code_point <= 126 {
11175 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11176 }
11177 c
11178 })
11179 .collect()
11180 })
11181 }
11182
11183 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11184 where
11185 Fn: FnMut(&str) -> String,
11186 {
11187 let buffer = self.buffer.read(cx).snapshot(cx);
11188
11189 let mut new_selections = Vec::new();
11190 let mut edits = Vec::new();
11191 let mut selection_adjustment = 0i32;
11192
11193 for selection in self.selections.all::<usize>(cx) {
11194 let selection_is_empty = selection.is_empty();
11195
11196 let (start, end) = if selection_is_empty {
11197 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11198 (word_range.start, word_range.end)
11199 } else {
11200 (selection.start, selection.end)
11201 };
11202
11203 let text = buffer.text_for_range(start..end).collect::<String>();
11204 let old_length = text.len() as i32;
11205 let text = callback(&text);
11206
11207 new_selections.push(Selection {
11208 start: (start as i32 - selection_adjustment) as usize,
11209 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11210 goal: SelectionGoal::None,
11211 ..selection
11212 });
11213
11214 selection_adjustment += old_length - text.len() as i32;
11215
11216 edits.push((start..end, text));
11217 }
11218
11219 self.transact(window, cx, |this, window, cx| {
11220 this.buffer.update(cx, |buffer, cx| {
11221 buffer.edit(edits, None, cx);
11222 });
11223
11224 this.change_selections(Default::default(), window, cx, |s| {
11225 s.select(new_selections);
11226 });
11227
11228 this.request_autoscroll(Autoscroll::fit(), cx);
11229 });
11230 }
11231
11232 pub fn move_selection_on_drop(
11233 &mut self,
11234 selection: &Selection<Anchor>,
11235 target: DisplayPoint,
11236 is_cut: bool,
11237 window: &mut Window,
11238 cx: &mut Context<Self>,
11239 ) {
11240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11241 let buffer = &display_map.buffer_snapshot;
11242 let mut edits = Vec::new();
11243 let insert_point = display_map
11244 .clip_point(target, Bias::Left)
11245 .to_point(&display_map);
11246 let text = buffer
11247 .text_for_range(selection.start..selection.end)
11248 .collect::<String>();
11249 if is_cut {
11250 edits.push(((selection.start..selection.end), String::new()));
11251 }
11252 let insert_anchor = buffer.anchor_before(insert_point);
11253 edits.push(((insert_anchor..insert_anchor), text));
11254 let last_edit_start = insert_anchor.bias_left(buffer);
11255 let last_edit_end = insert_anchor.bias_right(buffer);
11256 self.transact(window, cx, |this, window, cx| {
11257 this.buffer.update(cx, |buffer, cx| {
11258 buffer.edit(edits, None, cx);
11259 });
11260 this.change_selections(Default::default(), window, cx, |s| {
11261 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11262 });
11263 });
11264 }
11265
11266 pub fn clear_selection_drag_state(&mut self) {
11267 self.selection_drag_state = SelectionDragState::None;
11268 }
11269
11270 pub fn duplicate(
11271 &mut self,
11272 upwards: bool,
11273 whole_lines: bool,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11278
11279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11280 let buffer = &display_map.buffer_snapshot;
11281 let selections = self.selections.all::<Point>(cx);
11282
11283 let mut edits = Vec::new();
11284 let mut selections_iter = selections.iter().peekable();
11285 while let Some(selection) = selections_iter.next() {
11286 let mut rows = selection.spanned_rows(false, &display_map);
11287 // duplicate line-wise
11288 if whole_lines || selection.start == selection.end {
11289 // Avoid duplicating the same lines twice.
11290 while let Some(next_selection) = selections_iter.peek() {
11291 let next_rows = next_selection.spanned_rows(false, &display_map);
11292 if next_rows.start < rows.end {
11293 rows.end = next_rows.end;
11294 selections_iter.next().unwrap();
11295 } else {
11296 break;
11297 }
11298 }
11299
11300 // Copy the text from the selected row region and splice it either at the start
11301 // or end of the region.
11302 let start = Point::new(rows.start.0, 0);
11303 let end = Point::new(
11304 rows.end.previous_row().0,
11305 buffer.line_len(rows.end.previous_row()),
11306 );
11307 let text = buffer
11308 .text_for_range(start..end)
11309 .chain(Some("\n"))
11310 .collect::<String>();
11311 let insert_location = if upwards {
11312 Point::new(rows.end.0, 0)
11313 } else {
11314 start
11315 };
11316 edits.push((insert_location..insert_location, text));
11317 } else {
11318 // duplicate character-wise
11319 let start = selection.start;
11320 let end = selection.end;
11321 let text = buffer.text_for_range(start..end).collect::<String>();
11322 edits.push((selection.end..selection.end, text));
11323 }
11324 }
11325
11326 self.transact(window, cx, |this, _, cx| {
11327 this.buffer.update(cx, |buffer, cx| {
11328 buffer.edit(edits, None, cx);
11329 });
11330
11331 this.request_autoscroll(Autoscroll::fit(), cx);
11332 });
11333 }
11334
11335 pub fn duplicate_line_up(
11336 &mut self,
11337 _: &DuplicateLineUp,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 self.duplicate(true, true, window, cx);
11342 }
11343
11344 pub fn duplicate_line_down(
11345 &mut self,
11346 _: &DuplicateLineDown,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.duplicate(false, true, window, cx);
11351 }
11352
11353 pub fn duplicate_selection(
11354 &mut self,
11355 _: &DuplicateSelection,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.duplicate(false, false, window, cx);
11360 }
11361
11362 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11364 if self.mode.is_single_line() {
11365 cx.propagate();
11366 return;
11367 }
11368
11369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11370 let buffer = self.buffer.read(cx).snapshot(cx);
11371
11372 let mut edits = Vec::new();
11373 let mut unfold_ranges = Vec::new();
11374 let mut refold_creases = Vec::new();
11375
11376 let selections = self.selections.all::<Point>(cx);
11377 let mut selections = selections.iter().peekable();
11378 let mut contiguous_row_selections = Vec::new();
11379 let mut new_selections = Vec::new();
11380
11381 while let Some(selection) = selections.next() {
11382 // Find all the selections that span a contiguous row range
11383 let (start_row, end_row) = consume_contiguous_rows(
11384 &mut contiguous_row_selections,
11385 selection,
11386 &display_map,
11387 &mut selections,
11388 );
11389
11390 // Move the text spanned by the row range to be before the line preceding the row range
11391 if start_row.0 > 0 {
11392 let range_to_move = Point::new(
11393 start_row.previous_row().0,
11394 buffer.line_len(start_row.previous_row()),
11395 )
11396 ..Point::new(
11397 end_row.previous_row().0,
11398 buffer.line_len(end_row.previous_row()),
11399 );
11400 let insertion_point = display_map
11401 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11402 .0;
11403
11404 // Don't move lines across excerpts
11405 if buffer
11406 .excerpt_containing(insertion_point..range_to_move.end)
11407 .is_some()
11408 {
11409 let text = buffer
11410 .text_for_range(range_to_move.clone())
11411 .flat_map(|s| s.chars())
11412 .skip(1)
11413 .chain(['\n'])
11414 .collect::<String>();
11415
11416 edits.push((
11417 buffer.anchor_after(range_to_move.start)
11418 ..buffer.anchor_before(range_to_move.end),
11419 String::new(),
11420 ));
11421 let insertion_anchor = buffer.anchor_after(insertion_point);
11422 edits.push((insertion_anchor..insertion_anchor, text));
11423
11424 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11425
11426 // Move selections up
11427 new_selections.extend(contiguous_row_selections.drain(..).map(
11428 |mut selection| {
11429 selection.start.row -= row_delta;
11430 selection.end.row -= row_delta;
11431 selection
11432 },
11433 ));
11434
11435 // Move folds up
11436 unfold_ranges.push(range_to_move.clone());
11437 for fold in display_map.folds_in_range(
11438 buffer.anchor_before(range_to_move.start)
11439 ..buffer.anchor_after(range_to_move.end),
11440 ) {
11441 let mut start = fold.range.start.to_point(&buffer);
11442 let mut end = fold.range.end.to_point(&buffer);
11443 start.row -= row_delta;
11444 end.row -= row_delta;
11445 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11446 }
11447 }
11448 }
11449
11450 // If we didn't move line(s), preserve the existing selections
11451 new_selections.append(&mut contiguous_row_selections);
11452 }
11453
11454 self.transact(window, cx, |this, window, cx| {
11455 this.unfold_ranges(&unfold_ranges, true, true, cx);
11456 this.buffer.update(cx, |buffer, cx| {
11457 for (range, text) in edits {
11458 buffer.edit([(range, text)], None, cx);
11459 }
11460 });
11461 this.fold_creases(refold_creases, true, window, cx);
11462 this.change_selections(Default::default(), window, cx, |s| {
11463 s.select(new_selections);
11464 })
11465 });
11466 }
11467
11468 pub fn move_line_down(
11469 &mut self,
11470 _: &MoveLineDown,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11475 if self.mode.is_single_line() {
11476 cx.propagate();
11477 return;
11478 }
11479
11480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11481 let buffer = self.buffer.read(cx).snapshot(cx);
11482
11483 let mut edits = Vec::new();
11484 let mut unfold_ranges = Vec::new();
11485 let mut refold_creases = Vec::new();
11486
11487 let selections = self.selections.all::<Point>(cx);
11488 let mut selections = selections.iter().peekable();
11489 let mut contiguous_row_selections = Vec::new();
11490 let mut new_selections = Vec::new();
11491
11492 while let Some(selection) = selections.next() {
11493 // Find all the selections that span a contiguous row range
11494 let (start_row, end_row) = consume_contiguous_rows(
11495 &mut contiguous_row_selections,
11496 selection,
11497 &display_map,
11498 &mut selections,
11499 );
11500
11501 // Move the text spanned by the row range to be after the last line of the row range
11502 if end_row.0 <= buffer.max_point().row {
11503 let range_to_move =
11504 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11505 let insertion_point = display_map
11506 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11507 .0;
11508
11509 // Don't move lines across excerpt boundaries
11510 if buffer
11511 .excerpt_containing(range_to_move.start..insertion_point)
11512 .is_some()
11513 {
11514 let mut text = String::from("\n");
11515 text.extend(buffer.text_for_range(range_to_move.clone()));
11516 text.pop(); // Drop trailing newline
11517 edits.push((
11518 buffer.anchor_after(range_to_move.start)
11519 ..buffer.anchor_before(range_to_move.end),
11520 String::new(),
11521 ));
11522 let insertion_anchor = buffer.anchor_after(insertion_point);
11523 edits.push((insertion_anchor..insertion_anchor, text));
11524
11525 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11526
11527 // Move selections down
11528 new_selections.extend(contiguous_row_selections.drain(..).map(
11529 |mut selection| {
11530 selection.start.row += row_delta;
11531 selection.end.row += row_delta;
11532 selection
11533 },
11534 ));
11535
11536 // Move folds down
11537 unfold_ranges.push(range_to_move.clone());
11538 for fold in display_map.folds_in_range(
11539 buffer.anchor_before(range_to_move.start)
11540 ..buffer.anchor_after(range_to_move.end),
11541 ) {
11542 let mut start = fold.range.start.to_point(&buffer);
11543 let mut end = fold.range.end.to_point(&buffer);
11544 start.row += row_delta;
11545 end.row += row_delta;
11546 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11547 }
11548 }
11549 }
11550
11551 // If we didn't move line(s), preserve the existing selections
11552 new_selections.append(&mut contiguous_row_selections);
11553 }
11554
11555 self.transact(window, cx, |this, window, cx| {
11556 this.unfold_ranges(&unfold_ranges, true, true, cx);
11557 this.buffer.update(cx, |buffer, cx| {
11558 for (range, text) in edits {
11559 buffer.edit([(range, text)], None, cx);
11560 }
11561 });
11562 this.fold_creases(refold_creases, true, window, cx);
11563 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11564 });
11565 }
11566
11567 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11569 let text_layout_details = &self.text_layout_details(window);
11570 self.transact(window, cx, |this, window, cx| {
11571 let edits = this.change_selections(Default::default(), window, cx, |s| {
11572 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11573 s.move_with(|display_map, selection| {
11574 if !selection.is_empty() {
11575 return;
11576 }
11577
11578 let mut head = selection.head();
11579 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11580 if head.column() == display_map.line_len(head.row()) {
11581 transpose_offset = display_map
11582 .buffer_snapshot
11583 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11584 }
11585
11586 if transpose_offset == 0 {
11587 return;
11588 }
11589
11590 *head.column_mut() += 1;
11591 head = display_map.clip_point(head, Bias::Right);
11592 let goal = SelectionGoal::HorizontalPosition(
11593 display_map
11594 .x_for_display_point(head, text_layout_details)
11595 .into(),
11596 );
11597 selection.collapse_to(head, goal);
11598
11599 let transpose_start = display_map
11600 .buffer_snapshot
11601 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11602 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11603 let transpose_end = display_map
11604 .buffer_snapshot
11605 .clip_offset(transpose_offset + 1, Bias::Right);
11606 if let Some(ch) =
11607 display_map.buffer_snapshot.chars_at(transpose_start).next()
11608 {
11609 edits.push((transpose_start..transpose_offset, String::new()));
11610 edits.push((transpose_end..transpose_end, ch.to_string()));
11611 }
11612 }
11613 });
11614 edits
11615 });
11616 this.buffer
11617 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11618 let selections = this.selections.all::<usize>(cx);
11619 this.change_selections(Default::default(), window, cx, |s| {
11620 s.select(selections);
11621 });
11622 });
11623 }
11624
11625 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11627 if self.mode.is_single_line() {
11628 cx.propagate();
11629 return;
11630 }
11631
11632 self.rewrap_impl(RewrapOptions::default(), cx)
11633 }
11634
11635 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11636 let buffer = self.buffer.read(cx).snapshot(cx);
11637 let selections = self.selections.all::<Point>(cx);
11638
11639 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11640 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11641 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11642 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11643 .peekable();
11644
11645 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11646 row
11647 } else {
11648 return Vec::new();
11649 };
11650
11651 let language_settings = buffer.language_settings_at(selection.head(), cx);
11652 let language_scope = buffer.language_scope_at(selection.head());
11653
11654 let indent_and_prefix_for_row =
11655 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11656 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11657 let (comment_prefix, rewrap_prefix) =
11658 if let Some(language_scope) = &language_scope {
11659 let indent_end = Point::new(row, indent.len);
11660 let comment_prefix = language_scope
11661 .line_comment_prefixes()
11662 .iter()
11663 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11664 .map(|prefix| prefix.to_string());
11665 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11666 let line_text_after_indent = buffer
11667 .text_for_range(indent_end..line_end)
11668 .collect::<String>();
11669 let rewrap_prefix = language_scope
11670 .rewrap_prefixes()
11671 .iter()
11672 .find_map(|prefix_regex| {
11673 prefix_regex.find(&line_text_after_indent).map(|mat| {
11674 if mat.start() == 0 {
11675 Some(mat.as_str().to_string())
11676 } else {
11677 None
11678 }
11679 })
11680 })
11681 .flatten();
11682 (comment_prefix, rewrap_prefix)
11683 } else {
11684 (None, None)
11685 };
11686 (indent, comment_prefix, rewrap_prefix)
11687 };
11688
11689 let mut ranges = Vec::new();
11690 let from_empty_selection = selection.is_empty();
11691
11692 let mut current_range_start = first_row;
11693 let mut prev_row = first_row;
11694 let (
11695 mut current_range_indent,
11696 mut current_range_comment_prefix,
11697 mut current_range_rewrap_prefix,
11698 ) = indent_and_prefix_for_row(first_row);
11699
11700 for row in non_blank_rows_iter.skip(1) {
11701 let has_paragraph_break = row > prev_row + 1;
11702
11703 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11704 indent_and_prefix_for_row(row);
11705
11706 let has_indent_change = row_indent != current_range_indent;
11707 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11708
11709 let has_boundary_change = has_comment_change
11710 || row_rewrap_prefix.is_some()
11711 || (has_indent_change && current_range_comment_prefix.is_some());
11712
11713 if has_paragraph_break || has_boundary_change {
11714 ranges.push((
11715 language_settings.clone(),
11716 Point::new(current_range_start, 0)
11717 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11718 current_range_indent,
11719 current_range_comment_prefix.clone(),
11720 current_range_rewrap_prefix.clone(),
11721 from_empty_selection,
11722 ));
11723 current_range_start = row;
11724 current_range_indent = row_indent;
11725 current_range_comment_prefix = row_comment_prefix;
11726 current_range_rewrap_prefix = row_rewrap_prefix;
11727 }
11728 prev_row = row;
11729 }
11730
11731 ranges.push((
11732 language_settings.clone(),
11733 Point::new(current_range_start, 0)
11734 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11735 current_range_indent,
11736 current_range_comment_prefix,
11737 current_range_rewrap_prefix,
11738 from_empty_selection,
11739 ));
11740
11741 ranges
11742 });
11743
11744 let mut edits = Vec::new();
11745 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11746
11747 for (
11748 language_settings,
11749 wrap_range,
11750 indent_size,
11751 comment_prefix,
11752 rewrap_prefix,
11753 from_empty_selection,
11754 ) in wrap_ranges
11755 {
11756 let mut start_row = wrap_range.start.row;
11757 let mut end_row = wrap_range.end.row;
11758
11759 // Skip selections that overlap with a range that has already been rewrapped.
11760 let selection_range = start_row..end_row;
11761 if rewrapped_row_ranges
11762 .iter()
11763 .any(|range| range.overlaps(&selection_range))
11764 {
11765 continue;
11766 }
11767
11768 let tab_size = language_settings.tab_size;
11769
11770 let indent_prefix = indent_size.chars().collect::<String>();
11771 let mut line_prefix = indent_prefix.clone();
11772 let mut inside_comment = false;
11773 if let Some(prefix) = &comment_prefix {
11774 line_prefix.push_str(prefix);
11775 inside_comment = true;
11776 }
11777 if let Some(prefix) = &rewrap_prefix {
11778 line_prefix.push_str(prefix);
11779 }
11780
11781 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11782 RewrapBehavior::InComments => inside_comment,
11783 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11784 RewrapBehavior::Anywhere => true,
11785 };
11786
11787 let should_rewrap = options.override_language_settings
11788 || allow_rewrap_based_on_language
11789 || self.hard_wrap.is_some();
11790 if !should_rewrap {
11791 continue;
11792 }
11793
11794 if from_empty_selection {
11795 'expand_upwards: while start_row > 0 {
11796 let prev_row = start_row - 1;
11797 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11798 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11799 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11800 {
11801 start_row = prev_row;
11802 } else {
11803 break 'expand_upwards;
11804 }
11805 }
11806
11807 'expand_downwards: while end_row < buffer.max_point().row {
11808 let next_row = end_row + 1;
11809 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11810 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11811 && !buffer.is_line_blank(MultiBufferRow(next_row))
11812 {
11813 end_row = next_row;
11814 } else {
11815 break 'expand_downwards;
11816 }
11817 }
11818 }
11819
11820 let start = Point::new(start_row, 0);
11821 let start_offset = start.to_offset(&buffer);
11822 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11823 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11824 let Some(lines_without_prefixes) = selection_text
11825 .lines()
11826 .enumerate()
11827 .map(|(ix, line)| {
11828 let line_trimmed = line.trim_start();
11829 if rewrap_prefix.is_some() && ix > 0 {
11830 Ok(line_trimmed)
11831 } else {
11832 line_trimmed
11833 .strip_prefix(&line_prefix.trim_start())
11834 .with_context(|| {
11835 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11836 })
11837 }
11838 })
11839 .collect::<Result<Vec<_>, _>>()
11840 .log_err()
11841 else {
11842 continue;
11843 };
11844
11845 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11846 buffer
11847 .language_settings_at(Point::new(start_row, 0), cx)
11848 .preferred_line_length as usize
11849 });
11850
11851 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11852 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11853 } else {
11854 line_prefix.clone()
11855 };
11856
11857 let wrapped_text = wrap_with_prefix(
11858 line_prefix,
11859 subsequent_lines_prefix,
11860 lines_without_prefixes.join("\n"),
11861 wrap_column,
11862 tab_size,
11863 options.preserve_existing_whitespace,
11864 );
11865
11866 // TODO: should always use char-based diff while still supporting cursor behavior that
11867 // matches vim.
11868 let mut diff_options = DiffOptions::default();
11869 if options.override_language_settings {
11870 diff_options.max_word_diff_len = 0;
11871 diff_options.max_word_diff_line_count = 0;
11872 } else {
11873 diff_options.max_word_diff_len = usize::MAX;
11874 diff_options.max_word_diff_line_count = usize::MAX;
11875 }
11876
11877 for (old_range, new_text) in
11878 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11879 {
11880 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11881 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11882 edits.push((edit_start..edit_end, new_text));
11883 }
11884
11885 rewrapped_row_ranges.push(start_row..=end_row);
11886 }
11887
11888 self.buffer
11889 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11890 }
11891
11892 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11893 let mut text = String::new();
11894 let buffer = self.buffer.read(cx).snapshot(cx);
11895 let mut selections = self.selections.all::<Point>(cx);
11896 let mut clipboard_selections = Vec::with_capacity(selections.len());
11897 {
11898 let max_point = buffer.max_point();
11899 let mut is_first = true;
11900 for selection in &mut selections {
11901 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11902 if is_entire_line {
11903 selection.start = Point::new(selection.start.row, 0);
11904 if !selection.is_empty() && selection.end.column == 0 {
11905 selection.end = cmp::min(max_point, selection.end);
11906 } else {
11907 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11908 }
11909 selection.goal = SelectionGoal::None;
11910 }
11911 if is_first {
11912 is_first = false;
11913 } else {
11914 text += "\n";
11915 }
11916 let mut len = 0;
11917 for chunk in buffer.text_for_range(selection.start..selection.end) {
11918 text.push_str(chunk);
11919 len += chunk.len();
11920 }
11921 clipboard_selections.push(ClipboardSelection {
11922 len,
11923 is_entire_line,
11924 first_line_indent: buffer
11925 .indent_size_for_line(MultiBufferRow(selection.start.row))
11926 .len,
11927 });
11928 }
11929 }
11930
11931 self.transact(window, cx, |this, window, cx| {
11932 this.change_selections(Default::default(), window, cx, |s| {
11933 s.select(selections);
11934 });
11935 this.insert("", window, cx);
11936 });
11937 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11938 }
11939
11940 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11942 let item = self.cut_common(window, cx);
11943 cx.write_to_clipboard(item);
11944 }
11945
11946 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11948 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11949 s.move_with(|snapshot, sel| {
11950 if sel.is_empty() {
11951 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11952 }
11953 });
11954 });
11955 let item = self.cut_common(window, cx);
11956 cx.set_global(KillRing(item))
11957 }
11958
11959 pub fn kill_ring_yank(
11960 &mut self,
11961 _: &KillRingYank,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11966 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11967 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11968 (kill_ring.text().to_string(), kill_ring.metadata_json())
11969 } else {
11970 return;
11971 }
11972 } else {
11973 return;
11974 };
11975 self.do_paste(&text, metadata, false, window, cx);
11976 }
11977
11978 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11979 self.do_copy(true, cx);
11980 }
11981
11982 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11983 self.do_copy(false, cx);
11984 }
11985
11986 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11987 let selections = self.selections.all::<Point>(cx);
11988 let buffer = self.buffer.read(cx).read(cx);
11989 let mut text = String::new();
11990
11991 let mut clipboard_selections = Vec::with_capacity(selections.len());
11992 {
11993 let max_point = buffer.max_point();
11994 let mut is_first = true;
11995 for selection in &selections {
11996 let mut start = selection.start;
11997 let mut end = selection.end;
11998 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11999 if is_entire_line {
12000 start = Point::new(start.row, 0);
12001 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12002 }
12003
12004 let mut trimmed_selections = Vec::new();
12005 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12006 let row = MultiBufferRow(start.row);
12007 let first_indent = buffer.indent_size_for_line(row);
12008 if first_indent.len == 0 || start.column > first_indent.len {
12009 trimmed_selections.push(start..end);
12010 } else {
12011 trimmed_selections.push(
12012 Point::new(row.0, first_indent.len)
12013 ..Point::new(row.0, buffer.line_len(row)),
12014 );
12015 for row in start.row + 1..=end.row {
12016 let mut line_len = buffer.line_len(MultiBufferRow(row));
12017 if row == end.row {
12018 line_len = end.column;
12019 }
12020 if line_len == 0 {
12021 trimmed_selections
12022 .push(Point::new(row, 0)..Point::new(row, line_len));
12023 continue;
12024 }
12025 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12026 if row_indent_size.len >= first_indent.len {
12027 trimmed_selections.push(
12028 Point::new(row, first_indent.len)..Point::new(row, line_len),
12029 );
12030 } else {
12031 trimmed_selections.clear();
12032 trimmed_selections.push(start..end);
12033 break;
12034 }
12035 }
12036 }
12037 } else {
12038 trimmed_selections.push(start..end);
12039 }
12040
12041 for trimmed_range in trimmed_selections {
12042 if is_first {
12043 is_first = false;
12044 } else {
12045 text += "\n";
12046 }
12047 let mut len = 0;
12048 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12049 text.push_str(chunk);
12050 len += chunk.len();
12051 }
12052 clipboard_selections.push(ClipboardSelection {
12053 len,
12054 is_entire_line,
12055 first_line_indent: buffer
12056 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12057 .len,
12058 });
12059 }
12060 }
12061 }
12062
12063 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12064 text,
12065 clipboard_selections,
12066 ));
12067 }
12068
12069 pub fn do_paste(
12070 &mut self,
12071 text: &String,
12072 clipboard_selections: Option<Vec<ClipboardSelection>>,
12073 handle_entire_lines: bool,
12074 window: &mut Window,
12075 cx: &mut Context<Self>,
12076 ) {
12077 if self.read_only(cx) {
12078 return;
12079 }
12080
12081 let clipboard_text = Cow::Borrowed(text);
12082
12083 self.transact(window, cx, |this, window, cx| {
12084 if let Some(mut clipboard_selections) = clipboard_selections {
12085 let old_selections = this.selections.all::<usize>(cx);
12086 let all_selections_were_entire_line =
12087 clipboard_selections.iter().all(|s| s.is_entire_line);
12088 let first_selection_indent_column =
12089 clipboard_selections.first().map(|s| s.first_line_indent);
12090 if clipboard_selections.len() != old_selections.len() {
12091 clipboard_selections.drain(..);
12092 }
12093 let cursor_offset = this.selections.last::<usize>(cx).head();
12094 let mut auto_indent_on_paste = true;
12095
12096 this.buffer.update(cx, |buffer, cx| {
12097 let snapshot = buffer.read(cx);
12098 auto_indent_on_paste = snapshot
12099 .language_settings_at(cursor_offset, cx)
12100 .auto_indent_on_paste;
12101
12102 let mut start_offset = 0;
12103 let mut edits = Vec::new();
12104 let mut original_indent_columns = Vec::new();
12105 for (ix, selection) in old_selections.iter().enumerate() {
12106 let to_insert;
12107 let entire_line;
12108 let original_indent_column;
12109 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12110 let end_offset = start_offset + clipboard_selection.len;
12111 to_insert = &clipboard_text[start_offset..end_offset];
12112 entire_line = clipboard_selection.is_entire_line;
12113 start_offset = end_offset + 1;
12114 original_indent_column = Some(clipboard_selection.first_line_indent);
12115 } else {
12116 to_insert = clipboard_text.as_str();
12117 entire_line = all_selections_were_entire_line;
12118 original_indent_column = first_selection_indent_column
12119 }
12120
12121 // If the corresponding selection was empty when this slice of the
12122 // clipboard text was written, then the entire line containing the
12123 // selection was copied. If this selection is also currently empty,
12124 // then paste the line before the current line of the buffer.
12125 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12126 let column = selection.start.to_point(&snapshot).column as usize;
12127 let line_start = selection.start - column;
12128 line_start..line_start
12129 } else {
12130 selection.range()
12131 };
12132
12133 edits.push((range, to_insert));
12134 original_indent_columns.push(original_indent_column);
12135 }
12136 drop(snapshot);
12137
12138 buffer.edit(
12139 edits,
12140 if auto_indent_on_paste {
12141 Some(AutoindentMode::Block {
12142 original_indent_columns,
12143 })
12144 } else {
12145 None
12146 },
12147 cx,
12148 );
12149 });
12150
12151 let selections = this.selections.all::<usize>(cx);
12152 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12153 } else {
12154 this.insert(&clipboard_text, window, cx);
12155 }
12156 });
12157 }
12158
12159 pub fn diff_clipboard_with_selection(
12160 &mut self,
12161 _: &DiffClipboardWithSelection,
12162 window: &mut Window,
12163 cx: &mut Context<Self>,
12164 ) {
12165 let selections = self.selections.all::<usize>(cx);
12166
12167 if selections.is_empty() {
12168 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12169 return;
12170 };
12171
12172 let clipboard_text = match cx.read_from_clipboard() {
12173 Some(item) => match item.entries().first() {
12174 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12175 _ => None,
12176 },
12177 None => None,
12178 };
12179
12180 let Some(clipboard_text) = clipboard_text else {
12181 log::warn!("Clipboard doesn't contain text.");
12182 return;
12183 };
12184
12185 window.dispatch_action(
12186 Box::new(DiffClipboardWithSelectionData {
12187 clipboard_text,
12188 editor: cx.entity(),
12189 }),
12190 cx,
12191 );
12192 }
12193
12194 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12196 if let Some(item) = cx.read_from_clipboard() {
12197 let entries = item.entries();
12198
12199 match entries.first() {
12200 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12201 // of all the pasted entries.
12202 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12203 .do_paste(
12204 clipboard_string.text(),
12205 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12206 true,
12207 window,
12208 cx,
12209 ),
12210 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12211 }
12212 }
12213 }
12214
12215 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12216 if self.read_only(cx) {
12217 return;
12218 }
12219
12220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12221
12222 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12223 if let Some((selections, _)) =
12224 self.selection_history.transaction(transaction_id).cloned()
12225 {
12226 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12227 s.select_anchors(selections.to_vec());
12228 });
12229 } else {
12230 log::error!(
12231 "No entry in selection_history found for undo. \
12232 This may correspond to a bug where undo does not update the selection. \
12233 If this is occurring, please add details to \
12234 https://github.com/zed-industries/zed/issues/22692"
12235 );
12236 }
12237 self.request_autoscroll(Autoscroll::fit(), cx);
12238 self.unmark_text(window, cx);
12239 self.refresh_inline_completion(true, false, window, cx);
12240 cx.emit(EditorEvent::Edited { transaction_id });
12241 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12242 }
12243 }
12244
12245 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12246 if self.read_only(cx) {
12247 return;
12248 }
12249
12250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12251
12252 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12253 if let Some((_, Some(selections))) =
12254 self.selection_history.transaction(transaction_id).cloned()
12255 {
12256 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12257 s.select_anchors(selections.to_vec());
12258 });
12259 } else {
12260 log::error!(
12261 "No entry in selection_history found for redo. \
12262 This may correspond to a bug where undo does not update the selection. \
12263 If this is occurring, please add details to \
12264 https://github.com/zed-industries/zed/issues/22692"
12265 );
12266 }
12267 self.request_autoscroll(Autoscroll::fit(), cx);
12268 self.unmark_text(window, cx);
12269 self.refresh_inline_completion(true, false, window, cx);
12270 cx.emit(EditorEvent::Edited { transaction_id });
12271 }
12272 }
12273
12274 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12275 self.buffer
12276 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12277 }
12278
12279 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12280 self.buffer
12281 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12282 }
12283
12284 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12286 self.change_selections(Default::default(), window, cx, |s| {
12287 s.move_with(|map, selection| {
12288 let cursor = if selection.is_empty() {
12289 movement::left(map, selection.start)
12290 } else {
12291 selection.start
12292 };
12293 selection.collapse_to(cursor, SelectionGoal::None);
12294 });
12295 })
12296 }
12297
12298 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12300 self.change_selections(Default::default(), window, cx, |s| {
12301 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12302 })
12303 }
12304
12305 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12307 self.change_selections(Default::default(), window, cx, |s| {
12308 s.move_with(|map, selection| {
12309 let cursor = if selection.is_empty() {
12310 movement::right(map, selection.end)
12311 } else {
12312 selection.end
12313 };
12314 selection.collapse_to(cursor, SelectionGoal::None)
12315 });
12316 })
12317 }
12318
12319 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12321 self.change_selections(Default::default(), window, cx, |s| {
12322 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12323 })
12324 }
12325
12326 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12327 if self.take_rename(true, window, cx).is_some() {
12328 return;
12329 }
12330
12331 if self.mode.is_single_line() {
12332 cx.propagate();
12333 return;
12334 }
12335
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12337
12338 let text_layout_details = &self.text_layout_details(window);
12339 let selection_count = self.selections.count();
12340 let first_selection = self.selections.first_anchor();
12341
12342 self.change_selections(Default::default(), window, cx, |s| {
12343 s.move_with(|map, selection| {
12344 if !selection.is_empty() {
12345 selection.goal = SelectionGoal::None;
12346 }
12347 let (cursor, goal) = movement::up(
12348 map,
12349 selection.start,
12350 selection.goal,
12351 false,
12352 text_layout_details,
12353 );
12354 selection.collapse_to(cursor, goal);
12355 });
12356 });
12357
12358 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12359 {
12360 cx.propagate();
12361 }
12362 }
12363
12364 pub fn move_up_by_lines(
12365 &mut self,
12366 action: &MoveUpByLines,
12367 window: &mut Window,
12368 cx: &mut Context<Self>,
12369 ) {
12370 if self.take_rename(true, window, cx).is_some() {
12371 return;
12372 }
12373
12374 if self.mode.is_single_line() {
12375 cx.propagate();
12376 return;
12377 }
12378
12379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12380
12381 let text_layout_details = &self.text_layout_details(window);
12382
12383 self.change_selections(Default::default(), window, cx, |s| {
12384 s.move_with(|map, selection| {
12385 if !selection.is_empty() {
12386 selection.goal = SelectionGoal::None;
12387 }
12388 let (cursor, goal) = movement::up_by_rows(
12389 map,
12390 selection.start,
12391 action.lines,
12392 selection.goal,
12393 false,
12394 text_layout_details,
12395 );
12396 selection.collapse_to(cursor, goal);
12397 });
12398 })
12399 }
12400
12401 pub fn move_down_by_lines(
12402 &mut self,
12403 action: &MoveDownByLines,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if self.take_rename(true, window, cx).is_some() {
12408 return;
12409 }
12410
12411 if self.mode.is_single_line() {
12412 cx.propagate();
12413 return;
12414 }
12415
12416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12417
12418 let text_layout_details = &self.text_layout_details(window);
12419
12420 self.change_selections(Default::default(), window, cx, |s| {
12421 s.move_with(|map, selection| {
12422 if !selection.is_empty() {
12423 selection.goal = SelectionGoal::None;
12424 }
12425 let (cursor, goal) = movement::down_by_rows(
12426 map,
12427 selection.start,
12428 action.lines,
12429 selection.goal,
12430 false,
12431 text_layout_details,
12432 );
12433 selection.collapse_to(cursor, goal);
12434 });
12435 })
12436 }
12437
12438 pub fn select_down_by_lines(
12439 &mut self,
12440 action: &SelectDownByLines,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) {
12444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12445 let text_layout_details = &self.text_layout_details(window);
12446 self.change_selections(Default::default(), window, cx, |s| {
12447 s.move_heads_with(|map, head, goal| {
12448 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12449 })
12450 })
12451 }
12452
12453 pub fn select_up_by_lines(
12454 &mut self,
12455 action: &SelectUpByLines,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12460 let text_layout_details = &self.text_layout_details(window);
12461 self.change_selections(Default::default(), window, cx, |s| {
12462 s.move_heads_with(|map, head, goal| {
12463 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12464 })
12465 })
12466 }
12467
12468 pub fn select_page_up(
12469 &mut self,
12470 _: &SelectPageUp,
12471 window: &mut Window,
12472 cx: &mut Context<Self>,
12473 ) {
12474 let Some(row_count) = self.visible_row_count() else {
12475 return;
12476 };
12477
12478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12479
12480 let text_layout_details = &self.text_layout_details(window);
12481
12482 self.change_selections(Default::default(), window, cx, |s| {
12483 s.move_heads_with(|map, head, goal| {
12484 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12485 })
12486 })
12487 }
12488
12489 pub fn move_page_up(
12490 &mut self,
12491 action: &MovePageUp,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) {
12495 if self.take_rename(true, window, cx).is_some() {
12496 return;
12497 }
12498
12499 if self
12500 .context_menu
12501 .borrow_mut()
12502 .as_mut()
12503 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12504 .unwrap_or(false)
12505 {
12506 return;
12507 }
12508
12509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12510 cx.propagate();
12511 return;
12512 }
12513
12514 let Some(row_count) = self.visible_row_count() else {
12515 return;
12516 };
12517
12518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12519
12520 let effects = if action.center_cursor {
12521 SelectionEffects::scroll(Autoscroll::center())
12522 } else {
12523 SelectionEffects::default()
12524 };
12525
12526 let text_layout_details = &self.text_layout_details(window);
12527
12528 self.change_selections(effects, window, cx, |s| {
12529 s.move_with(|map, selection| {
12530 if !selection.is_empty() {
12531 selection.goal = SelectionGoal::None;
12532 }
12533 let (cursor, goal) = movement::up_by_rows(
12534 map,
12535 selection.end,
12536 row_count,
12537 selection.goal,
12538 false,
12539 text_layout_details,
12540 );
12541 selection.collapse_to(cursor, goal);
12542 });
12543 });
12544 }
12545
12546 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12548 let text_layout_details = &self.text_layout_details(window);
12549 self.change_selections(Default::default(), window, cx, |s| {
12550 s.move_heads_with(|map, head, goal| {
12551 movement::up(map, head, goal, false, text_layout_details)
12552 })
12553 })
12554 }
12555
12556 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12557 self.take_rename(true, window, cx);
12558
12559 if self.mode.is_single_line() {
12560 cx.propagate();
12561 return;
12562 }
12563
12564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12565
12566 let text_layout_details = &self.text_layout_details(window);
12567 let selection_count = self.selections.count();
12568 let first_selection = self.selections.first_anchor();
12569
12570 self.change_selections(Default::default(), window, cx, |s| {
12571 s.move_with(|map, selection| {
12572 if !selection.is_empty() {
12573 selection.goal = SelectionGoal::None;
12574 }
12575 let (cursor, goal) = movement::down(
12576 map,
12577 selection.end,
12578 selection.goal,
12579 false,
12580 text_layout_details,
12581 );
12582 selection.collapse_to(cursor, goal);
12583 });
12584 });
12585
12586 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12587 {
12588 cx.propagate();
12589 }
12590 }
12591
12592 pub fn select_page_down(
12593 &mut self,
12594 _: &SelectPageDown,
12595 window: &mut Window,
12596 cx: &mut Context<Self>,
12597 ) {
12598 let Some(row_count) = self.visible_row_count() else {
12599 return;
12600 };
12601
12602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12603
12604 let text_layout_details = &self.text_layout_details(window);
12605
12606 self.change_selections(Default::default(), window, cx, |s| {
12607 s.move_heads_with(|map, head, goal| {
12608 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12609 })
12610 })
12611 }
12612
12613 pub fn move_page_down(
12614 &mut self,
12615 action: &MovePageDown,
12616 window: &mut Window,
12617 cx: &mut Context<Self>,
12618 ) {
12619 if self.take_rename(true, window, cx).is_some() {
12620 return;
12621 }
12622
12623 if self
12624 .context_menu
12625 .borrow_mut()
12626 .as_mut()
12627 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12628 .unwrap_or(false)
12629 {
12630 return;
12631 }
12632
12633 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12634 cx.propagate();
12635 return;
12636 }
12637
12638 let Some(row_count) = self.visible_row_count() else {
12639 return;
12640 };
12641
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643
12644 let effects = if action.center_cursor {
12645 SelectionEffects::scroll(Autoscroll::center())
12646 } else {
12647 SelectionEffects::default()
12648 };
12649
12650 let text_layout_details = &self.text_layout_details(window);
12651 self.change_selections(effects, window, cx, |s| {
12652 s.move_with(|map, selection| {
12653 if !selection.is_empty() {
12654 selection.goal = SelectionGoal::None;
12655 }
12656 let (cursor, goal) = movement::down_by_rows(
12657 map,
12658 selection.end,
12659 row_count,
12660 selection.goal,
12661 false,
12662 text_layout_details,
12663 );
12664 selection.collapse_to(cursor, goal);
12665 });
12666 });
12667 }
12668
12669 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12671 let text_layout_details = &self.text_layout_details(window);
12672 self.change_selections(Default::default(), window, cx, |s| {
12673 s.move_heads_with(|map, head, goal| {
12674 movement::down(map, head, goal, false, text_layout_details)
12675 })
12676 });
12677 }
12678
12679 pub fn context_menu_first(
12680 &mut self,
12681 _: &ContextMenuFirst,
12682 window: &mut Window,
12683 cx: &mut Context<Self>,
12684 ) {
12685 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12686 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12687 }
12688 }
12689
12690 pub fn context_menu_prev(
12691 &mut self,
12692 _: &ContextMenuPrevious,
12693 window: &mut Window,
12694 cx: &mut Context<Self>,
12695 ) {
12696 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12697 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12698 }
12699 }
12700
12701 pub fn context_menu_next(
12702 &mut self,
12703 _: &ContextMenuNext,
12704 window: &mut Window,
12705 cx: &mut Context<Self>,
12706 ) {
12707 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12708 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12709 }
12710 }
12711
12712 pub fn context_menu_last(
12713 &mut self,
12714 _: &ContextMenuLast,
12715 window: &mut Window,
12716 cx: &mut Context<Self>,
12717 ) {
12718 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12719 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12720 }
12721 }
12722
12723 pub fn signature_help_prev(
12724 &mut self,
12725 _: &SignatureHelpPrevious,
12726 _: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 if let Some(popover) = self.signature_help_state.popover_mut() {
12730 if popover.current_signature == 0 {
12731 popover.current_signature = popover.signatures.len() - 1;
12732 } else {
12733 popover.current_signature -= 1;
12734 }
12735 cx.notify();
12736 }
12737 }
12738
12739 pub fn signature_help_next(
12740 &mut self,
12741 _: &SignatureHelpNext,
12742 _: &mut Window,
12743 cx: &mut Context<Self>,
12744 ) {
12745 if let Some(popover) = self.signature_help_state.popover_mut() {
12746 if popover.current_signature + 1 == popover.signatures.len() {
12747 popover.current_signature = 0;
12748 } else {
12749 popover.current_signature += 1;
12750 }
12751 cx.notify();
12752 }
12753 }
12754
12755 pub fn move_to_previous_word_start(
12756 &mut self,
12757 _: &MoveToPreviousWordStart,
12758 window: &mut Window,
12759 cx: &mut Context<Self>,
12760 ) {
12761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12762 self.change_selections(Default::default(), window, cx, |s| {
12763 s.move_cursors_with(|map, head, _| {
12764 (
12765 movement::previous_word_start(map, head),
12766 SelectionGoal::None,
12767 )
12768 });
12769 })
12770 }
12771
12772 pub fn move_to_previous_subword_start(
12773 &mut self,
12774 _: &MoveToPreviousSubwordStart,
12775 window: &mut Window,
12776 cx: &mut Context<Self>,
12777 ) {
12778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12779 self.change_selections(Default::default(), window, cx, |s| {
12780 s.move_cursors_with(|map, head, _| {
12781 (
12782 movement::previous_subword_start(map, head),
12783 SelectionGoal::None,
12784 )
12785 });
12786 })
12787 }
12788
12789 pub fn select_to_previous_word_start(
12790 &mut self,
12791 _: &SelectToPreviousWordStart,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_heads_with(|map, head, _| {
12798 (
12799 movement::previous_word_start(map, head),
12800 SelectionGoal::None,
12801 )
12802 });
12803 })
12804 }
12805
12806 pub fn select_to_previous_subword_start(
12807 &mut self,
12808 _: &SelectToPreviousSubwordStart,
12809 window: &mut Window,
12810 cx: &mut Context<Self>,
12811 ) {
12812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12813 self.change_selections(Default::default(), window, cx, |s| {
12814 s.move_heads_with(|map, head, _| {
12815 (
12816 movement::previous_subword_start(map, head),
12817 SelectionGoal::None,
12818 )
12819 });
12820 })
12821 }
12822
12823 pub fn delete_to_previous_word_start(
12824 &mut self,
12825 action: &DeleteToPreviousWordStart,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12830 self.transact(window, cx, |this, window, cx| {
12831 this.select_autoclose_pair(window, cx);
12832 this.change_selections(Default::default(), window, cx, |s| {
12833 s.move_with(|map, selection| {
12834 if selection.is_empty() {
12835 let cursor = if action.ignore_newlines {
12836 movement::previous_word_start(map, selection.head())
12837 } else {
12838 movement::previous_word_start_or_newline(map, selection.head())
12839 };
12840 selection.set_head(cursor, SelectionGoal::None);
12841 }
12842 });
12843 });
12844 this.insert("", window, cx);
12845 });
12846 }
12847
12848 pub fn delete_to_previous_subword_start(
12849 &mut self,
12850 _: &DeleteToPreviousSubwordStart,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12855 self.transact(window, cx, |this, window, cx| {
12856 this.select_autoclose_pair(window, cx);
12857 this.change_selections(Default::default(), window, cx, |s| {
12858 s.move_with(|map, selection| {
12859 if selection.is_empty() {
12860 let cursor = movement::previous_subword_start(map, selection.head());
12861 selection.set_head(cursor, SelectionGoal::None);
12862 }
12863 });
12864 });
12865 this.insert("", window, cx);
12866 });
12867 }
12868
12869 pub fn move_to_next_word_end(
12870 &mut self,
12871 _: &MoveToNextWordEnd,
12872 window: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12876 self.change_selections(Default::default(), window, cx, |s| {
12877 s.move_cursors_with(|map, head, _| {
12878 (movement::next_word_end(map, head), SelectionGoal::None)
12879 });
12880 })
12881 }
12882
12883 pub fn move_to_next_subword_end(
12884 &mut self,
12885 _: &MoveToNextSubwordEnd,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890 self.change_selections(Default::default(), window, cx, |s| {
12891 s.move_cursors_with(|map, head, _| {
12892 (movement::next_subword_end(map, head), SelectionGoal::None)
12893 });
12894 })
12895 }
12896
12897 pub fn select_to_next_word_end(
12898 &mut self,
12899 _: &SelectToNextWordEnd,
12900 window: &mut Window,
12901 cx: &mut Context<Self>,
12902 ) {
12903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12904 self.change_selections(Default::default(), window, cx, |s| {
12905 s.move_heads_with(|map, head, _| {
12906 (movement::next_word_end(map, head), SelectionGoal::None)
12907 });
12908 })
12909 }
12910
12911 pub fn select_to_next_subword_end(
12912 &mut self,
12913 _: &SelectToNextSubwordEnd,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918 self.change_selections(Default::default(), window, cx, |s| {
12919 s.move_heads_with(|map, head, _| {
12920 (movement::next_subword_end(map, head), SelectionGoal::None)
12921 });
12922 })
12923 }
12924
12925 pub fn delete_to_next_word_end(
12926 &mut self,
12927 action: &DeleteToNextWordEnd,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12932 self.transact(window, cx, |this, window, cx| {
12933 this.change_selections(Default::default(), window, cx, |s| {
12934 s.move_with(|map, selection| {
12935 if selection.is_empty() {
12936 let cursor = if action.ignore_newlines {
12937 movement::next_word_end(map, selection.head())
12938 } else {
12939 movement::next_word_end_or_newline(map, selection.head())
12940 };
12941 selection.set_head(cursor, SelectionGoal::None);
12942 }
12943 });
12944 });
12945 this.insert("", window, cx);
12946 });
12947 }
12948
12949 pub fn delete_to_next_subword_end(
12950 &mut self,
12951 _: &DeleteToNextSubwordEnd,
12952 window: &mut Window,
12953 cx: &mut Context<Self>,
12954 ) {
12955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12956 self.transact(window, cx, |this, window, cx| {
12957 this.change_selections(Default::default(), window, cx, |s| {
12958 s.move_with(|map, selection| {
12959 if selection.is_empty() {
12960 let cursor = movement::next_subword_end(map, selection.head());
12961 selection.set_head(cursor, SelectionGoal::None);
12962 }
12963 });
12964 });
12965 this.insert("", window, cx);
12966 });
12967 }
12968
12969 pub fn move_to_beginning_of_line(
12970 &mut self,
12971 action: &MoveToBeginningOfLine,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976 self.change_selections(Default::default(), window, cx, |s| {
12977 s.move_cursors_with(|map, head, _| {
12978 (
12979 movement::indented_line_beginning(
12980 map,
12981 head,
12982 action.stop_at_soft_wraps,
12983 action.stop_at_indent,
12984 ),
12985 SelectionGoal::None,
12986 )
12987 });
12988 })
12989 }
12990
12991 pub fn select_to_beginning_of_line(
12992 &mut self,
12993 action: &SelectToBeginningOfLine,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12998 self.change_selections(Default::default(), window, cx, |s| {
12999 s.move_heads_with(|map, head, _| {
13000 (
13001 movement::indented_line_beginning(
13002 map,
13003 head,
13004 action.stop_at_soft_wraps,
13005 action.stop_at_indent,
13006 ),
13007 SelectionGoal::None,
13008 )
13009 });
13010 });
13011 }
13012
13013 pub fn delete_to_beginning_of_line(
13014 &mut self,
13015 action: &DeleteToBeginningOfLine,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13020 self.transact(window, cx, |this, window, cx| {
13021 this.change_selections(Default::default(), window, cx, |s| {
13022 s.move_with(|_, selection| {
13023 selection.reversed = true;
13024 });
13025 });
13026
13027 this.select_to_beginning_of_line(
13028 &SelectToBeginningOfLine {
13029 stop_at_soft_wraps: false,
13030 stop_at_indent: action.stop_at_indent,
13031 },
13032 window,
13033 cx,
13034 );
13035 this.backspace(&Backspace, window, cx);
13036 });
13037 }
13038
13039 pub fn move_to_end_of_line(
13040 &mut self,
13041 action: &MoveToEndOfLine,
13042 window: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) {
13045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13046 self.change_selections(Default::default(), window, cx, |s| {
13047 s.move_cursors_with(|map, head, _| {
13048 (
13049 movement::line_end(map, head, action.stop_at_soft_wraps),
13050 SelectionGoal::None,
13051 )
13052 });
13053 })
13054 }
13055
13056 pub fn select_to_end_of_line(
13057 &mut self,
13058 action: &SelectToEndOfLine,
13059 window: &mut Window,
13060 cx: &mut Context<Self>,
13061 ) {
13062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_heads_with(|map, head, _| {
13065 (
13066 movement::line_end(map, head, action.stop_at_soft_wraps),
13067 SelectionGoal::None,
13068 )
13069 });
13070 })
13071 }
13072
13073 pub fn delete_to_end_of_line(
13074 &mut self,
13075 _: &DeleteToEndOfLine,
13076 window: &mut Window,
13077 cx: &mut Context<Self>,
13078 ) {
13079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13080 self.transact(window, cx, |this, window, cx| {
13081 this.select_to_end_of_line(
13082 &SelectToEndOfLine {
13083 stop_at_soft_wraps: false,
13084 },
13085 window,
13086 cx,
13087 );
13088 this.delete(&Delete, window, cx);
13089 });
13090 }
13091
13092 pub fn cut_to_end_of_line(
13093 &mut self,
13094 _: &CutToEndOfLine,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13099 self.transact(window, cx, |this, window, cx| {
13100 this.select_to_end_of_line(
13101 &SelectToEndOfLine {
13102 stop_at_soft_wraps: false,
13103 },
13104 window,
13105 cx,
13106 );
13107 this.cut(&Cut, window, cx);
13108 });
13109 }
13110
13111 pub fn move_to_start_of_paragraph(
13112 &mut self,
13113 _: &MoveToStartOfParagraph,
13114 window: &mut Window,
13115 cx: &mut Context<Self>,
13116 ) {
13117 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13118 cx.propagate();
13119 return;
13120 }
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_with(|map, selection| {
13124 selection.collapse_to(
13125 movement::start_of_paragraph(map, selection.head(), 1),
13126 SelectionGoal::None,
13127 )
13128 });
13129 })
13130 }
13131
13132 pub fn move_to_end_of_paragraph(
13133 &mut self,
13134 _: &MoveToEndOfParagraph,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13139 cx.propagate();
13140 return;
13141 }
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_with(|map, selection| {
13145 selection.collapse_to(
13146 movement::end_of_paragraph(map, selection.head(), 1),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn select_to_start_of_paragraph(
13154 &mut self,
13155 _: &SelectToStartOfParagraph,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13160 cx.propagate();
13161 return;
13162 }
13163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13164 self.change_selections(Default::default(), window, cx, |s| {
13165 s.move_heads_with(|map, head, _| {
13166 (
13167 movement::start_of_paragraph(map, head, 1),
13168 SelectionGoal::None,
13169 )
13170 });
13171 })
13172 }
13173
13174 pub fn select_to_end_of_paragraph(
13175 &mut self,
13176 _: &SelectToEndOfParagraph,
13177 window: &mut Window,
13178 cx: &mut Context<Self>,
13179 ) {
13180 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13181 cx.propagate();
13182 return;
13183 }
13184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13185 self.change_selections(Default::default(), window, cx, |s| {
13186 s.move_heads_with(|map, head, _| {
13187 (
13188 movement::end_of_paragraph(map, head, 1),
13189 SelectionGoal::None,
13190 )
13191 });
13192 })
13193 }
13194
13195 pub fn move_to_start_of_excerpt(
13196 &mut self,
13197 _: &MoveToStartOfExcerpt,
13198 window: &mut Window,
13199 cx: &mut Context<Self>,
13200 ) {
13201 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13202 cx.propagate();
13203 return;
13204 }
13205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13206 self.change_selections(Default::default(), window, cx, |s| {
13207 s.move_with(|map, selection| {
13208 selection.collapse_to(
13209 movement::start_of_excerpt(
13210 map,
13211 selection.head(),
13212 workspace::searchable::Direction::Prev,
13213 ),
13214 SelectionGoal::None,
13215 )
13216 });
13217 })
13218 }
13219
13220 pub fn move_to_start_of_next_excerpt(
13221 &mut self,
13222 _: &MoveToStartOfNextExcerpt,
13223 window: &mut Window,
13224 cx: &mut Context<Self>,
13225 ) {
13226 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13227 cx.propagate();
13228 return;
13229 }
13230
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_with(|map, selection| {
13233 selection.collapse_to(
13234 movement::start_of_excerpt(
13235 map,
13236 selection.head(),
13237 workspace::searchable::Direction::Next,
13238 ),
13239 SelectionGoal::None,
13240 )
13241 });
13242 })
13243 }
13244
13245 pub fn move_to_end_of_excerpt(
13246 &mut self,
13247 _: &MoveToEndOfExcerpt,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) {
13251 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13252 cx.propagate();
13253 return;
13254 }
13255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13256 self.change_selections(Default::default(), window, cx, |s| {
13257 s.move_with(|map, selection| {
13258 selection.collapse_to(
13259 movement::end_of_excerpt(
13260 map,
13261 selection.head(),
13262 workspace::searchable::Direction::Next,
13263 ),
13264 SelectionGoal::None,
13265 )
13266 });
13267 })
13268 }
13269
13270 pub fn move_to_end_of_previous_excerpt(
13271 &mut self,
13272 _: &MoveToEndOfPreviousExcerpt,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) {
13276 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13277 cx.propagate();
13278 return;
13279 }
13280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13281 self.change_selections(Default::default(), window, cx, |s| {
13282 s.move_with(|map, selection| {
13283 selection.collapse_to(
13284 movement::end_of_excerpt(
13285 map,
13286 selection.head(),
13287 workspace::searchable::Direction::Prev,
13288 ),
13289 SelectionGoal::None,
13290 )
13291 });
13292 })
13293 }
13294
13295 pub fn select_to_start_of_excerpt(
13296 &mut self,
13297 _: &SelectToStartOfExcerpt,
13298 window: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13302 cx.propagate();
13303 return;
13304 }
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_heads_with(|map, head, _| {
13308 (
13309 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13310 SelectionGoal::None,
13311 )
13312 });
13313 })
13314 }
13315
13316 pub fn select_to_start_of_next_excerpt(
13317 &mut self,
13318 _: &SelectToStartOfNextExcerpt,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13323 cx.propagate();
13324 return;
13325 }
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327 self.change_selections(Default::default(), window, cx, |s| {
13328 s.move_heads_with(|map, head, _| {
13329 (
13330 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13331 SelectionGoal::None,
13332 )
13333 });
13334 })
13335 }
13336
13337 pub fn select_to_end_of_excerpt(
13338 &mut self,
13339 _: &SelectToEndOfExcerpt,
13340 window: &mut Window,
13341 cx: &mut Context<Self>,
13342 ) {
13343 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13344 cx.propagate();
13345 return;
13346 }
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13348 self.change_selections(Default::default(), window, cx, |s| {
13349 s.move_heads_with(|map, head, _| {
13350 (
13351 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13352 SelectionGoal::None,
13353 )
13354 });
13355 })
13356 }
13357
13358 pub fn select_to_end_of_previous_excerpt(
13359 &mut self,
13360 _: &SelectToEndOfPreviousExcerpt,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13365 cx.propagate();
13366 return;
13367 }
13368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13369 self.change_selections(Default::default(), window, cx, |s| {
13370 s.move_heads_with(|map, head, _| {
13371 (
13372 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13373 SelectionGoal::None,
13374 )
13375 });
13376 })
13377 }
13378
13379 pub fn move_to_beginning(
13380 &mut self,
13381 _: &MoveToBeginning,
13382 window: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13386 cx.propagate();
13387 return;
13388 }
13389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13390 self.change_selections(Default::default(), window, cx, |s| {
13391 s.select_ranges(vec![0..0]);
13392 });
13393 }
13394
13395 pub fn select_to_beginning(
13396 &mut self,
13397 _: &SelectToBeginning,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 let mut selection = self.selections.last::<Point>(cx);
13402 selection.set_head(Point::zero(), SelectionGoal::None);
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13404 self.change_selections(Default::default(), window, cx, |s| {
13405 s.select(vec![selection]);
13406 });
13407 }
13408
13409 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13410 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13411 cx.propagate();
13412 return;
13413 }
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13415 let cursor = self.buffer.read(cx).read(cx).len();
13416 self.change_selections(Default::default(), window, cx, |s| {
13417 s.select_ranges(vec![cursor..cursor])
13418 });
13419 }
13420
13421 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13422 self.nav_history = nav_history;
13423 }
13424
13425 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13426 self.nav_history.as_ref()
13427 }
13428
13429 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13430 self.push_to_nav_history(
13431 self.selections.newest_anchor().head(),
13432 None,
13433 false,
13434 true,
13435 cx,
13436 );
13437 }
13438
13439 fn push_to_nav_history(
13440 &mut self,
13441 cursor_anchor: Anchor,
13442 new_position: Option<Point>,
13443 is_deactivate: bool,
13444 always: bool,
13445 cx: &mut Context<Self>,
13446 ) {
13447 if let Some(nav_history) = self.nav_history.as_mut() {
13448 let buffer = self.buffer.read(cx).read(cx);
13449 let cursor_position = cursor_anchor.to_point(&buffer);
13450 let scroll_state = self.scroll_manager.anchor();
13451 let scroll_top_row = scroll_state.top_row(&buffer);
13452 drop(buffer);
13453
13454 if let Some(new_position) = new_position {
13455 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13456 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13457 return;
13458 }
13459 }
13460
13461 nav_history.push(
13462 Some(NavigationData {
13463 cursor_anchor,
13464 cursor_position,
13465 scroll_anchor: scroll_state,
13466 scroll_top_row,
13467 }),
13468 cx,
13469 );
13470 cx.emit(EditorEvent::PushedToNavHistory {
13471 anchor: cursor_anchor,
13472 is_deactivate,
13473 })
13474 }
13475 }
13476
13477 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13479 let buffer = self.buffer.read(cx).snapshot(cx);
13480 let mut selection = self.selections.first::<usize>(cx);
13481 selection.set_head(buffer.len(), SelectionGoal::None);
13482 self.change_selections(Default::default(), window, cx, |s| {
13483 s.select(vec![selection]);
13484 });
13485 }
13486
13487 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13488 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13489 let end = self.buffer.read(cx).read(cx).len();
13490 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13491 s.select_ranges(vec![0..end]);
13492 });
13493 }
13494
13495 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13498 let mut selections = self.selections.all::<Point>(cx);
13499 let max_point = display_map.buffer_snapshot.max_point();
13500 for selection in &mut selections {
13501 let rows = selection.spanned_rows(true, &display_map);
13502 selection.start = Point::new(rows.start.0, 0);
13503 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13504 selection.reversed = false;
13505 }
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.select(selections);
13508 });
13509 }
13510
13511 pub fn split_selection_into_lines(
13512 &mut self,
13513 _: &SplitSelectionIntoLines,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 let selections = self
13518 .selections
13519 .all::<Point>(cx)
13520 .into_iter()
13521 .map(|selection| selection.start..selection.end)
13522 .collect::<Vec<_>>();
13523 self.unfold_ranges(&selections, true, true, cx);
13524
13525 let mut new_selection_ranges = Vec::new();
13526 {
13527 let buffer = self.buffer.read(cx).read(cx);
13528 for selection in selections {
13529 for row in selection.start.row..selection.end.row {
13530 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13531 new_selection_ranges.push(cursor..cursor);
13532 }
13533
13534 let is_multiline_selection = selection.start.row != selection.end.row;
13535 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13536 // so this action feels more ergonomic when paired with other selection operations
13537 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13538 if !should_skip_last {
13539 new_selection_ranges.push(selection.end..selection.end);
13540 }
13541 }
13542 }
13543 self.change_selections(Default::default(), window, cx, |s| {
13544 s.select_ranges(new_selection_ranges);
13545 });
13546 }
13547
13548 pub fn add_selection_above(
13549 &mut self,
13550 _: &AddSelectionAbove,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 self.add_selection(true, window, cx);
13555 }
13556
13557 pub fn add_selection_below(
13558 &mut self,
13559 _: &AddSelectionBelow,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 self.add_selection(false, window, cx);
13564 }
13565
13566 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13568
13569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13570 let all_selections = self.selections.all::<Point>(cx);
13571 let text_layout_details = self.text_layout_details(window);
13572
13573 let (mut columnar_selections, new_selections_to_columnarize) = {
13574 if let Some(state) = self.add_selections_state.as_ref() {
13575 let columnar_selection_ids: HashSet<_> = state
13576 .groups
13577 .iter()
13578 .flat_map(|group| group.stack.iter())
13579 .copied()
13580 .collect();
13581
13582 all_selections
13583 .into_iter()
13584 .partition(|s| columnar_selection_ids.contains(&s.id))
13585 } else {
13586 (Vec::new(), all_selections)
13587 }
13588 };
13589
13590 let mut state = self
13591 .add_selections_state
13592 .take()
13593 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13594
13595 for selection in new_selections_to_columnarize {
13596 let range = selection.display_range(&display_map).sorted();
13597 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13598 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13599 let positions = start_x.min(end_x)..start_x.max(end_x);
13600 let mut stack = Vec::new();
13601 for row in range.start.row().0..=range.end.row().0 {
13602 if let Some(selection) = self.selections.build_columnar_selection(
13603 &display_map,
13604 DisplayRow(row),
13605 &positions,
13606 selection.reversed,
13607 &text_layout_details,
13608 ) {
13609 stack.push(selection.id);
13610 columnar_selections.push(selection);
13611 }
13612 }
13613 if !stack.is_empty() {
13614 if above {
13615 stack.reverse();
13616 }
13617 state.groups.push(AddSelectionsGroup { above, stack });
13618 }
13619 }
13620
13621 let mut final_selections = Vec::new();
13622 let end_row = if above {
13623 DisplayRow(0)
13624 } else {
13625 display_map.max_point().row()
13626 };
13627
13628 let mut last_added_item_per_group = HashMap::default();
13629 for group in state.groups.iter_mut() {
13630 if let Some(last_id) = group.stack.last() {
13631 last_added_item_per_group.insert(*last_id, group);
13632 }
13633 }
13634
13635 for selection in columnar_selections {
13636 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13637 if above == group.above {
13638 let range = selection.display_range(&display_map).sorted();
13639 debug_assert_eq!(range.start.row(), range.end.row());
13640 let mut row = range.start.row();
13641 let positions =
13642 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13643 px(start)..px(end)
13644 } else {
13645 let start_x =
13646 display_map.x_for_display_point(range.start, &text_layout_details);
13647 let end_x =
13648 display_map.x_for_display_point(range.end, &text_layout_details);
13649 start_x.min(end_x)..start_x.max(end_x)
13650 };
13651
13652 let mut maybe_new_selection = None;
13653 while row != end_row {
13654 if above {
13655 row.0 -= 1;
13656 } else {
13657 row.0 += 1;
13658 }
13659 if let Some(new_selection) = self.selections.build_columnar_selection(
13660 &display_map,
13661 row,
13662 &positions,
13663 selection.reversed,
13664 &text_layout_details,
13665 ) {
13666 maybe_new_selection = Some(new_selection);
13667 break;
13668 }
13669 }
13670
13671 if let Some(new_selection) = maybe_new_selection {
13672 group.stack.push(new_selection.id);
13673 if above {
13674 final_selections.push(new_selection);
13675 final_selections.push(selection);
13676 } else {
13677 final_selections.push(selection);
13678 final_selections.push(new_selection);
13679 }
13680 } else {
13681 final_selections.push(selection);
13682 }
13683 } else {
13684 group.stack.pop();
13685 }
13686 } else {
13687 final_selections.push(selection);
13688 }
13689 }
13690
13691 self.change_selections(Default::default(), window, cx, |s| {
13692 s.select(final_selections);
13693 });
13694
13695 let final_selection_ids: HashSet<_> = self
13696 .selections
13697 .all::<Point>(cx)
13698 .iter()
13699 .map(|s| s.id)
13700 .collect();
13701 state.groups.retain_mut(|group| {
13702 // selections might get merged above so we remove invalid items from stacks
13703 group.stack.retain(|id| final_selection_ids.contains(id));
13704
13705 // single selection in stack can be treated as initial state
13706 group.stack.len() > 1
13707 });
13708
13709 if !state.groups.is_empty() {
13710 self.add_selections_state = Some(state);
13711 }
13712 }
13713
13714 fn select_match_ranges(
13715 &mut self,
13716 range: Range<usize>,
13717 reversed: bool,
13718 replace_newest: bool,
13719 auto_scroll: Option<Autoscroll>,
13720 window: &mut Window,
13721 cx: &mut Context<Editor>,
13722 ) {
13723 self.unfold_ranges(
13724 std::slice::from_ref(&range),
13725 false,
13726 auto_scroll.is_some(),
13727 cx,
13728 );
13729 let effects = if let Some(scroll) = auto_scroll {
13730 SelectionEffects::scroll(scroll)
13731 } else {
13732 SelectionEffects::no_scroll()
13733 };
13734 self.change_selections(effects, window, cx, |s| {
13735 if replace_newest {
13736 s.delete(s.newest_anchor().id);
13737 }
13738 if reversed {
13739 s.insert_range(range.end..range.start);
13740 } else {
13741 s.insert_range(range);
13742 }
13743 });
13744 }
13745
13746 pub fn select_next_match_internal(
13747 &mut self,
13748 display_map: &DisplaySnapshot,
13749 replace_newest: bool,
13750 autoscroll: Option<Autoscroll>,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) -> Result<()> {
13754 let buffer = &display_map.buffer_snapshot;
13755 let mut selections = self.selections.all::<usize>(cx);
13756 if let Some(mut select_next_state) = self.select_next_state.take() {
13757 let query = &select_next_state.query;
13758 if !select_next_state.done {
13759 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13760 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13761 let mut next_selected_range = None;
13762
13763 let bytes_after_last_selection =
13764 buffer.bytes_in_range(last_selection.end..buffer.len());
13765 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13766 let query_matches = query
13767 .stream_find_iter(bytes_after_last_selection)
13768 .map(|result| (last_selection.end, result))
13769 .chain(
13770 query
13771 .stream_find_iter(bytes_before_first_selection)
13772 .map(|result| (0, result)),
13773 );
13774
13775 for (start_offset, query_match) in query_matches {
13776 let query_match = query_match.unwrap(); // can only fail due to I/O
13777 let offset_range =
13778 start_offset + query_match.start()..start_offset + query_match.end();
13779
13780 if !select_next_state.wordwise
13781 || (!buffer.is_inside_word(offset_range.start, false)
13782 && !buffer.is_inside_word(offset_range.end, false))
13783 {
13784 // TODO: This is n^2, because we might check all the selections
13785 if !selections
13786 .iter()
13787 .any(|selection| selection.range().overlaps(&offset_range))
13788 {
13789 next_selected_range = Some(offset_range);
13790 break;
13791 }
13792 }
13793 }
13794
13795 if let Some(next_selected_range) = next_selected_range {
13796 self.select_match_ranges(
13797 next_selected_range,
13798 last_selection.reversed,
13799 replace_newest,
13800 autoscroll,
13801 window,
13802 cx,
13803 );
13804 } else {
13805 select_next_state.done = true;
13806 }
13807 }
13808
13809 self.select_next_state = Some(select_next_state);
13810 } else {
13811 let mut only_carets = true;
13812 let mut same_text_selected = true;
13813 let mut selected_text = None;
13814
13815 let mut selections_iter = selections.iter().peekable();
13816 while let Some(selection) = selections_iter.next() {
13817 if selection.start != selection.end {
13818 only_carets = false;
13819 }
13820
13821 if same_text_selected {
13822 if selected_text.is_none() {
13823 selected_text =
13824 Some(buffer.text_for_range(selection.range()).collect::<String>());
13825 }
13826
13827 if let Some(next_selection) = selections_iter.peek() {
13828 if next_selection.range().len() == selection.range().len() {
13829 let next_selected_text = buffer
13830 .text_for_range(next_selection.range())
13831 .collect::<String>();
13832 if Some(next_selected_text) != selected_text {
13833 same_text_selected = false;
13834 selected_text = None;
13835 }
13836 } else {
13837 same_text_selected = false;
13838 selected_text = None;
13839 }
13840 }
13841 }
13842 }
13843
13844 if only_carets {
13845 for selection in &mut selections {
13846 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13847 selection.start = word_range.start;
13848 selection.end = word_range.end;
13849 selection.goal = SelectionGoal::None;
13850 selection.reversed = false;
13851 self.select_match_ranges(
13852 selection.start..selection.end,
13853 selection.reversed,
13854 replace_newest,
13855 autoscroll,
13856 window,
13857 cx,
13858 );
13859 }
13860
13861 if selections.len() == 1 {
13862 let selection = selections
13863 .last()
13864 .expect("ensured that there's only one selection");
13865 let query = buffer
13866 .text_for_range(selection.start..selection.end)
13867 .collect::<String>();
13868 let is_empty = query.is_empty();
13869 let select_state = SelectNextState {
13870 query: AhoCorasick::new(&[query])?,
13871 wordwise: true,
13872 done: is_empty,
13873 };
13874 self.select_next_state = Some(select_state);
13875 } else {
13876 self.select_next_state = None;
13877 }
13878 } else if let Some(selected_text) = selected_text {
13879 self.select_next_state = Some(SelectNextState {
13880 query: AhoCorasick::new(&[selected_text])?,
13881 wordwise: false,
13882 done: false,
13883 });
13884 self.select_next_match_internal(
13885 display_map,
13886 replace_newest,
13887 autoscroll,
13888 window,
13889 cx,
13890 )?;
13891 }
13892 }
13893 Ok(())
13894 }
13895
13896 pub fn select_all_matches(
13897 &mut self,
13898 _action: &SelectAllMatches,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) -> Result<()> {
13902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13903
13904 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13905
13906 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13907 let Some(select_next_state) = self.select_next_state.as_mut() else {
13908 return Ok(());
13909 };
13910 if select_next_state.done {
13911 return Ok(());
13912 }
13913
13914 let mut new_selections = Vec::new();
13915
13916 let reversed = self.selections.oldest::<usize>(cx).reversed;
13917 let buffer = &display_map.buffer_snapshot;
13918 let query_matches = select_next_state
13919 .query
13920 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13921
13922 for query_match in query_matches.into_iter() {
13923 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13924 let offset_range = if reversed {
13925 query_match.end()..query_match.start()
13926 } else {
13927 query_match.start()..query_match.end()
13928 };
13929
13930 if !select_next_state.wordwise
13931 || (!buffer.is_inside_word(offset_range.start, false)
13932 && !buffer.is_inside_word(offset_range.end, false))
13933 {
13934 new_selections.push(offset_range.start..offset_range.end);
13935 }
13936 }
13937
13938 select_next_state.done = true;
13939
13940 if new_selections.is_empty() {
13941 log::error!("bug: new_selections is empty in select_all_matches");
13942 return Ok(());
13943 }
13944
13945 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13946 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13947 selections.select_ranges(new_selections)
13948 });
13949
13950 Ok(())
13951 }
13952
13953 pub fn select_next(
13954 &mut self,
13955 action: &SelectNext,
13956 window: &mut Window,
13957 cx: &mut Context<Self>,
13958 ) -> Result<()> {
13959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13960 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13961 self.select_next_match_internal(
13962 &display_map,
13963 action.replace_newest,
13964 Some(Autoscroll::newest()),
13965 window,
13966 cx,
13967 )?;
13968 Ok(())
13969 }
13970
13971 pub fn select_previous(
13972 &mut self,
13973 action: &SelectPrevious,
13974 window: &mut Window,
13975 cx: &mut Context<Self>,
13976 ) -> Result<()> {
13977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13979 let buffer = &display_map.buffer_snapshot;
13980 let mut selections = self.selections.all::<usize>(cx);
13981 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13982 let query = &select_prev_state.query;
13983 if !select_prev_state.done {
13984 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13985 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13986 let mut next_selected_range = None;
13987 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13988 let bytes_before_last_selection =
13989 buffer.reversed_bytes_in_range(0..last_selection.start);
13990 let bytes_after_first_selection =
13991 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13992 let query_matches = query
13993 .stream_find_iter(bytes_before_last_selection)
13994 .map(|result| (last_selection.start, result))
13995 .chain(
13996 query
13997 .stream_find_iter(bytes_after_first_selection)
13998 .map(|result| (buffer.len(), result)),
13999 );
14000 for (end_offset, query_match) in query_matches {
14001 let query_match = query_match.unwrap(); // can only fail due to I/O
14002 let offset_range =
14003 end_offset - query_match.end()..end_offset - query_match.start();
14004
14005 if !select_prev_state.wordwise
14006 || (!buffer.is_inside_word(offset_range.start, false)
14007 && !buffer.is_inside_word(offset_range.end, false))
14008 {
14009 next_selected_range = Some(offset_range);
14010 break;
14011 }
14012 }
14013
14014 if let Some(next_selected_range) = next_selected_range {
14015 self.select_match_ranges(
14016 next_selected_range,
14017 last_selection.reversed,
14018 action.replace_newest,
14019 Some(Autoscroll::newest()),
14020 window,
14021 cx,
14022 );
14023 } else {
14024 select_prev_state.done = true;
14025 }
14026 }
14027
14028 self.select_prev_state = Some(select_prev_state);
14029 } else {
14030 let mut only_carets = true;
14031 let mut same_text_selected = true;
14032 let mut selected_text = None;
14033
14034 let mut selections_iter = selections.iter().peekable();
14035 while let Some(selection) = selections_iter.next() {
14036 if selection.start != selection.end {
14037 only_carets = false;
14038 }
14039
14040 if same_text_selected {
14041 if selected_text.is_none() {
14042 selected_text =
14043 Some(buffer.text_for_range(selection.range()).collect::<String>());
14044 }
14045
14046 if let Some(next_selection) = selections_iter.peek() {
14047 if next_selection.range().len() == selection.range().len() {
14048 let next_selected_text = buffer
14049 .text_for_range(next_selection.range())
14050 .collect::<String>();
14051 if Some(next_selected_text) != selected_text {
14052 same_text_selected = false;
14053 selected_text = None;
14054 }
14055 } else {
14056 same_text_selected = false;
14057 selected_text = None;
14058 }
14059 }
14060 }
14061 }
14062
14063 if only_carets {
14064 for selection in &mut selections {
14065 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14066 selection.start = word_range.start;
14067 selection.end = word_range.end;
14068 selection.goal = SelectionGoal::None;
14069 selection.reversed = false;
14070 self.select_match_ranges(
14071 selection.start..selection.end,
14072 selection.reversed,
14073 action.replace_newest,
14074 Some(Autoscroll::newest()),
14075 window,
14076 cx,
14077 );
14078 }
14079 if selections.len() == 1 {
14080 let selection = selections
14081 .last()
14082 .expect("ensured that there's only one selection");
14083 let query = buffer
14084 .text_for_range(selection.start..selection.end)
14085 .collect::<String>();
14086 let is_empty = query.is_empty();
14087 let select_state = SelectNextState {
14088 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14089 wordwise: true,
14090 done: is_empty,
14091 };
14092 self.select_prev_state = Some(select_state);
14093 } else {
14094 self.select_prev_state = None;
14095 }
14096 } else if let Some(selected_text) = selected_text {
14097 self.select_prev_state = Some(SelectNextState {
14098 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14099 wordwise: false,
14100 done: false,
14101 });
14102 self.select_previous(action, window, cx)?;
14103 }
14104 }
14105 Ok(())
14106 }
14107
14108 pub fn find_next_match(
14109 &mut self,
14110 _: &FindNextMatch,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) -> Result<()> {
14114 let selections = self.selections.disjoint_anchors();
14115 match selections.first() {
14116 Some(first) if selections.len() >= 2 => {
14117 self.change_selections(Default::default(), window, cx, |s| {
14118 s.select_ranges([first.range()]);
14119 });
14120 }
14121 _ => self.select_next(
14122 &SelectNext {
14123 replace_newest: true,
14124 },
14125 window,
14126 cx,
14127 )?,
14128 }
14129 Ok(())
14130 }
14131
14132 pub fn find_previous_match(
14133 &mut self,
14134 _: &FindPreviousMatch,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) -> Result<()> {
14138 let selections = self.selections.disjoint_anchors();
14139 match selections.last() {
14140 Some(last) if selections.len() >= 2 => {
14141 self.change_selections(Default::default(), window, cx, |s| {
14142 s.select_ranges([last.range()]);
14143 });
14144 }
14145 _ => self.select_previous(
14146 &SelectPrevious {
14147 replace_newest: true,
14148 },
14149 window,
14150 cx,
14151 )?,
14152 }
14153 Ok(())
14154 }
14155
14156 pub fn toggle_comments(
14157 &mut self,
14158 action: &ToggleComments,
14159 window: &mut Window,
14160 cx: &mut Context<Self>,
14161 ) {
14162 if self.read_only(cx) {
14163 return;
14164 }
14165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14166 let text_layout_details = &self.text_layout_details(window);
14167 self.transact(window, cx, |this, window, cx| {
14168 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14169 let mut edits = Vec::new();
14170 let mut selection_edit_ranges = Vec::new();
14171 let mut last_toggled_row = None;
14172 let snapshot = this.buffer.read(cx).read(cx);
14173 let empty_str: Arc<str> = Arc::default();
14174 let mut suffixes_inserted = Vec::new();
14175 let ignore_indent = action.ignore_indent;
14176
14177 fn comment_prefix_range(
14178 snapshot: &MultiBufferSnapshot,
14179 row: MultiBufferRow,
14180 comment_prefix: &str,
14181 comment_prefix_whitespace: &str,
14182 ignore_indent: bool,
14183 ) -> Range<Point> {
14184 let indent_size = if ignore_indent {
14185 0
14186 } else {
14187 snapshot.indent_size_for_line(row).len
14188 };
14189
14190 let start = Point::new(row.0, indent_size);
14191
14192 let mut line_bytes = snapshot
14193 .bytes_in_range(start..snapshot.max_point())
14194 .flatten()
14195 .copied();
14196
14197 // If this line currently begins with the line comment prefix, then record
14198 // the range containing the prefix.
14199 if line_bytes
14200 .by_ref()
14201 .take(comment_prefix.len())
14202 .eq(comment_prefix.bytes())
14203 {
14204 // Include any whitespace that matches the comment prefix.
14205 let matching_whitespace_len = line_bytes
14206 .zip(comment_prefix_whitespace.bytes())
14207 .take_while(|(a, b)| a == b)
14208 .count() as u32;
14209 let end = Point::new(
14210 start.row,
14211 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14212 );
14213 start..end
14214 } else {
14215 start..start
14216 }
14217 }
14218
14219 fn comment_suffix_range(
14220 snapshot: &MultiBufferSnapshot,
14221 row: MultiBufferRow,
14222 comment_suffix: &str,
14223 comment_suffix_has_leading_space: bool,
14224 ) -> Range<Point> {
14225 let end = Point::new(row.0, snapshot.line_len(row));
14226 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14227
14228 let mut line_end_bytes = snapshot
14229 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14230 .flatten()
14231 .copied();
14232
14233 let leading_space_len = if suffix_start_column > 0
14234 && line_end_bytes.next() == Some(b' ')
14235 && comment_suffix_has_leading_space
14236 {
14237 1
14238 } else {
14239 0
14240 };
14241
14242 // If this line currently begins with the line comment prefix, then record
14243 // the range containing the prefix.
14244 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14245 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14246 start..end
14247 } else {
14248 end..end
14249 }
14250 }
14251
14252 // TODO: Handle selections that cross excerpts
14253 for selection in &mut selections {
14254 let start_column = snapshot
14255 .indent_size_for_line(MultiBufferRow(selection.start.row))
14256 .len;
14257 let language = if let Some(language) =
14258 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14259 {
14260 language
14261 } else {
14262 continue;
14263 };
14264
14265 selection_edit_ranges.clear();
14266
14267 // If multiple selections contain a given row, avoid processing that
14268 // row more than once.
14269 let mut start_row = MultiBufferRow(selection.start.row);
14270 if last_toggled_row == Some(start_row) {
14271 start_row = start_row.next_row();
14272 }
14273 let end_row =
14274 if selection.end.row > selection.start.row && selection.end.column == 0 {
14275 MultiBufferRow(selection.end.row - 1)
14276 } else {
14277 MultiBufferRow(selection.end.row)
14278 };
14279 last_toggled_row = Some(end_row);
14280
14281 if start_row > end_row {
14282 continue;
14283 }
14284
14285 // If the language has line comments, toggle those.
14286 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14287
14288 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14289 if ignore_indent {
14290 full_comment_prefixes = full_comment_prefixes
14291 .into_iter()
14292 .map(|s| Arc::from(s.trim_end()))
14293 .collect();
14294 }
14295
14296 if !full_comment_prefixes.is_empty() {
14297 let first_prefix = full_comment_prefixes
14298 .first()
14299 .expect("prefixes is non-empty");
14300 let prefix_trimmed_lengths = full_comment_prefixes
14301 .iter()
14302 .map(|p| p.trim_end_matches(' ').len())
14303 .collect::<SmallVec<[usize; 4]>>();
14304
14305 let mut all_selection_lines_are_comments = true;
14306
14307 for row in start_row.0..=end_row.0 {
14308 let row = MultiBufferRow(row);
14309 if start_row < end_row && snapshot.is_line_blank(row) {
14310 continue;
14311 }
14312
14313 let prefix_range = full_comment_prefixes
14314 .iter()
14315 .zip(prefix_trimmed_lengths.iter().copied())
14316 .map(|(prefix, trimmed_prefix_len)| {
14317 comment_prefix_range(
14318 snapshot.deref(),
14319 row,
14320 &prefix[..trimmed_prefix_len],
14321 &prefix[trimmed_prefix_len..],
14322 ignore_indent,
14323 )
14324 })
14325 .max_by_key(|range| range.end.column - range.start.column)
14326 .expect("prefixes is non-empty");
14327
14328 if prefix_range.is_empty() {
14329 all_selection_lines_are_comments = false;
14330 }
14331
14332 selection_edit_ranges.push(prefix_range);
14333 }
14334
14335 if all_selection_lines_are_comments {
14336 edits.extend(
14337 selection_edit_ranges
14338 .iter()
14339 .cloned()
14340 .map(|range| (range, empty_str.clone())),
14341 );
14342 } else {
14343 let min_column = selection_edit_ranges
14344 .iter()
14345 .map(|range| range.start.column)
14346 .min()
14347 .unwrap_or(0);
14348 edits.extend(selection_edit_ranges.iter().map(|range| {
14349 let position = Point::new(range.start.row, min_column);
14350 (position..position, first_prefix.clone())
14351 }));
14352 }
14353 } else if let Some(BlockCommentConfig {
14354 start: full_comment_prefix,
14355 end: comment_suffix,
14356 ..
14357 }) = language.block_comment()
14358 {
14359 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14360 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14361 let prefix_range = comment_prefix_range(
14362 snapshot.deref(),
14363 start_row,
14364 comment_prefix,
14365 comment_prefix_whitespace,
14366 ignore_indent,
14367 );
14368 let suffix_range = comment_suffix_range(
14369 snapshot.deref(),
14370 end_row,
14371 comment_suffix.trim_start_matches(' '),
14372 comment_suffix.starts_with(' '),
14373 );
14374
14375 if prefix_range.is_empty() || suffix_range.is_empty() {
14376 edits.push((
14377 prefix_range.start..prefix_range.start,
14378 full_comment_prefix.clone(),
14379 ));
14380 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14381 suffixes_inserted.push((end_row, comment_suffix.len()));
14382 } else {
14383 edits.push((prefix_range, empty_str.clone()));
14384 edits.push((suffix_range, empty_str.clone()));
14385 }
14386 } else {
14387 continue;
14388 }
14389 }
14390
14391 drop(snapshot);
14392 this.buffer.update(cx, |buffer, cx| {
14393 buffer.edit(edits, None, cx);
14394 });
14395
14396 // Adjust selections so that they end before any comment suffixes that
14397 // were inserted.
14398 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14399 let mut selections = this.selections.all::<Point>(cx);
14400 let snapshot = this.buffer.read(cx).read(cx);
14401 for selection in &mut selections {
14402 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14403 match row.cmp(&MultiBufferRow(selection.end.row)) {
14404 Ordering::Less => {
14405 suffixes_inserted.next();
14406 continue;
14407 }
14408 Ordering::Greater => break,
14409 Ordering::Equal => {
14410 if selection.end.column == snapshot.line_len(row) {
14411 if selection.is_empty() {
14412 selection.start.column -= suffix_len as u32;
14413 }
14414 selection.end.column -= suffix_len as u32;
14415 }
14416 break;
14417 }
14418 }
14419 }
14420 }
14421
14422 drop(snapshot);
14423 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14424
14425 let selections = this.selections.all::<Point>(cx);
14426 let selections_on_single_row = selections.windows(2).all(|selections| {
14427 selections[0].start.row == selections[1].start.row
14428 && selections[0].end.row == selections[1].end.row
14429 && selections[0].start.row == selections[0].end.row
14430 });
14431 let selections_selecting = selections
14432 .iter()
14433 .any(|selection| selection.start != selection.end);
14434 let advance_downwards = action.advance_downwards
14435 && selections_on_single_row
14436 && !selections_selecting
14437 && !matches!(this.mode, EditorMode::SingleLine { .. });
14438
14439 if advance_downwards {
14440 let snapshot = this.buffer.read(cx).snapshot(cx);
14441
14442 this.change_selections(Default::default(), window, cx, |s| {
14443 s.move_cursors_with(|display_snapshot, display_point, _| {
14444 let mut point = display_point.to_point(display_snapshot);
14445 point.row += 1;
14446 point = snapshot.clip_point(point, Bias::Left);
14447 let display_point = point.to_display_point(display_snapshot);
14448 let goal = SelectionGoal::HorizontalPosition(
14449 display_snapshot
14450 .x_for_display_point(display_point, text_layout_details)
14451 .into(),
14452 );
14453 (display_point, goal)
14454 })
14455 });
14456 }
14457 });
14458 }
14459
14460 pub fn select_enclosing_symbol(
14461 &mut self,
14462 _: &SelectEnclosingSymbol,
14463 window: &mut Window,
14464 cx: &mut Context<Self>,
14465 ) {
14466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14467
14468 let buffer = self.buffer.read(cx).snapshot(cx);
14469 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14470
14471 fn update_selection(
14472 selection: &Selection<usize>,
14473 buffer_snap: &MultiBufferSnapshot,
14474 ) -> Option<Selection<usize>> {
14475 let cursor = selection.head();
14476 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14477 for symbol in symbols.iter().rev() {
14478 let start = symbol.range.start.to_offset(buffer_snap);
14479 let end = symbol.range.end.to_offset(buffer_snap);
14480 let new_range = start..end;
14481 if start < selection.start || end > selection.end {
14482 return Some(Selection {
14483 id: selection.id,
14484 start: new_range.start,
14485 end: new_range.end,
14486 goal: SelectionGoal::None,
14487 reversed: selection.reversed,
14488 });
14489 }
14490 }
14491 None
14492 }
14493
14494 let mut selected_larger_symbol = false;
14495 let new_selections = old_selections
14496 .iter()
14497 .map(|selection| match update_selection(selection, &buffer) {
14498 Some(new_selection) => {
14499 if new_selection.range() != selection.range() {
14500 selected_larger_symbol = true;
14501 }
14502 new_selection
14503 }
14504 None => selection.clone(),
14505 })
14506 .collect::<Vec<_>>();
14507
14508 if selected_larger_symbol {
14509 self.change_selections(Default::default(), window, cx, |s| {
14510 s.select(new_selections);
14511 });
14512 }
14513 }
14514
14515 pub fn select_larger_syntax_node(
14516 &mut self,
14517 _: &SelectLargerSyntaxNode,
14518 window: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) {
14521 let Some(visible_row_count) = self.visible_row_count() else {
14522 return;
14523 };
14524 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14525 if old_selections.is_empty() {
14526 return;
14527 }
14528
14529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14530
14531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14532 let buffer = self.buffer.read(cx).snapshot(cx);
14533
14534 let mut selected_larger_node = false;
14535 let mut new_selections = old_selections
14536 .iter()
14537 .map(|selection| {
14538 let old_range = selection.start..selection.end;
14539
14540 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14541 // manually select word at selection
14542 if ["string_content", "inline"].contains(&node.kind()) {
14543 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14544 // ignore if word is already selected
14545 if !word_range.is_empty() && old_range != word_range {
14546 let (last_word_range, _) =
14547 buffer.surrounding_word(old_range.end, false);
14548 // only select word if start and end point belongs to same word
14549 if word_range == last_word_range {
14550 selected_larger_node = true;
14551 return Selection {
14552 id: selection.id,
14553 start: word_range.start,
14554 end: word_range.end,
14555 goal: SelectionGoal::None,
14556 reversed: selection.reversed,
14557 };
14558 }
14559 }
14560 }
14561 }
14562
14563 let mut new_range = old_range.clone();
14564 while let Some((_node, containing_range)) =
14565 buffer.syntax_ancestor(new_range.clone())
14566 {
14567 new_range = match containing_range {
14568 MultiOrSingleBufferOffsetRange::Single(_) => break,
14569 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14570 };
14571 if !display_map.intersects_fold(new_range.start)
14572 && !display_map.intersects_fold(new_range.end)
14573 {
14574 break;
14575 }
14576 }
14577
14578 selected_larger_node |= new_range != old_range;
14579 Selection {
14580 id: selection.id,
14581 start: new_range.start,
14582 end: new_range.end,
14583 goal: SelectionGoal::None,
14584 reversed: selection.reversed,
14585 }
14586 })
14587 .collect::<Vec<_>>();
14588
14589 if !selected_larger_node {
14590 return; // don't put this call in the history
14591 }
14592
14593 // scroll based on transformation done to the last selection created by the user
14594 let (last_old, last_new) = old_selections
14595 .last()
14596 .zip(new_selections.last().cloned())
14597 .expect("old_selections isn't empty");
14598
14599 // revert selection
14600 let is_selection_reversed = {
14601 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14602 new_selections.last_mut().expect("checked above").reversed =
14603 should_newest_selection_be_reversed;
14604 should_newest_selection_be_reversed
14605 };
14606
14607 if selected_larger_node {
14608 self.select_syntax_node_history.disable_clearing = true;
14609 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14610 s.select(new_selections.clone());
14611 });
14612 self.select_syntax_node_history.disable_clearing = false;
14613 }
14614
14615 let start_row = last_new.start.to_display_point(&display_map).row().0;
14616 let end_row = last_new.end.to_display_point(&display_map).row().0;
14617 let selection_height = end_row - start_row + 1;
14618 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14619
14620 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14621 let scroll_behavior = if fits_on_the_screen {
14622 self.request_autoscroll(Autoscroll::fit(), cx);
14623 SelectSyntaxNodeScrollBehavior::FitSelection
14624 } else if is_selection_reversed {
14625 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14626 SelectSyntaxNodeScrollBehavior::CursorTop
14627 } else {
14628 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14629 SelectSyntaxNodeScrollBehavior::CursorBottom
14630 };
14631
14632 self.select_syntax_node_history.push((
14633 old_selections,
14634 scroll_behavior,
14635 is_selection_reversed,
14636 ));
14637 }
14638
14639 pub fn select_smaller_syntax_node(
14640 &mut self,
14641 _: &SelectSmallerSyntaxNode,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646
14647 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14648 self.select_syntax_node_history.pop()
14649 {
14650 if let Some(selection) = selections.last_mut() {
14651 selection.reversed = is_selection_reversed;
14652 }
14653
14654 self.select_syntax_node_history.disable_clearing = true;
14655 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14656 s.select(selections.to_vec());
14657 });
14658 self.select_syntax_node_history.disable_clearing = false;
14659
14660 match scroll_behavior {
14661 SelectSyntaxNodeScrollBehavior::CursorTop => {
14662 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14663 }
14664 SelectSyntaxNodeScrollBehavior::FitSelection => {
14665 self.request_autoscroll(Autoscroll::fit(), cx);
14666 }
14667 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14668 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14669 }
14670 }
14671 }
14672 }
14673
14674 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14675 if !EditorSettings::get_global(cx).gutter.runnables {
14676 self.clear_tasks();
14677 return Task::ready(());
14678 }
14679 let project = self.project.as_ref().map(Entity::downgrade);
14680 let task_sources = self.lsp_task_sources(cx);
14681 let multi_buffer = self.buffer.downgrade();
14682 cx.spawn_in(window, async move |editor, cx| {
14683 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14684 let Some(project) = project.and_then(|p| p.upgrade()) else {
14685 return;
14686 };
14687 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14688 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14689 }) else {
14690 return;
14691 };
14692
14693 let hide_runnables = project
14694 .update(cx, |project, cx| {
14695 // Do not display any test indicators in non-dev server remote projects.
14696 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14697 })
14698 .unwrap_or(true);
14699 if hide_runnables {
14700 return;
14701 }
14702 let new_rows =
14703 cx.background_spawn({
14704 let snapshot = display_snapshot.clone();
14705 async move {
14706 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14707 }
14708 })
14709 .await;
14710 let Ok(lsp_tasks) =
14711 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14712 else {
14713 return;
14714 };
14715 let lsp_tasks = lsp_tasks.await;
14716
14717 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14718 lsp_tasks
14719 .into_iter()
14720 .flat_map(|(kind, tasks)| {
14721 tasks.into_iter().filter_map(move |(location, task)| {
14722 Some((kind.clone(), location?, task))
14723 })
14724 })
14725 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14726 let buffer = location.target.buffer;
14727 let buffer_snapshot = buffer.read(cx).snapshot();
14728 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14729 |(excerpt_id, snapshot, _)| {
14730 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14731 display_snapshot
14732 .buffer_snapshot
14733 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14734 } else {
14735 None
14736 }
14737 },
14738 );
14739 if let Some(offset) = offset {
14740 let task_buffer_range =
14741 location.target.range.to_point(&buffer_snapshot);
14742 let context_buffer_range =
14743 task_buffer_range.to_offset(&buffer_snapshot);
14744 let context_range = BufferOffset(context_buffer_range.start)
14745 ..BufferOffset(context_buffer_range.end);
14746
14747 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14748 .or_insert_with(|| RunnableTasks {
14749 templates: Vec::new(),
14750 offset,
14751 column: task_buffer_range.start.column,
14752 extra_variables: HashMap::default(),
14753 context_range,
14754 })
14755 .templates
14756 .push((kind, task.original_task().clone()));
14757 }
14758
14759 acc
14760 })
14761 }) else {
14762 return;
14763 };
14764
14765 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14766 buffer.language_settings(cx).tasks.prefer_lsp
14767 }) else {
14768 return;
14769 };
14770
14771 let rows = Self::runnable_rows(
14772 project,
14773 display_snapshot,
14774 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14775 new_rows,
14776 cx.clone(),
14777 )
14778 .await;
14779 editor
14780 .update(cx, |editor, _| {
14781 editor.clear_tasks();
14782 for (key, mut value) in rows {
14783 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14784 value.templates.extend(lsp_tasks.templates);
14785 }
14786
14787 editor.insert_tasks(key, value);
14788 }
14789 for (key, value) in lsp_tasks_by_rows {
14790 editor.insert_tasks(key, value);
14791 }
14792 })
14793 .ok();
14794 })
14795 }
14796 fn fetch_runnable_ranges(
14797 snapshot: &DisplaySnapshot,
14798 range: Range<Anchor>,
14799 ) -> Vec<language::RunnableRange> {
14800 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14801 }
14802
14803 fn runnable_rows(
14804 project: Entity<Project>,
14805 snapshot: DisplaySnapshot,
14806 prefer_lsp: bool,
14807 runnable_ranges: Vec<RunnableRange>,
14808 cx: AsyncWindowContext,
14809 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14810 cx.spawn(async move |cx| {
14811 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14812 for mut runnable in runnable_ranges {
14813 let Some(tasks) = cx
14814 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14815 .ok()
14816 else {
14817 continue;
14818 };
14819 let mut tasks = tasks.await;
14820
14821 if prefer_lsp {
14822 tasks.retain(|(task_kind, _)| {
14823 !matches!(task_kind, TaskSourceKind::Language { .. })
14824 });
14825 }
14826 if tasks.is_empty() {
14827 continue;
14828 }
14829
14830 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14831 let Some(row) = snapshot
14832 .buffer_snapshot
14833 .buffer_line_for_row(MultiBufferRow(point.row))
14834 .map(|(_, range)| range.start.row)
14835 else {
14836 continue;
14837 };
14838
14839 let context_range =
14840 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14841 runnable_rows.push((
14842 (runnable.buffer_id, row),
14843 RunnableTasks {
14844 templates: tasks,
14845 offset: snapshot
14846 .buffer_snapshot
14847 .anchor_before(runnable.run_range.start),
14848 context_range,
14849 column: point.column,
14850 extra_variables: runnable.extra_captures,
14851 },
14852 ));
14853 }
14854 runnable_rows
14855 })
14856 }
14857
14858 fn templates_with_tags(
14859 project: &Entity<Project>,
14860 runnable: &mut Runnable,
14861 cx: &mut App,
14862 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14863 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14864 let (worktree_id, file) = project
14865 .buffer_for_id(runnable.buffer, cx)
14866 .and_then(|buffer| buffer.read(cx).file())
14867 .map(|file| (file.worktree_id(cx), file.clone()))
14868 .unzip();
14869
14870 (
14871 project.task_store().read(cx).task_inventory().cloned(),
14872 worktree_id,
14873 file,
14874 )
14875 });
14876
14877 let tags = mem::take(&mut runnable.tags);
14878 let language = runnable.language.clone();
14879 cx.spawn(async move |cx| {
14880 let mut templates_with_tags = Vec::new();
14881 if let Some(inventory) = inventory {
14882 for RunnableTag(tag) in tags {
14883 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14884 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14885 }) else {
14886 return templates_with_tags;
14887 };
14888 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14889 move |(_, template)| {
14890 template.tags.iter().any(|source_tag| source_tag == &tag)
14891 },
14892 ));
14893 }
14894 }
14895 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14896
14897 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14898 // Strongest source wins; if we have worktree tag binding, prefer that to
14899 // global and language bindings;
14900 // if we have a global binding, prefer that to language binding.
14901 let first_mismatch = templates_with_tags
14902 .iter()
14903 .position(|(tag_source, _)| tag_source != leading_tag_source);
14904 if let Some(index) = first_mismatch {
14905 templates_with_tags.truncate(index);
14906 }
14907 }
14908
14909 templates_with_tags
14910 })
14911 }
14912
14913 pub fn move_to_enclosing_bracket(
14914 &mut self,
14915 _: &MoveToEnclosingBracket,
14916 window: &mut Window,
14917 cx: &mut Context<Self>,
14918 ) {
14919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14920 self.change_selections(Default::default(), window, cx, |s| {
14921 s.move_offsets_with(|snapshot, selection| {
14922 let Some(enclosing_bracket_ranges) =
14923 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14924 else {
14925 return;
14926 };
14927
14928 let mut best_length = usize::MAX;
14929 let mut best_inside = false;
14930 let mut best_in_bracket_range = false;
14931 let mut best_destination = None;
14932 for (open, close) in enclosing_bracket_ranges {
14933 let close = close.to_inclusive();
14934 let length = close.end() - open.start;
14935 let inside = selection.start >= open.end && selection.end <= *close.start();
14936 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14937 || close.contains(&selection.head());
14938
14939 // If best is next to a bracket and current isn't, skip
14940 if !in_bracket_range && best_in_bracket_range {
14941 continue;
14942 }
14943
14944 // Prefer smaller lengths unless best is inside and current isn't
14945 if length > best_length && (best_inside || !inside) {
14946 continue;
14947 }
14948
14949 best_length = length;
14950 best_inside = inside;
14951 best_in_bracket_range = in_bracket_range;
14952 best_destination = Some(
14953 if close.contains(&selection.start) && close.contains(&selection.end) {
14954 if inside { open.end } else { open.start }
14955 } else if inside {
14956 *close.start()
14957 } else {
14958 *close.end()
14959 },
14960 );
14961 }
14962
14963 if let Some(destination) = best_destination {
14964 selection.collapse_to(destination, SelectionGoal::None);
14965 }
14966 })
14967 });
14968 }
14969
14970 pub fn undo_selection(
14971 &mut self,
14972 _: &UndoSelection,
14973 window: &mut Window,
14974 cx: &mut Context<Self>,
14975 ) {
14976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14977 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14978 self.selection_history.mode = SelectionHistoryMode::Undoing;
14979 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14980 this.end_selection(window, cx);
14981 this.change_selections(
14982 SelectionEffects::scroll(Autoscroll::newest()),
14983 window,
14984 cx,
14985 |s| s.select_anchors(entry.selections.to_vec()),
14986 );
14987 });
14988 self.selection_history.mode = SelectionHistoryMode::Normal;
14989
14990 self.select_next_state = entry.select_next_state;
14991 self.select_prev_state = entry.select_prev_state;
14992 self.add_selections_state = entry.add_selections_state;
14993 }
14994 }
14995
14996 pub fn redo_selection(
14997 &mut self,
14998 _: &RedoSelection,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15003 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15004 self.selection_history.mode = SelectionHistoryMode::Redoing;
15005 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15006 this.end_selection(window, cx);
15007 this.change_selections(
15008 SelectionEffects::scroll(Autoscroll::newest()),
15009 window,
15010 cx,
15011 |s| s.select_anchors(entry.selections.to_vec()),
15012 );
15013 });
15014 self.selection_history.mode = SelectionHistoryMode::Normal;
15015
15016 self.select_next_state = entry.select_next_state;
15017 self.select_prev_state = entry.select_prev_state;
15018 self.add_selections_state = entry.add_selections_state;
15019 }
15020 }
15021
15022 pub fn expand_excerpts(
15023 &mut self,
15024 action: &ExpandExcerpts,
15025 _: &mut Window,
15026 cx: &mut Context<Self>,
15027 ) {
15028 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15029 }
15030
15031 pub fn expand_excerpts_down(
15032 &mut self,
15033 action: &ExpandExcerptsDown,
15034 _: &mut Window,
15035 cx: &mut Context<Self>,
15036 ) {
15037 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15038 }
15039
15040 pub fn expand_excerpts_up(
15041 &mut self,
15042 action: &ExpandExcerptsUp,
15043 _: &mut Window,
15044 cx: &mut Context<Self>,
15045 ) {
15046 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15047 }
15048
15049 pub fn expand_excerpts_for_direction(
15050 &mut self,
15051 lines: u32,
15052 direction: ExpandExcerptDirection,
15053
15054 cx: &mut Context<Self>,
15055 ) {
15056 let selections = self.selections.disjoint_anchors();
15057
15058 let lines = if lines == 0 {
15059 EditorSettings::get_global(cx).expand_excerpt_lines
15060 } else {
15061 lines
15062 };
15063
15064 self.buffer.update(cx, |buffer, cx| {
15065 let snapshot = buffer.snapshot(cx);
15066 let mut excerpt_ids = selections
15067 .iter()
15068 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15069 .collect::<Vec<_>>();
15070 excerpt_ids.sort();
15071 excerpt_ids.dedup();
15072 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15073 })
15074 }
15075
15076 pub fn expand_excerpt(
15077 &mut self,
15078 excerpt: ExcerptId,
15079 direction: ExpandExcerptDirection,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 let current_scroll_position = self.scroll_position(cx);
15084 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15085 let mut should_scroll_up = false;
15086
15087 if direction == ExpandExcerptDirection::Down {
15088 let multi_buffer = self.buffer.read(cx);
15089 let snapshot = multi_buffer.snapshot(cx);
15090 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15091 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15092 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15093 let buffer_snapshot = buffer.read(cx).snapshot();
15094 let excerpt_end_row =
15095 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15096 let last_row = buffer_snapshot.max_point().row;
15097 let lines_below = last_row.saturating_sub(excerpt_end_row);
15098 should_scroll_up = lines_below >= lines_to_expand;
15099 }
15100 }
15101 }
15102 }
15103
15104 self.buffer.update(cx, |buffer, cx| {
15105 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15106 });
15107
15108 if should_scroll_up {
15109 let new_scroll_position =
15110 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15111 self.set_scroll_position(new_scroll_position, window, cx);
15112 }
15113 }
15114
15115 pub fn go_to_singleton_buffer_point(
15116 &mut self,
15117 point: Point,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) {
15121 self.go_to_singleton_buffer_range(point..point, window, cx);
15122 }
15123
15124 pub fn go_to_singleton_buffer_range(
15125 &mut self,
15126 range: Range<Point>,
15127 window: &mut Window,
15128 cx: &mut Context<Self>,
15129 ) {
15130 let multibuffer = self.buffer().read(cx);
15131 let Some(buffer) = multibuffer.as_singleton() else {
15132 return;
15133 };
15134 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15135 return;
15136 };
15137 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15138 return;
15139 };
15140 self.change_selections(
15141 SelectionEffects::default().nav_history(true),
15142 window,
15143 cx,
15144 |s| s.select_anchor_ranges([start..end]),
15145 );
15146 }
15147
15148 pub fn go_to_diagnostic(
15149 &mut self,
15150 action: &GoToDiagnostic,
15151 window: &mut Window,
15152 cx: &mut Context<Self>,
15153 ) {
15154 if !self.diagnostics_enabled() {
15155 return;
15156 }
15157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15158 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15159 }
15160
15161 pub fn go_to_prev_diagnostic(
15162 &mut self,
15163 action: &GoToPreviousDiagnostic,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) {
15167 if !self.diagnostics_enabled() {
15168 return;
15169 }
15170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15171 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15172 }
15173
15174 pub fn go_to_diagnostic_impl(
15175 &mut self,
15176 direction: Direction,
15177 severity: GoToDiagnosticSeverityFilter,
15178 window: &mut Window,
15179 cx: &mut Context<Self>,
15180 ) {
15181 let buffer = self.buffer.read(cx).snapshot(cx);
15182 let selection = self.selections.newest::<usize>(cx);
15183
15184 let mut active_group_id = None;
15185 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15186 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15187 active_group_id = Some(active_group.group_id);
15188 }
15189 }
15190
15191 fn filtered(
15192 snapshot: EditorSnapshot,
15193 severity: GoToDiagnosticSeverityFilter,
15194 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15195 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15196 diagnostics
15197 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15198 .filter(|entry| entry.range.start != entry.range.end)
15199 .filter(|entry| !entry.diagnostic.is_unnecessary)
15200 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15201 }
15202
15203 let snapshot = self.snapshot(window, cx);
15204 let before = filtered(
15205 snapshot.clone(),
15206 severity,
15207 buffer
15208 .diagnostics_in_range(0..selection.start)
15209 .filter(|entry| entry.range.start <= selection.start),
15210 );
15211 let after = filtered(
15212 snapshot,
15213 severity,
15214 buffer
15215 .diagnostics_in_range(selection.start..buffer.len())
15216 .filter(|entry| entry.range.start >= selection.start),
15217 );
15218
15219 let mut found: Option<DiagnosticEntry<usize>> = None;
15220 if direction == Direction::Prev {
15221 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15222 {
15223 for diagnostic in prev_diagnostics.into_iter().rev() {
15224 if diagnostic.range.start != selection.start
15225 || active_group_id
15226 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15227 {
15228 found = Some(diagnostic);
15229 break 'outer;
15230 }
15231 }
15232 }
15233 } else {
15234 for diagnostic in after.chain(before) {
15235 if diagnostic.range.start != selection.start
15236 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15237 {
15238 found = Some(diagnostic);
15239 break;
15240 }
15241 }
15242 }
15243 let Some(next_diagnostic) = found else {
15244 return;
15245 };
15246
15247 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15248 return;
15249 };
15250 self.change_selections(Default::default(), window, cx, |s| {
15251 s.select_ranges(vec![
15252 next_diagnostic.range.start..next_diagnostic.range.start,
15253 ])
15254 });
15255 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15256 self.refresh_inline_completion(false, true, window, cx);
15257 }
15258
15259 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15260 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15261 let snapshot = self.snapshot(window, cx);
15262 let selection = self.selections.newest::<Point>(cx);
15263 self.go_to_hunk_before_or_after_position(
15264 &snapshot,
15265 selection.head(),
15266 Direction::Next,
15267 window,
15268 cx,
15269 );
15270 }
15271
15272 pub fn go_to_hunk_before_or_after_position(
15273 &mut self,
15274 snapshot: &EditorSnapshot,
15275 position: Point,
15276 direction: Direction,
15277 window: &mut Window,
15278 cx: &mut Context<Editor>,
15279 ) {
15280 let row = if direction == Direction::Next {
15281 self.hunk_after_position(snapshot, position)
15282 .map(|hunk| hunk.row_range.start)
15283 } else {
15284 self.hunk_before_position(snapshot, position)
15285 };
15286
15287 if let Some(row) = row {
15288 let destination = Point::new(row.0, 0);
15289 let autoscroll = Autoscroll::center();
15290
15291 self.unfold_ranges(&[destination..destination], false, false, cx);
15292 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15293 s.select_ranges([destination..destination]);
15294 });
15295 }
15296 }
15297
15298 fn hunk_after_position(
15299 &mut self,
15300 snapshot: &EditorSnapshot,
15301 position: Point,
15302 ) -> Option<MultiBufferDiffHunk> {
15303 snapshot
15304 .buffer_snapshot
15305 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15306 .find(|hunk| hunk.row_range.start.0 > position.row)
15307 .or_else(|| {
15308 snapshot
15309 .buffer_snapshot
15310 .diff_hunks_in_range(Point::zero()..position)
15311 .find(|hunk| hunk.row_range.end.0 < position.row)
15312 })
15313 }
15314
15315 fn go_to_prev_hunk(
15316 &mut self,
15317 _: &GoToPreviousHunk,
15318 window: &mut Window,
15319 cx: &mut Context<Self>,
15320 ) {
15321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15322 let snapshot = self.snapshot(window, cx);
15323 let selection = self.selections.newest::<Point>(cx);
15324 self.go_to_hunk_before_or_after_position(
15325 &snapshot,
15326 selection.head(),
15327 Direction::Prev,
15328 window,
15329 cx,
15330 );
15331 }
15332
15333 fn hunk_before_position(
15334 &mut self,
15335 snapshot: &EditorSnapshot,
15336 position: Point,
15337 ) -> Option<MultiBufferRow> {
15338 snapshot
15339 .buffer_snapshot
15340 .diff_hunk_before(position)
15341 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15342 }
15343
15344 fn go_to_next_change(
15345 &mut self,
15346 _: &GoToNextChange,
15347 window: &mut Window,
15348 cx: &mut Context<Self>,
15349 ) {
15350 if let Some(selections) = self
15351 .change_list
15352 .next_change(1, Direction::Next)
15353 .map(|s| s.to_vec())
15354 {
15355 self.change_selections(Default::default(), window, cx, |s| {
15356 let map = s.display_map();
15357 s.select_display_ranges(selections.iter().map(|a| {
15358 let point = a.to_display_point(&map);
15359 point..point
15360 }))
15361 })
15362 }
15363 }
15364
15365 fn go_to_previous_change(
15366 &mut self,
15367 _: &GoToPreviousChange,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 if let Some(selections) = self
15372 .change_list
15373 .next_change(1, Direction::Prev)
15374 .map(|s| s.to_vec())
15375 {
15376 self.change_selections(Default::default(), window, cx, |s| {
15377 let map = s.display_map();
15378 s.select_display_ranges(selections.iter().map(|a| {
15379 let point = a.to_display_point(&map);
15380 point..point
15381 }))
15382 })
15383 }
15384 }
15385
15386 fn go_to_line<T: 'static>(
15387 &mut self,
15388 position: Anchor,
15389 highlight_color: Option<Hsla>,
15390 window: &mut Window,
15391 cx: &mut Context<Self>,
15392 ) {
15393 let snapshot = self.snapshot(window, cx).display_snapshot;
15394 let position = position.to_point(&snapshot.buffer_snapshot);
15395 let start = snapshot
15396 .buffer_snapshot
15397 .clip_point(Point::new(position.row, 0), Bias::Left);
15398 let end = start + Point::new(1, 0);
15399 let start = snapshot.buffer_snapshot.anchor_before(start);
15400 let end = snapshot.buffer_snapshot.anchor_before(end);
15401
15402 self.highlight_rows::<T>(
15403 start..end,
15404 highlight_color
15405 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15406 Default::default(),
15407 cx,
15408 );
15409
15410 if self.buffer.read(cx).is_singleton() {
15411 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15412 }
15413 }
15414
15415 pub fn go_to_definition(
15416 &mut self,
15417 _: &GoToDefinition,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) -> Task<Result<Navigated>> {
15421 let definition =
15422 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15423 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15424 cx.spawn_in(window, async move |editor, cx| {
15425 if definition.await? == Navigated::Yes {
15426 return Ok(Navigated::Yes);
15427 }
15428 match fallback_strategy {
15429 GoToDefinitionFallback::None => Ok(Navigated::No),
15430 GoToDefinitionFallback::FindAllReferences => {
15431 match editor.update_in(cx, |editor, window, cx| {
15432 editor.find_all_references(&FindAllReferences, window, cx)
15433 })? {
15434 Some(references) => references.await,
15435 None => Ok(Navigated::No),
15436 }
15437 }
15438 }
15439 })
15440 }
15441
15442 pub fn go_to_declaration(
15443 &mut self,
15444 _: &GoToDeclaration,
15445 window: &mut Window,
15446 cx: &mut Context<Self>,
15447 ) -> Task<Result<Navigated>> {
15448 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15449 }
15450
15451 pub fn go_to_declaration_split(
15452 &mut self,
15453 _: &GoToDeclaration,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) -> Task<Result<Navigated>> {
15457 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15458 }
15459
15460 pub fn go_to_implementation(
15461 &mut self,
15462 _: &GoToImplementation,
15463 window: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) -> Task<Result<Navigated>> {
15466 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15467 }
15468
15469 pub fn go_to_implementation_split(
15470 &mut self,
15471 _: &GoToImplementationSplit,
15472 window: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) -> Task<Result<Navigated>> {
15475 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15476 }
15477
15478 pub fn go_to_type_definition(
15479 &mut self,
15480 _: &GoToTypeDefinition,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) -> Task<Result<Navigated>> {
15484 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15485 }
15486
15487 pub fn go_to_definition_split(
15488 &mut self,
15489 _: &GoToDefinitionSplit,
15490 window: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) -> Task<Result<Navigated>> {
15493 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15494 }
15495
15496 pub fn go_to_type_definition_split(
15497 &mut self,
15498 _: &GoToTypeDefinitionSplit,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) -> Task<Result<Navigated>> {
15502 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15503 }
15504
15505 fn go_to_definition_of_kind(
15506 &mut self,
15507 kind: GotoDefinitionKind,
15508 split: bool,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) -> Task<Result<Navigated>> {
15512 let Some(provider) = self.semantics_provider.clone() else {
15513 return Task::ready(Ok(Navigated::No));
15514 };
15515 let head = self.selections.newest::<usize>(cx).head();
15516 let buffer = self.buffer.read(cx);
15517 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15518 text_anchor
15519 } else {
15520 return Task::ready(Ok(Navigated::No));
15521 };
15522
15523 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15524 return Task::ready(Ok(Navigated::No));
15525 };
15526
15527 cx.spawn_in(window, async move |editor, cx| {
15528 let definitions = definitions.await?;
15529 let navigated = editor
15530 .update_in(cx, |editor, window, cx| {
15531 editor.navigate_to_hover_links(
15532 Some(kind),
15533 definitions
15534 .into_iter()
15535 .filter(|location| {
15536 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15537 })
15538 .map(HoverLink::Text)
15539 .collect::<Vec<_>>(),
15540 split,
15541 window,
15542 cx,
15543 )
15544 })?
15545 .await?;
15546 anyhow::Ok(navigated)
15547 })
15548 }
15549
15550 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15551 let selection = self.selections.newest_anchor();
15552 let head = selection.head();
15553 let tail = selection.tail();
15554
15555 let Some((buffer, start_position)) =
15556 self.buffer.read(cx).text_anchor_for_position(head, cx)
15557 else {
15558 return;
15559 };
15560
15561 let end_position = if head != tail {
15562 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15563 return;
15564 };
15565 Some(pos)
15566 } else {
15567 None
15568 };
15569
15570 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15571 let url = if let Some(end_pos) = end_position {
15572 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15573 } else {
15574 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15575 };
15576
15577 if let Some(url) = url {
15578 editor.update(cx, |_, cx| {
15579 cx.open_url(&url);
15580 })
15581 } else {
15582 Ok(())
15583 }
15584 });
15585
15586 url_finder.detach();
15587 }
15588
15589 pub fn open_selected_filename(
15590 &mut self,
15591 _: &OpenSelectedFilename,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 let Some(workspace) = self.workspace() else {
15596 return;
15597 };
15598
15599 let position = self.selections.newest_anchor().head();
15600
15601 let Some((buffer, buffer_position)) =
15602 self.buffer.read(cx).text_anchor_for_position(position, cx)
15603 else {
15604 return;
15605 };
15606
15607 let project = self.project.clone();
15608
15609 cx.spawn_in(window, async move |_, cx| {
15610 let result = find_file(&buffer, project, buffer_position, cx).await;
15611
15612 if let Some((_, path)) = result {
15613 workspace
15614 .update_in(cx, |workspace, window, cx| {
15615 workspace.open_resolved_path(path, window, cx)
15616 })?
15617 .await?;
15618 }
15619 anyhow::Ok(())
15620 })
15621 .detach();
15622 }
15623
15624 pub(crate) fn navigate_to_hover_links(
15625 &mut self,
15626 kind: Option<GotoDefinitionKind>,
15627 mut definitions: Vec<HoverLink>,
15628 split: bool,
15629 window: &mut Window,
15630 cx: &mut Context<Editor>,
15631 ) -> Task<Result<Navigated>> {
15632 // If there is one definition, just open it directly
15633 if definitions.len() == 1 {
15634 let definition = definitions.pop().unwrap();
15635
15636 enum TargetTaskResult {
15637 Location(Option<Location>),
15638 AlreadyNavigated,
15639 }
15640
15641 let target_task = match definition {
15642 HoverLink::Text(link) => {
15643 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15644 }
15645 HoverLink::InlayHint(lsp_location, server_id) => {
15646 let computation =
15647 self.compute_target_location(lsp_location, server_id, window, cx);
15648 cx.background_spawn(async move {
15649 let location = computation.await?;
15650 Ok(TargetTaskResult::Location(location))
15651 })
15652 }
15653 HoverLink::Url(url) => {
15654 cx.open_url(&url);
15655 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15656 }
15657 HoverLink::File(path) => {
15658 if let Some(workspace) = self.workspace() {
15659 cx.spawn_in(window, async move |_, cx| {
15660 workspace
15661 .update_in(cx, |workspace, window, cx| {
15662 workspace.open_resolved_path(path, window, cx)
15663 })?
15664 .await
15665 .map(|_| TargetTaskResult::AlreadyNavigated)
15666 })
15667 } else {
15668 Task::ready(Ok(TargetTaskResult::Location(None)))
15669 }
15670 }
15671 };
15672 cx.spawn_in(window, async move |editor, cx| {
15673 let target = match target_task.await.context("target resolution task")? {
15674 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15675 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15676 TargetTaskResult::Location(Some(target)) => target,
15677 };
15678
15679 editor.update_in(cx, |editor, window, cx| {
15680 let Some(workspace) = editor.workspace() else {
15681 return Navigated::No;
15682 };
15683 let pane = workspace.read(cx).active_pane().clone();
15684
15685 let range = target.range.to_point(target.buffer.read(cx));
15686 let range = editor.range_for_match(&range);
15687 let range = collapse_multiline_range(range);
15688
15689 if !split
15690 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15691 {
15692 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15693 } else {
15694 window.defer(cx, move |window, cx| {
15695 let target_editor: Entity<Self> =
15696 workspace.update(cx, |workspace, cx| {
15697 let pane = if split {
15698 workspace.adjacent_pane(window, cx)
15699 } else {
15700 workspace.active_pane().clone()
15701 };
15702
15703 workspace.open_project_item(
15704 pane,
15705 target.buffer.clone(),
15706 true,
15707 true,
15708 window,
15709 cx,
15710 )
15711 });
15712 target_editor.update(cx, |target_editor, cx| {
15713 // When selecting a definition in a different buffer, disable the nav history
15714 // to avoid creating a history entry at the previous cursor location.
15715 pane.update(cx, |pane, _| pane.disable_history());
15716 target_editor.go_to_singleton_buffer_range(range, window, cx);
15717 pane.update(cx, |pane, _| pane.enable_history());
15718 });
15719 });
15720 }
15721 Navigated::Yes
15722 })
15723 })
15724 } else if !definitions.is_empty() {
15725 cx.spawn_in(window, async move |editor, cx| {
15726 let (title, location_tasks, workspace) = editor
15727 .update_in(cx, |editor, window, cx| {
15728 let tab_kind = match kind {
15729 Some(GotoDefinitionKind::Implementation) => "Implementations",
15730 _ => "Definitions",
15731 };
15732 let title = definitions
15733 .iter()
15734 .find_map(|definition| match definition {
15735 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15736 let buffer = origin.buffer.read(cx);
15737 format!(
15738 "{} for {}",
15739 tab_kind,
15740 buffer
15741 .text_for_range(origin.range.clone())
15742 .collect::<String>()
15743 )
15744 }),
15745 HoverLink::InlayHint(_, _) => None,
15746 HoverLink::Url(_) => None,
15747 HoverLink::File(_) => None,
15748 })
15749 .unwrap_or(tab_kind.to_string());
15750 let location_tasks = definitions
15751 .into_iter()
15752 .map(|definition| match definition {
15753 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15754 HoverLink::InlayHint(lsp_location, server_id) => editor
15755 .compute_target_location(lsp_location, server_id, window, cx),
15756 HoverLink::Url(_) => Task::ready(Ok(None)),
15757 HoverLink::File(_) => Task::ready(Ok(None)),
15758 })
15759 .collect::<Vec<_>>();
15760 (title, location_tasks, editor.workspace().clone())
15761 })
15762 .context("location tasks preparation")?;
15763
15764 let locations: Vec<Location> = future::join_all(location_tasks)
15765 .await
15766 .into_iter()
15767 .filter_map(|location| location.transpose())
15768 .collect::<Result<_>>()
15769 .context("location tasks")?;
15770
15771 if locations.is_empty() {
15772 return Ok(Navigated::No);
15773 }
15774
15775 let Some(workspace) = workspace else {
15776 return Ok(Navigated::No);
15777 };
15778
15779 let opened = workspace
15780 .update_in(cx, |workspace, window, cx| {
15781 Self::open_locations_in_multibuffer(
15782 workspace,
15783 locations,
15784 title,
15785 split,
15786 MultibufferSelectionMode::First,
15787 window,
15788 cx,
15789 )
15790 })
15791 .ok();
15792
15793 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15794 })
15795 } else {
15796 Task::ready(Ok(Navigated::No))
15797 }
15798 }
15799
15800 fn compute_target_location(
15801 &self,
15802 lsp_location: lsp::Location,
15803 server_id: LanguageServerId,
15804 window: &mut Window,
15805 cx: &mut Context<Self>,
15806 ) -> Task<anyhow::Result<Option<Location>>> {
15807 let Some(project) = self.project.clone() else {
15808 return Task::ready(Ok(None));
15809 };
15810
15811 cx.spawn_in(window, async move |editor, cx| {
15812 let location_task = editor.update(cx, |_, cx| {
15813 project.update(cx, |project, cx| {
15814 let language_server_name = project
15815 .language_server_statuses(cx)
15816 .find(|(id, _)| server_id == *id)
15817 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15818 language_server_name.map(|language_server_name| {
15819 project.open_local_buffer_via_lsp(
15820 lsp_location.uri.clone(),
15821 server_id,
15822 language_server_name,
15823 cx,
15824 )
15825 })
15826 })
15827 })?;
15828 let location = match location_task {
15829 Some(task) => Some({
15830 let target_buffer_handle = task.await.context("open local buffer")?;
15831 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15832 let target_start = target_buffer
15833 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15834 let target_end = target_buffer
15835 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15836 target_buffer.anchor_after(target_start)
15837 ..target_buffer.anchor_before(target_end)
15838 })?;
15839 Location {
15840 buffer: target_buffer_handle,
15841 range,
15842 }
15843 }),
15844 None => None,
15845 };
15846 Ok(location)
15847 })
15848 }
15849
15850 pub fn find_all_references(
15851 &mut self,
15852 _: &FindAllReferences,
15853 window: &mut Window,
15854 cx: &mut Context<Self>,
15855 ) -> Option<Task<Result<Navigated>>> {
15856 let selection = self.selections.newest::<usize>(cx);
15857 let multi_buffer = self.buffer.read(cx);
15858 let head = selection.head();
15859
15860 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15861 let head_anchor = multi_buffer_snapshot.anchor_at(
15862 head,
15863 if head < selection.tail() {
15864 Bias::Right
15865 } else {
15866 Bias::Left
15867 },
15868 );
15869
15870 match self
15871 .find_all_references_task_sources
15872 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15873 {
15874 Ok(_) => {
15875 log::info!(
15876 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15877 );
15878 return None;
15879 }
15880 Err(i) => {
15881 self.find_all_references_task_sources.insert(i, head_anchor);
15882 }
15883 }
15884
15885 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15886 let workspace = self.workspace()?;
15887 let project = workspace.read(cx).project().clone();
15888 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15889 Some(cx.spawn_in(window, async move |editor, cx| {
15890 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15891 if let Ok(i) = editor
15892 .find_all_references_task_sources
15893 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15894 {
15895 editor.find_all_references_task_sources.remove(i);
15896 }
15897 });
15898
15899 let locations = references.await?;
15900 if locations.is_empty() {
15901 return anyhow::Ok(Navigated::No);
15902 }
15903
15904 workspace.update_in(cx, |workspace, window, cx| {
15905 let title = locations
15906 .first()
15907 .as_ref()
15908 .map(|location| {
15909 let buffer = location.buffer.read(cx);
15910 format!(
15911 "References to `{}`",
15912 buffer
15913 .text_for_range(location.range.clone())
15914 .collect::<String>()
15915 )
15916 })
15917 .unwrap();
15918 Self::open_locations_in_multibuffer(
15919 workspace,
15920 locations,
15921 title,
15922 false,
15923 MultibufferSelectionMode::First,
15924 window,
15925 cx,
15926 );
15927 Navigated::Yes
15928 })
15929 }))
15930 }
15931
15932 /// Opens a multibuffer with the given project locations in it
15933 pub fn open_locations_in_multibuffer(
15934 workspace: &mut Workspace,
15935 mut locations: Vec<Location>,
15936 title: String,
15937 split: bool,
15938 multibuffer_selection_mode: MultibufferSelectionMode,
15939 window: &mut Window,
15940 cx: &mut Context<Workspace>,
15941 ) {
15942 if locations.is_empty() {
15943 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15944 return;
15945 }
15946
15947 // If there are multiple definitions, open them in a multibuffer
15948 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15949 let mut locations = locations.into_iter().peekable();
15950 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15951 let capability = workspace.project().read(cx).capability();
15952
15953 let excerpt_buffer = cx.new(|cx| {
15954 let mut multibuffer = MultiBuffer::new(capability);
15955 while let Some(location) = locations.next() {
15956 let buffer = location.buffer.read(cx);
15957 let mut ranges_for_buffer = Vec::new();
15958 let range = location.range.to_point(buffer);
15959 ranges_for_buffer.push(range.clone());
15960
15961 while let Some(next_location) = locations.peek() {
15962 if next_location.buffer == location.buffer {
15963 ranges_for_buffer.push(next_location.range.to_point(buffer));
15964 locations.next();
15965 } else {
15966 break;
15967 }
15968 }
15969
15970 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15971 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15972 PathKey::for_buffer(&location.buffer, cx),
15973 location.buffer.clone(),
15974 ranges_for_buffer,
15975 DEFAULT_MULTIBUFFER_CONTEXT,
15976 cx,
15977 );
15978 ranges.extend(new_ranges)
15979 }
15980
15981 multibuffer.with_title(title)
15982 });
15983
15984 let editor = cx.new(|cx| {
15985 Editor::for_multibuffer(
15986 excerpt_buffer,
15987 Some(workspace.project().clone()),
15988 window,
15989 cx,
15990 )
15991 });
15992 editor.update(cx, |editor, cx| {
15993 match multibuffer_selection_mode {
15994 MultibufferSelectionMode::First => {
15995 if let Some(first_range) = ranges.first() {
15996 editor.change_selections(
15997 SelectionEffects::no_scroll(),
15998 window,
15999 cx,
16000 |selections| {
16001 selections.clear_disjoint();
16002 selections
16003 .select_anchor_ranges(std::iter::once(first_range.clone()));
16004 },
16005 );
16006 }
16007 editor.highlight_background::<Self>(
16008 &ranges,
16009 |theme| theme.colors().editor_highlighted_line_background,
16010 cx,
16011 );
16012 }
16013 MultibufferSelectionMode::All => {
16014 editor.change_selections(
16015 SelectionEffects::no_scroll(),
16016 window,
16017 cx,
16018 |selections| {
16019 selections.clear_disjoint();
16020 selections.select_anchor_ranges(ranges);
16021 },
16022 );
16023 }
16024 }
16025 editor.register_buffers_with_language_servers(cx);
16026 });
16027
16028 let item = Box::new(editor);
16029 let item_id = item.item_id();
16030
16031 if split {
16032 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16033 } else {
16034 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16035 let (preview_item_id, preview_item_idx) =
16036 workspace.active_pane().read_with(cx, |pane, _| {
16037 (pane.preview_item_id(), pane.preview_item_idx())
16038 });
16039
16040 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16041
16042 if let Some(preview_item_id) = preview_item_id {
16043 workspace.active_pane().update(cx, |pane, cx| {
16044 pane.remove_item(preview_item_id, false, false, window, cx);
16045 });
16046 }
16047 } else {
16048 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16049 }
16050 }
16051 workspace.active_pane().update(cx, |pane, cx| {
16052 pane.set_preview_item_id(Some(item_id), cx);
16053 });
16054 }
16055
16056 pub fn rename(
16057 &mut self,
16058 _: &Rename,
16059 window: &mut Window,
16060 cx: &mut Context<Self>,
16061 ) -> Option<Task<Result<()>>> {
16062 use language::ToOffset as _;
16063
16064 let provider = self.semantics_provider.clone()?;
16065 let selection = self.selections.newest_anchor().clone();
16066 let (cursor_buffer, cursor_buffer_position) = self
16067 .buffer
16068 .read(cx)
16069 .text_anchor_for_position(selection.head(), cx)?;
16070 let (tail_buffer, cursor_buffer_position_end) = self
16071 .buffer
16072 .read(cx)
16073 .text_anchor_for_position(selection.tail(), cx)?;
16074 if tail_buffer != cursor_buffer {
16075 return None;
16076 }
16077
16078 let snapshot = cursor_buffer.read(cx).snapshot();
16079 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16080 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16081 let prepare_rename = provider
16082 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16083 .unwrap_or_else(|| Task::ready(Ok(None)));
16084 drop(snapshot);
16085
16086 Some(cx.spawn_in(window, async move |this, cx| {
16087 let rename_range = if let Some(range) = prepare_rename.await? {
16088 Some(range)
16089 } else {
16090 this.update(cx, |this, cx| {
16091 let buffer = this.buffer.read(cx).snapshot(cx);
16092 let mut buffer_highlights = this
16093 .document_highlights_for_position(selection.head(), &buffer)
16094 .filter(|highlight| {
16095 highlight.start.excerpt_id == selection.head().excerpt_id
16096 && highlight.end.excerpt_id == selection.head().excerpt_id
16097 });
16098 buffer_highlights
16099 .next()
16100 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16101 })?
16102 };
16103 if let Some(rename_range) = rename_range {
16104 this.update_in(cx, |this, window, cx| {
16105 let snapshot = cursor_buffer.read(cx).snapshot();
16106 let rename_buffer_range = rename_range.to_offset(&snapshot);
16107 let cursor_offset_in_rename_range =
16108 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16109 let cursor_offset_in_rename_range_end =
16110 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16111
16112 this.take_rename(false, window, cx);
16113 let buffer = this.buffer.read(cx).read(cx);
16114 let cursor_offset = selection.head().to_offset(&buffer);
16115 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16116 let rename_end = rename_start + rename_buffer_range.len();
16117 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16118 let mut old_highlight_id = None;
16119 let old_name: Arc<str> = buffer
16120 .chunks(rename_start..rename_end, true)
16121 .map(|chunk| {
16122 if old_highlight_id.is_none() {
16123 old_highlight_id = chunk.syntax_highlight_id;
16124 }
16125 chunk.text
16126 })
16127 .collect::<String>()
16128 .into();
16129
16130 drop(buffer);
16131
16132 // Position the selection in the rename editor so that it matches the current selection.
16133 this.show_local_selections = false;
16134 let rename_editor = cx.new(|cx| {
16135 let mut editor = Editor::single_line(window, cx);
16136 editor.buffer.update(cx, |buffer, cx| {
16137 buffer.edit([(0..0, old_name.clone())], None, cx)
16138 });
16139 let rename_selection_range = match cursor_offset_in_rename_range
16140 .cmp(&cursor_offset_in_rename_range_end)
16141 {
16142 Ordering::Equal => {
16143 editor.select_all(&SelectAll, window, cx);
16144 return editor;
16145 }
16146 Ordering::Less => {
16147 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16148 }
16149 Ordering::Greater => {
16150 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16151 }
16152 };
16153 if rename_selection_range.end > old_name.len() {
16154 editor.select_all(&SelectAll, window, cx);
16155 } else {
16156 editor.change_selections(Default::default(), window, cx, |s| {
16157 s.select_ranges([rename_selection_range]);
16158 });
16159 }
16160 editor
16161 });
16162 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16163 if e == &EditorEvent::Focused {
16164 cx.emit(EditorEvent::FocusedIn)
16165 }
16166 })
16167 .detach();
16168
16169 let write_highlights =
16170 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16171 let read_highlights =
16172 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16173 let ranges = write_highlights
16174 .iter()
16175 .flat_map(|(_, ranges)| ranges.iter())
16176 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16177 .cloned()
16178 .collect();
16179
16180 this.highlight_text::<Rename>(
16181 ranges,
16182 HighlightStyle {
16183 fade_out: Some(0.6),
16184 ..Default::default()
16185 },
16186 cx,
16187 );
16188 let rename_focus_handle = rename_editor.focus_handle(cx);
16189 window.focus(&rename_focus_handle);
16190 let block_id = this.insert_blocks(
16191 [BlockProperties {
16192 style: BlockStyle::Flex,
16193 placement: BlockPlacement::Below(range.start),
16194 height: Some(1),
16195 render: Arc::new({
16196 let rename_editor = rename_editor.clone();
16197 move |cx: &mut BlockContext| {
16198 let mut text_style = cx.editor_style.text.clone();
16199 if let Some(highlight_style) = old_highlight_id
16200 .and_then(|h| h.style(&cx.editor_style.syntax))
16201 {
16202 text_style = text_style.highlight(highlight_style);
16203 }
16204 div()
16205 .block_mouse_except_scroll()
16206 .pl(cx.anchor_x)
16207 .child(EditorElement::new(
16208 &rename_editor,
16209 EditorStyle {
16210 background: cx.theme().system().transparent,
16211 local_player: cx.editor_style.local_player,
16212 text: text_style,
16213 scrollbar_width: cx.editor_style.scrollbar_width,
16214 syntax: cx.editor_style.syntax.clone(),
16215 status: cx.editor_style.status.clone(),
16216 inlay_hints_style: HighlightStyle {
16217 font_weight: Some(FontWeight::BOLD),
16218 ..make_inlay_hints_style(cx.app)
16219 },
16220 inline_completion_styles: make_suggestion_styles(
16221 cx.app,
16222 ),
16223 ..EditorStyle::default()
16224 },
16225 ))
16226 .into_any_element()
16227 }
16228 }),
16229 priority: 0,
16230 }],
16231 Some(Autoscroll::fit()),
16232 cx,
16233 )[0];
16234 this.pending_rename = Some(RenameState {
16235 range,
16236 old_name,
16237 editor: rename_editor,
16238 block_id,
16239 });
16240 })?;
16241 }
16242
16243 Ok(())
16244 }))
16245 }
16246
16247 pub fn confirm_rename(
16248 &mut self,
16249 _: &ConfirmRename,
16250 window: &mut Window,
16251 cx: &mut Context<Self>,
16252 ) -> Option<Task<Result<()>>> {
16253 let rename = self.take_rename(false, window, cx)?;
16254 let workspace = self.workspace()?.downgrade();
16255 let (buffer, start) = self
16256 .buffer
16257 .read(cx)
16258 .text_anchor_for_position(rename.range.start, cx)?;
16259 let (end_buffer, _) = self
16260 .buffer
16261 .read(cx)
16262 .text_anchor_for_position(rename.range.end, cx)?;
16263 if buffer != end_buffer {
16264 return None;
16265 }
16266
16267 let old_name = rename.old_name;
16268 let new_name = rename.editor.read(cx).text(cx);
16269
16270 let rename = self.semantics_provider.as_ref()?.perform_rename(
16271 &buffer,
16272 start,
16273 new_name.clone(),
16274 cx,
16275 )?;
16276
16277 Some(cx.spawn_in(window, async move |editor, cx| {
16278 let project_transaction = rename.await?;
16279 Self::open_project_transaction(
16280 &editor,
16281 workspace,
16282 project_transaction,
16283 format!("Rename: {} → {}", old_name, new_name),
16284 cx,
16285 )
16286 .await?;
16287
16288 editor.update(cx, |editor, cx| {
16289 editor.refresh_document_highlights(cx);
16290 })?;
16291 Ok(())
16292 }))
16293 }
16294
16295 fn take_rename(
16296 &mut self,
16297 moving_cursor: bool,
16298 window: &mut Window,
16299 cx: &mut Context<Self>,
16300 ) -> Option<RenameState> {
16301 let rename = self.pending_rename.take()?;
16302 if rename.editor.focus_handle(cx).is_focused(window) {
16303 window.focus(&self.focus_handle);
16304 }
16305
16306 self.remove_blocks(
16307 [rename.block_id].into_iter().collect(),
16308 Some(Autoscroll::fit()),
16309 cx,
16310 );
16311 self.clear_highlights::<Rename>(cx);
16312 self.show_local_selections = true;
16313
16314 if moving_cursor {
16315 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16316 editor.selections.newest::<usize>(cx).head()
16317 });
16318
16319 // Update the selection to match the position of the selection inside
16320 // the rename editor.
16321 let snapshot = self.buffer.read(cx).read(cx);
16322 let rename_range = rename.range.to_offset(&snapshot);
16323 let cursor_in_editor = snapshot
16324 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16325 .min(rename_range.end);
16326 drop(snapshot);
16327
16328 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16329 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16330 });
16331 } else {
16332 self.refresh_document_highlights(cx);
16333 }
16334
16335 Some(rename)
16336 }
16337
16338 pub fn pending_rename(&self) -> Option<&RenameState> {
16339 self.pending_rename.as_ref()
16340 }
16341
16342 fn format(
16343 &mut self,
16344 _: &Format,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) -> Option<Task<Result<()>>> {
16348 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16349
16350 let project = match &self.project {
16351 Some(project) => project.clone(),
16352 None => return None,
16353 };
16354
16355 Some(self.perform_format(
16356 project,
16357 FormatTrigger::Manual,
16358 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16359 window,
16360 cx,
16361 ))
16362 }
16363
16364 fn format_selections(
16365 &mut self,
16366 _: &FormatSelections,
16367 window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) -> Option<Task<Result<()>>> {
16370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16371
16372 let project = match &self.project {
16373 Some(project) => project.clone(),
16374 None => return None,
16375 };
16376
16377 let ranges = self
16378 .selections
16379 .all_adjusted(cx)
16380 .into_iter()
16381 .map(|selection| selection.range())
16382 .collect_vec();
16383
16384 Some(self.perform_format(
16385 project,
16386 FormatTrigger::Manual,
16387 FormatTarget::Ranges(ranges),
16388 window,
16389 cx,
16390 ))
16391 }
16392
16393 fn perform_format(
16394 &mut self,
16395 project: Entity<Project>,
16396 trigger: FormatTrigger,
16397 target: FormatTarget,
16398 window: &mut Window,
16399 cx: &mut Context<Self>,
16400 ) -> Task<Result<()>> {
16401 let buffer = self.buffer.clone();
16402 let (buffers, target) = match target {
16403 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16404 FormatTarget::Ranges(selection_ranges) => {
16405 let multi_buffer = buffer.read(cx);
16406 let snapshot = multi_buffer.read(cx);
16407 let mut buffers = HashSet::default();
16408 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16409 BTreeMap::new();
16410 for selection_range in selection_ranges {
16411 for (buffer, buffer_range, _) in
16412 snapshot.range_to_buffer_ranges(selection_range)
16413 {
16414 let buffer_id = buffer.remote_id();
16415 let start = buffer.anchor_before(buffer_range.start);
16416 let end = buffer.anchor_after(buffer_range.end);
16417 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16418 buffer_id_to_ranges
16419 .entry(buffer_id)
16420 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16421 .or_insert_with(|| vec![start..end]);
16422 }
16423 }
16424 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16425 }
16426 };
16427
16428 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16429 let selections_prev = transaction_id_prev
16430 .and_then(|transaction_id_prev| {
16431 // default to selections as they were after the last edit, if we have them,
16432 // instead of how they are now.
16433 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16434 // will take you back to where you made the last edit, instead of staying where you scrolled
16435 self.selection_history
16436 .transaction(transaction_id_prev)
16437 .map(|t| t.0.clone())
16438 })
16439 .unwrap_or_else(|| {
16440 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16441 self.selections.disjoint_anchors()
16442 });
16443
16444 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16445 let format = project.update(cx, |project, cx| {
16446 project.format(buffers, target, true, trigger, cx)
16447 });
16448
16449 cx.spawn_in(window, async move |editor, cx| {
16450 let transaction = futures::select_biased! {
16451 transaction = format.log_err().fuse() => transaction,
16452 () = timeout => {
16453 log::warn!("timed out waiting for formatting");
16454 None
16455 }
16456 };
16457
16458 buffer
16459 .update(cx, |buffer, cx| {
16460 if let Some(transaction) = transaction {
16461 if !buffer.is_singleton() {
16462 buffer.push_transaction(&transaction.0, cx);
16463 }
16464 }
16465 cx.notify();
16466 })
16467 .ok();
16468
16469 if let Some(transaction_id_now) =
16470 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16471 {
16472 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16473 if has_new_transaction {
16474 _ = editor.update(cx, |editor, _| {
16475 editor
16476 .selection_history
16477 .insert_transaction(transaction_id_now, selections_prev);
16478 });
16479 }
16480 }
16481
16482 Ok(())
16483 })
16484 }
16485
16486 fn organize_imports(
16487 &mut self,
16488 _: &OrganizeImports,
16489 window: &mut Window,
16490 cx: &mut Context<Self>,
16491 ) -> Option<Task<Result<()>>> {
16492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16493 let project = match &self.project {
16494 Some(project) => project.clone(),
16495 None => return None,
16496 };
16497 Some(self.perform_code_action_kind(
16498 project,
16499 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16500 window,
16501 cx,
16502 ))
16503 }
16504
16505 fn perform_code_action_kind(
16506 &mut self,
16507 project: Entity<Project>,
16508 kind: CodeActionKind,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) -> Task<Result<()>> {
16512 let buffer = self.buffer.clone();
16513 let buffers = buffer.read(cx).all_buffers();
16514 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16515 let apply_action = project.update(cx, |project, cx| {
16516 project.apply_code_action_kind(buffers, kind, true, cx)
16517 });
16518 cx.spawn_in(window, async move |_, cx| {
16519 let transaction = futures::select_biased! {
16520 () = timeout => {
16521 log::warn!("timed out waiting for executing code action");
16522 None
16523 }
16524 transaction = apply_action.log_err().fuse() => transaction,
16525 };
16526 buffer
16527 .update(cx, |buffer, cx| {
16528 // check if we need this
16529 if let Some(transaction) = transaction {
16530 if !buffer.is_singleton() {
16531 buffer.push_transaction(&transaction.0, cx);
16532 }
16533 }
16534 cx.notify();
16535 })
16536 .ok();
16537 Ok(())
16538 })
16539 }
16540
16541 pub fn restart_language_server(
16542 &mut self,
16543 _: &RestartLanguageServer,
16544 _: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) {
16547 if let Some(project) = self.project.clone() {
16548 self.buffer.update(cx, |multi_buffer, cx| {
16549 project.update(cx, |project, cx| {
16550 project.restart_language_servers_for_buffers(
16551 multi_buffer.all_buffers().into_iter().collect(),
16552 HashSet::default(),
16553 cx,
16554 );
16555 });
16556 })
16557 }
16558 }
16559
16560 pub fn stop_language_server(
16561 &mut self,
16562 _: &StopLanguageServer,
16563 _: &mut Window,
16564 cx: &mut Context<Self>,
16565 ) {
16566 if let Some(project) = self.project.clone() {
16567 self.buffer.update(cx, |multi_buffer, cx| {
16568 project.update(cx, |project, cx| {
16569 project.stop_language_servers_for_buffers(
16570 multi_buffer.all_buffers().into_iter().collect(),
16571 HashSet::default(),
16572 cx,
16573 );
16574 cx.emit(project::Event::RefreshInlayHints);
16575 });
16576 });
16577 }
16578 }
16579
16580 fn cancel_language_server_work(
16581 workspace: &mut Workspace,
16582 _: &actions::CancelLanguageServerWork,
16583 _: &mut Window,
16584 cx: &mut Context<Workspace>,
16585 ) {
16586 let project = workspace.project();
16587 let buffers = workspace
16588 .active_item(cx)
16589 .and_then(|item| item.act_as::<Editor>(cx))
16590 .map_or(HashSet::default(), |editor| {
16591 editor.read(cx).buffer.read(cx).all_buffers()
16592 });
16593 project.update(cx, |project, cx| {
16594 project.cancel_language_server_work_for_buffers(buffers, cx);
16595 });
16596 }
16597
16598 fn show_character_palette(
16599 &mut self,
16600 _: &ShowCharacterPalette,
16601 window: &mut Window,
16602 _: &mut Context<Self>,
16603 ) {
16604 window.show_character_palette();
16605 }
16606
16607 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16608 if !self.diagnostics_enabled() {
16609 return;
16610 }
16611
16612 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16613 let buffer = self.buffer.read(cx).snapshot(cx);
16614 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16615 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16616 let is_valid = buffer
16617 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16618 .any(|entry| {
16619 entry.diagnostic.is_primary
16620 && !entry.range.is_empty()
16621 && entry.range.start == primary_range_start
16622 && entry.diagnostic.message == active_diagnostics.active_message
16623 });
16624
16625 if !is_valid {
16626 self.dismiss_diagnostics(cx);
16627 }
16628 }
16629 }
16630
16631 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16632 match &self.active_diagnostics {
16633 ActiveDiagnostic::Group(group) => Some(group),
16634 _ => None,
16635 }
16636 }
16637
16638 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16639 if !self.diagnostics_enabled() {
16640 return;
16641 }
16642 self.dismiss_diagnostics(cx);
16643 self.active_diagnostics = ActiveDiagnostic::All;
16644 }
16645
16646 fn activate_diagnostics(
16647 &mut self,
16648 buffer_id: BufferId,
16649 diagnostic: DiagnosticEntry<usize>,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16654 return;
16655 }
16656 self.dismiss_diagnostics(cx);
16657 let snapshot = self.snapshot(window, cx);
16658 let buffer = self.buffer.read(cx).snapshot(cx);
16659 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16660 return;
16661 };
16662
16663 let diagnostic_group = buffer
16664 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16665 .collect::<Vec<_>>();
16666
16667 let blocks =
16668 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16669
16670 let blocks = self.display_map.update(cx, |display_map, cx| {
16671 display_map.insert_blocks(blocks, cx).into_iter().collect()
16672 });
16673 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16674 active_range: buffer.anchor_before(diagnostic.range.start)
16675 ..buffer.anchor_after(diagnostic.range.end),
16676 active_message: diagnostic.diagnostic.message.clone(),
16677 group_id: diagnostic.diagnostic.group_id,
16678 blocks,
16679 });
16680 cx.notify();
16681 }
16682
16683 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16684 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16685 return;
16686 };
16687
16688 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16689 if let ActiveDiagnostic::Group(group) = prev {
16690 self.display_map.update(cx, |display_map, cx| {
16691 display_map.remove_blocks(group.blocks, cx);
16692 });
16693 cx.notify();
16694 }
16695 }
16696
16697 /// Disable inline diagnostics rendering for this editor.
16698 pub fn disable_inline_diagnostics(&mut self) {
16699 self.inline_diagnostics_enabled = false;
16700 self.inline_diagnostics_update = Task::ready(());
16701 self.inline_diagnostics.clear();
16702 }
16703
16704 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16705 self.diagnostics_enabled = false;
16706 self.dismiss_diagnostics(cx);
16707 self.inline_diagnostics_update = Task::ready(());
16708 self.inline_diagnostics.clear();
16709 }
16710
16711 pub fn diagnostics_enabled(&self) -> bool {
16712 self.diagnostics_enabled && self.mode.is_full()
16713 }
16714
16715 pub fn inline_diagnostics_enabled(&self) -> bool {
16716 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16717 }
16718
16719 pub fn show_inline_diagnostics(&self) -> bool {
16720 self.show_inline_diagnostics
16721 }
16722
16723 pub fn toggle_inline_diagnostics(
16724 &mut self,
16725 _: &ToggleInlineDiagnostics,
16726 window: &mut Window,
16727 cx: &mut Context<Editor>,
16728 ) {
16729 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16730 self.refresh_inline_diagnostics(false, window, cx);
16731 }
16732
16733 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16734 self.diagnostics_max_severity = severity;
16735 self.display_map.update(cx, |display_map, _| {
16736 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16737 });
16738 }
16739
16740 pub fn toggle_diagnostics(
16741 &mut self,
16742 _: &ToggleDiagnostics,
16743 window: &mut Window,
16744 cx: &mut Context<Editor>,
16745 ) {
16746 if !self.diagnostics_enabled() {
16747 return;
16748 }
16749
16750 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16751 EditorSettings::get_global(cx)
16752 .diagnostics_max_severity
16753 .filter(|severity| severity != &DiagnosticSeverity::Off)
16754 .unwrap_or(DiagnosticSeverity::Hint)
16755 } else {
16756 DiagnosticSeverity::Off
16757 };
16758 self.set_max_diagnostics_severity(new_severity, cx);
16759 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16760 self.active_diagnostics = ActiveDiagnostic::None;
16761 self.inline_diagnostics_update = Task::ready(());
16762 self.inline_diagnostics.clear();
16763 } else {
16764 self.refresh_inline_diagnostics(false, window, cx);
16765 }
16766
16767 cx.notify();
16768 }
16769
16770 pub fn toggle_minimap(
16771 &mut self,
16772 _: &ToggleMinimap,
16773 window: &mut Window,
16774 cx: &mut Context<Editor>,
16775 ) {
16776 if self.supports_minimap(cx) {
16777 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16778 }
16779 }
16780
16781 fn refresh_inline_diagnostics(
16782 &mut self,
16783 debounce: bool,
16784 window: &mut Window,
16785 cx: &mut Context<Self>,
16786 ) {
16787 let max_severity = ProjectSettings::get_global(cx)
16788 .diagnostics
16789 .inline
16790 .max_severity
16791 .unwrap_or(self.diagnostics_max_severity);
16792
16793 if !self.inline_diagnostics_enabled()
16794 || !self.show_inline_diagnostics
16795 || max_severity == DiagnosticSeverity::Off
16796 {
16797 self.inline_diagnostics_update = Task::ready(());
16798 self.inline_diagnostics.clear();
16799 return;
16800 }
16801
16802 let debounce_ms = ProjectSettings::get_global(cx)
16803 .diagnostics
16804 .inline
16805 .update_debounce_ms;
16806 let debounce = if debounce && debounce_ms > 0 {
16807 Some(Duration::from_millis(debounce_ms))
16808 } else {
16809 None
16810 };
16811 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16812 if let Some(debounce) = debounce {
16813 cx.background_executor().timer(debounce).await;
16814 }
16815 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16816 editor
16817 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16818 .ok()
16819 }) else {
16820 return;
16821 };
16822
16823 let new_inline_diagnostics = cx
16824 .background_spawn(async move {
16825 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16826 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16827 let message = diagnostic_entry
16828 .diagnostic
16829 .message
16830 .split_once('\n')
16831 .map(|(line, _)| line)
16832 .map(SharedString::new)
16833 .unwrap_or_else(|| {
16834 SharedString::from(diagnostic_entry.diagnostic.message)
16835 });
16836 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16837 let (Ok(i) | Err(i)) = inline_diagnostics
16838 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16839 inline_diagnostics.insert(
16840 i,
16841 (
16842 start_anchor,
16843 InlineDiagnostic {
16844 message,
16845 group_id: diagnostic_entry.diagnostic.group_id,
16846 start: diagnostic_entry.range.start.to_point(&snapshot),
16847 is_primary: diagnostic_entry.diagnostic.is_primary,
16848 severity: diagnostic_entry.diagnostic.severity,
16849 },
16850 ),
16851 );
16852 }
16853 inline_diagnostics
16854 })
16855 .await;
16856
16857 editor
16858 .update(cx, |editor, cx| {
16859 editor.inline_diagnostics = new_inline_diagnostics;
16860 cx.notify();
16861 })
16862 .ok();
16863 });
16864 }
16865
16866 fn pull_diagnostics(
16867 &mut self,
16868 buffer_id: Option<BufferId>,
16869 window: &Window,
16870 cx: &mut Context<Self>,
16871 ) -> Option<()> {
16872 if !self.mode().is_full() {
16873 return None;
16874 }
16875 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16876 .diagnostics
16877 .lsp_pull_diagnostics;
16878 if !pull_diagnostics_settings.enabled {
16879 return None;
16880 }
16881 let project = self.project.as_ref()?.downgrade();
16882 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16883 let mut buffers = self.buffer.read(cx).all_buffers();
16884 if let Some(buffer_id) = buffer_id {
16885 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16886 }
16887
16888 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16889 cx.background_executor().timer(debounce).await;
16890
16891 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16892 buffers
16893 .into_iter()
16894 .filter_map(|buffer| {
16895 project
16896 .update(cx, |project, cx| {
16897 project.lsp_store().update(cx, |lsp_store, cx| {
16898 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16899 })
16900 })
16901 .ok()
16902 })
16903 .collect::<FuturesUnordered<_>>()
16904 }) else {
16905 return;
16906 };
16907
16908 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16909 match pull_task {
16910 Ok(()) => {
16911 if editor
16912 .update_in(cx, |editor, window, cx| {
16913 editor.update_diagnostics_state(window, cx);
16914 })
16915 .is_err()
16916 {
16917 return;
16918 }
16919 }
16920 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16921 }
16922 }
16923 });
16924
16925 Some(())
16926 }
16927
16928 pub fn set_selections_from_remote(
16929 &mut self,
16930 selections: Vec<Selection<Anchor>>,
16931 pending_selection: Option<Selection<Anchor>>,
16932 window: &mut Window,
16933 cx: &mut Context<Self>,
16934 ) {
16935 let old_cursor_position = self.selections.newest_anchor().head();
16936 self.selections.change_with(cx, |s| {
16937 s.select_anchors(selections);
16938 if let Some(pending_selection) = pending_selection {
16939 s.set_pending(pending_selection, SelectMode::Character);
16940 } else {
16941 s.clear_pending();
16942 }
16943 });
16944 self.selections_did_change(
16945 false,
16946 &old_cursor_position,
16947 SelectionEffects::default(),
16948 window,
16949 cx,
16950 );
16951 }
16952
16953 pub fn transact(
16954 &mut self,
16955 window: &mut Window,
16956 cx: &mut Context<Self>,
16957 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16958 ) -> Option<TransactionId> {
16959 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16960 this.start_transaction_at(Instant::now(), window, cx);
16961 update(this, window, cx);
16962 this.end_transaction_at(Instant::now(), cx)
16963 })
16964 }
16965
16966 pub fn start_transaction_at(
16967 &mut self,
16968 now: Instant,
16969 window: &mut Window,
16970 cx: &mut Context<Self>,
16971 ) {
16972 self.end_selection(window, cx);
16973 if let Some(tx_id) = self
16974 .buffer
16975 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16976 {
16977 self.selection_history
16978 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16979 cx.emit(EditorEvent::TransactionBegun {
16980 transaction_id: tx_id,
16981 })
16982 }
16983 }
16984
16985 pub fn end_transaction_at(
16986 &mut self,
16987 now: Instant,
16988 cx: &mut Context<Self>,
16989 ) -> Option<TransactionId> {
16990 if let Some(transaction_id) = self
16991 .buffer
16992 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16993 {
16994 if let Some((_, end_selections)) =
16995 self.selection_history.transaction_mut(transaction_id)
16996 {
16997 *end_selections = Some(self.selections.disjoint_anchors());
16998 } else {
16999 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17000 }
17001
17002 cx.emit(EditorEvent::Edited { transaction_id });
17003 Some(transaction_id)
17004 } else {
17005 None
17006 }
17007 }
17008
17009 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17010 if self.selection_mark_mode {
17011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17012 s.move_with(|_, sel| {
17013 sel.collapse_to(sel.head(), SelectionGoal::None);
17014 });
17015 })
17016 }
17017 self.selection_mark_mode = true;
17018 cx.notify();
17019 }
17020
17021 pub fn swap_selection_ends(
17022 &mut self,
17023 _: &actions::SwapSelectionEnds,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) {
17027 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17028 s.move_with(|_, sel| {
17029 if sel.start != sel.end {
17030 sel.reversed = !sel.reversed
17031 }
17032 });
17033 });
17034 self.request_autoscroll(Autoscroll::newest(), cx);
17035 cx.notify();
17036 }
17037
17038 pub fn toggle_focus(
17039 workspace: &mut Workspace,
17040 _: &actions::ToggleFocus,
17041 window: &mut Window,
17042 cx: &mut Context<Workspace>,
17043 ) {
17044 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17045 return;
17046 };
17047 workspace.activate_item(&item, true, true, window, cx);
17048 }
17049
17050 pub fn toggle_fold(
17051 &mut self,
17052 _: &actions::ToggleFold,
17053 window: &mut Window,
17054 cx: &mut Context<Self>,
17055 ) {
17056 if self.is_singleton(cx) {
17057 let selection = self.selections.newest::<Point>(cx);
17058
17059 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17060 let range = if selection.is_empty() {
17061 let point = selection.head().to_display_point(&display_map);
17062 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17063 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17064 .to_point(&display_map);
17065 start..end
17066 } else {
17067 selection.range()
17068 };
17069 if display_map.folds_in_range(range).next().is_some() {
17070 self.unfold_lines(&Default::default(), window, cx)
17071 } else {
17072 self.fold(&Default::default(), window, cx)
17073 }
17074 } else {
17075 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17076 let buffer_ids: HashSet<_> = self
17077 .selections
17078 .disjoint_anchor_ranges()
17079 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17080 .collect();
17081
17082 let should_unfold = buffer_ids
17083 .iter()
17084 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17085
17086 for buffer_id in buffer_ids {
17087 if should_unfold {
17088 self.unfold_buffer(buffer_id, cx);
17089 } else {
17090 self.fold_buffer(buffer_id, cx);
17091 }
17092 }
17093 }
17094 }
17095
17096 pub fn toggle_fold_recursive(
17097 &mut self,
17098 _: &actions::ToggleFoldRecursive,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 let selection = self.selections.newest::<Point>(cx);
17103
17104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17105 let range = if selection.is_empty() {
17106 let point = selection.head().to_display_point(&display_map);
17107 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17108 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17109 .to_point(&display_map);
17110 start..end
17111 } else {
17112 selection.range()
17113 };
17114 if display_map.folds_in_range(range).next().is_some() {
17115 self.unfold_recursive(&Default::default(), window, cx)
17116 } else {
17117 self.fold_recursive(&Default::default(), window, cx)
17118 }
17119 }
17120
17121 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17122 if self.is_singleton(cx) {
17123 let mut to_fold = Vec::new();
17124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17125 let selections = self.selections.all_adjusted(cx);
17126
17127 for selection in selections {
17128 let range = selection.range().sorted();
17129 let buffer_start_row = range.start.row;
17130
17131 if range.start.row != range.end.row {
17132 let mut found = false;
17133 let mut row = range.start.row;
17134 while row <= range.end.row {
17135 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17136 {
17137 found = true;
17138 row = crease.range().end.row + 1;
17139 to_fold.push(crease);
17140 } else {
17141 row += 1
17142 }
17143 }
17144 if found {
17145 continue;
17146 }
17147 }
17148
17149 for row in (0..=range.start.row).rev() {
17150 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17151 if crease.range().end.row >= buffer_start_row {
17152 to_fold.push(crease);
17153 if row <= range.start.row {
17154 break;
17155 }
17156 }
17157 }
17158 }
17159 }
17160
17161 self.fold_creases(to_fold, true, window, cx);
17162 } else {
17163 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17164 let buffer_ids = self
17165 .selections
17166 .disjoint_anchor_ranges()
17167 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17168 .collect::<HashSet<_>>();
17169 for buffer_id in buffer_ids {
17170 self.fold_buffer(buffer_id, cx);
17171 }
17172 }
17173 }
17174
17175 pub fn toggle_fold_all(
17176 &mut self,
17177 _: &actions::ToggleFoldAll,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) {
17181 if self.buffer.read(cx).is_singleton() {
17182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17183 let has_folds = display_map
17184 .folds_in_range(0..display_map.buffer_snapshot.len())
17185 .next()
17186 .is_some();
17187
17188 if has_folds {
17189 self.unfold_all(&actions::UnfoldAll, window, cx);
17190 } else {
17191 self.fold_all(&actions::FoldAll, window, cx);
17192 }
17193 } else {
17194 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17195 let should_unfold = buffer_ids
17196 .iter()
17197 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17198
17199 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17200 editor
17201 .update_in(cx, |editor, _, cx| {
17202 for buffer_id in buffer_ids {
17203 if should_unfold {
17204 editor.unfold_buffer(buffer_id, cx);
17205 } else {
17206 editor.fold_buffer(buffer_id, cx);
17207 }
17208 }
17209 })
17210 .ok();
17211 });
17212 }
17213 }
17214
17215 fn fold_at_level(
17216 &mut self,
17217 fold_at: &FoldAtLevel,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) {
17221 if !self.buffer.read(cx).is_singleton() {
17222 return;
17223 }
17224
17225 let fold_at_level = fold_at.0;
17226 let snapshot = self.buffer.read(cx).snapshot(cx);
17227 let mut to_fold = Vec::new();
17228 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17229
17230 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17231 while start_row < end_row {
17232 match self
17233 .snapshot(window, cx)
17234 .crease_for_buffer_row(MultiBufferRow(start_row))
17235 {
17236 Some(crease) => {
17237 let nested_start_row = crease.range().start.row + 1;
17238 let nested_end_row = crease.range().end.row;
17239
17240 if current_level < fold_at_level {
17241 stack.push((nested_start_row, nested_end_row, current_level + 1));
17242 } else if current_level == fold_at_level {
17243 to_fold.push(crease);
17244 }
17245
17246 start_row = nested_end_row + 1;
17247 }
17248 None => start_row += 1,
17249 }
17250 }
17251 }
17252
17253 self.fold_creases(to_fold, true, window, cx);
17254 }
17255
17256 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17257 if self.buffer.read(cx).is_singleton() {
17258 let mut fold_ranges = Vec::new();
17259 let snapshot = self.buffer.read(cx).snapshot(cx);
17260
17261 for row in 0..snapshot.max_row().0 {
17262 if let Some(foldable_range) = self
17263 .snapshot(window, cx)
17264 .crease_for_buffer_row(MultiBufferRow(row))
17265 {
17266 fold_ranges.push(foldable_range);
17267 }
17268 }
17269
17270 self.fold_creases(fold_ranges, true, window, cx);
17271 } else {
17272 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17273 editor
17274 .update_in(cx, |editor, _, cx| {
17275 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17276 editor.fold_buffer(buffer_id, cx);
17277 }
17278 })
17279 .ok();
17280 });
17281 }
17282 }
17283
17284 pub fn fold_function_bodies(
17285 &mut self,
17286 _: &actions::FoldFunctionBodies,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) {
17290 let snapshot = self.buffer.read(cx).snapshot(cx);
17291
17292 let ranges = snapshot
17293 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17294 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17295 .collect::<Vec<_>>();
17296
17297 let creases = ranges
17298 .into_iter()
17299 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17300 .collect();
17301
17302 self.fold_creases(creases, true, window, cx);
17303 }
17304
17305 pub fn fold_recursive(
17306 &mut self,
17307 _: &actions::FoldRecursive,
17308 window: &mut Window,
17309 cx: &mut Context<Self>,
17310 ) {
17311 let mut to_fold = Vec::new();
17312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17313 let selections = self.selections.all_adjusted(cx);
17314
17315 for selection in selections {
17316 let range = selection.range().sorted();
17317 let buffer_start_row = range.start.row;
17318
17319 if range.start.row != range.end.row {
17320 let mut found = false;
17321 for row in range.start.row..=range.end.row {
17322 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17323 found = true;
17324 to_fold.push(crease);
17325 }
17326 }
17327 if found {
17328 continue;
17329 }
17330 }
17331
17332 for row in (0..=range.start.row).rev() {
17333 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17334 if crease.range().end.row >= buffer_start_row {
17335 to_fold.push(crease);
17336 } else {
17337 break;
17338 }
17339 }
17340 }
17341 }
17342
17343 self.fold_creases(to_fold, true, window, cx);
17344 }
17345
17346 pub fn fold_at(
17347 &mut self,
17348 buffer_row: MultiBufferRow,
17349 window: &mut Window,
17350 cx: &mut Context<Self>,
17351 ) {
17352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17353
17354 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17355 let autoscroll = self
17356 .selections
17357 .all::<Point>(cx)
17358 .iter()
17359 .any(|selection| crease.range().overlaps(&selection.range()));
17360
17361 self.fold_creases(vec![crease], autoscroll, window, cx);
17362 }
17363 }
17364
17365 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17366 if self.is_singleton(cx) {
17367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17368 let buffer = &display_map.buffer_snapshot;
17369 let selections = self.selections.all::<Point>(cx);
17370 let ranges = selections
17371 .iter()
17372 .map(|s| {
17373 let range = s.display_range(&display_map).sorted();
17374 let mut start = range.start.to_point(&display_map);
17375 let mut end = range.end.to_point(&display_map);
17376 start.column = 0;
17377 end.column = buffer.line_len(MultiBufferRow(end.row));
17378 start..end
17379 })
17380 .collect::<Vec<_>>();
17381
17382 self.unfold_ranges(&ranges, true, true, cx);
17383 } else {
17384 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17385 let buffer_ids = self
17386 .selections
17387 .disjoint_anchor_ranges()
17388 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17389 .collect::<HashSet<_>>();
17390 for buffer_id in buffer_ids {
17391 self.unfold_buffer(buffer_id, cx);
17392 }
17393 }
17394 }
17395
17396 pub fn unfold_recursive(
17397 &mut self,
17398 _: &UnfoldRecursive,
17399 _window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) {
17402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17403 let selections = self.selections.all::<Point>(cx);
17404 let ranges = selections
17405 .iter()
17406 .map(|s| {
17407 let mut range = s.display_range(&display_map).sorted();
17408 *range.start.column_mut() = 0;
17409 *range.end.column_mut() = display_map.line_len(range.end.row());
17410 let start = range.start.to_point(&display_map);
17411 let end = range.end.to_point(&display_map);
17412 start..end
17413 })
17414 .collect::<Vec<_>>();
17415
17416 self.unfold_ranges(&ranges, true, true, cx);
17417 }
17418
17419 pub fn unfold_at(
17420 &mut self,
17421 buffer_row: MultiBufferRow,
17422 _window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17426
17427 let intersection_range = Point::new(buffer_row.0, 0)
17428 ..Point::new(
17429 buffer_row.0,
17430 display_map.buffer_snapshot.line_len(buffer_row),
17431 );
17432
17433 let autoscroll = self
17434 .selections
17435 .all::<Point>(cx)
17436 .iter()
17437 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17438
17439 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17440 }
17441
17442 pub fn unfold_all(
17443 &mut self,
17444 _: &actions::UnfoldAll,
17445 _window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 if self.buffer.read(cx).is_singleton() {
17449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17450 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17451 } else {
17452 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17453 editor
17454 .update(cx, |editor, cx| {
17455 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17456 editor.unfold_buffer(buffer_id, cx);
17457 }
17458 })
17459 .ok();
17460 });
17461 }
17462 }
17463
17464 pub fn fold_selected_ranges(
17465 &mut self,
17466 _: &FoldSelectedRanges,
17467 window: &mut Window,
17468 cx: &mut Context<Self>,
17469 ) {
17470 let selections = self.selections.all_adjusted(cx);
17471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17472 let ranges = selections
17473 .into_iter()
17474 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17475 .collect::<Vec<_>>();
17476 self.fold_creases(ranges, true, window, cx);
17477 }
17478
17479 pub fn fold_ranges<T: ToOffset + Clone>(
17480 &mut self,
17481 ranges: Vec<Range<T>>,
17482 auto_scroll: bool,
17483 window: &mut Window,
17484 cx: &mut Context<Self>,
17485 ) {
17486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17487 let ranges = ranges
17488 .into_iter()
17489 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17490 .collect::<Vec<_>>();
17491 self.fold_creases(ranges, auto_scroll, window, cx);
17492 }
17493
17494 pub fn fold_creases<T: ToOffset + Clone>(
17495 &mut self,
17496 creases: Vec<Crease<T>>,
17497 auto_scroll: bool,
17498 _window: &mut Window,
17499 cx: &mut Context<Self>,
17500 ) {
17501 if creases.is_empty() {
17502 return;
17503 }
17504
17505 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17506
17507 if auto_scroll {
17508 self.request_autoscroll(Autoscroll::fit(), cx);
17509 }
17510
17511 cx.notify();
17512
17513 self.scrollbar_marker_state.dirty = true;
17514 self.folds_did_change(cx);
17515 }
17516
17517 /// Removes any folds whose ranges intersect any of the given ranges.
17518 pub fn unfold_ranges<T: ToOffset + Clone>(
17519 &mut self,
17520 ranges: &[Range<T>],
17521 inclusive: bool,
17522 auto_scroll: bool,
17523 cx: &mut Context<Self>,
17524 ) {
17525 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17526 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17527 });
17528 self.folds_did_change(cx);
17529 }
17530
17531 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17532 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17533 return;
17534 }
17535 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17536 self.display_map.update(cx, |display_map, cx| {
17537 display_map.fold_buffers([buffer_id], cx)
17538 });
17539 cx.emit(EditorEvent::BufferFoldToggled {
17540 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17541 folded: true,
17542 });
17543 cx.notify();
17544 }
17545
17546 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17547 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17548 return;
17549 }
17550 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17551 self.display_map.update(cx, |display_map, cx| {
17552 display_map.unfold_buffers([buffer_id], cx);
17553 });
17554 cx.emit(EditorEvent::BufferFoldToggled {
17555 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17556 folded: false,
17557 });
17558 cx.notify();
17559 }
17560
17561 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17562 self.display_map.read(cx).is_buffer_folded(buffer)
17563 }
17564
17565 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17566 self.display_map.read(cx).folded_buffers()
17567 }
17568
17569 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17570 self.display_map.update(cx, |display_map, cx| {
17571 display_map.disable_header_for_buffer(buffer_id, cx);
17572 });
17573 cx.notify();
17574 }
17575
17576 /// Removes any folds with the given ranges.
17577 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17578 &mut self,
17579 ranges: &[Range<T>],
17580 type_id: TypeId,
17581 auto_scroll: bool,
17582 cx: &mut Context<Self>,
17583 ) {
17584 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17585 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17586 });
17587 self.folds_did_change(cx);
17588 }
17589
17590 fn remove_folds_with<T: ToOffset + Clone>(
17591 &mut self,
17592 ranges: &[Range<T>],
17593 auto_scroll: bool,
17594 cx: &mut Context<Self>,
17595 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17596 ) {
17597 if ranges.is_empty() {
17598 return;
17599 }
17600
17601 let mut buffers_affected = HashSet::default();
17602 let multi_buffer = self.buffer().read(cx);
17603 for range in ranges {
17604 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17605 buffers_affected.insert(buffer.read(cx).remote_id());
17606 };
17607 }
17608
17609 self.display_map.update(cx, update);
17610
17611 if auto_scroll {
17612 self.request_autoscroll(Autoscroll::fit(), cx);
17613 }
17614
17615 cx.notify();
17616 self.scrollbar_marker_state.dirty = true;
17617 self.active_indent_guides_state.dirty = true;
17618 }
17619
17620 pub fn update_renderer_widths(
17621 &mut self,
17622 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17623 cx: &mut Context<Self>,
17624 ) -> bool {
17625 self.display_map
17626 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17627 }
17628
17629 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17630 self.display_map.read(cx).fold_placeholder.clone()
17631 }
17632
17633 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17634 self.buffer.update(cx, |buffer, cx| {
17635 buffer.set_all_diff_hunks_expanded(cx);
17636 });
17637 }
17638
17639 pub fn expand_all_diff_hunks(
17640 &mut self,
17641 _: &ExpandAllDiffHunks,
17642 _window: &mut Window,
17643 cx: &mut Context<Self>,
17644 ) {
17645 self.buffer.update(cx, |buffer, cx| {
17646 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17647 });
17648 }
17649
17650 pub fn toggle_selected_diff_hunks(
17651 &mut self,
17652 _: &ToggleSelectedDiffHunks,
17653 _window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17657 self.toggle_diff_hunks_in_ranges(ranges, cx);
17658 }
17659
17660 pub fn diff_hunks_in_ranges<'a>(
17661 &'a self,
17662 ranges: &'a [Range<Anchor>],
17663 buffer: &'a MultiBufferSnapshot,
17664 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17665 ranges.iter().flat_map(move |range| {
17666 let end_excerpt_id = range.end.excerpt_id;
17667 let range = range.to_point(buffer);
17668 let mut peek_end = range.end;
17669 if range.end.row < buffer.max_row().0 {
17670 peek_end = Point::new(range.end.row + 1, 0);
17671 }
17672 buffer
17673 .diff_hunks_in_range(range.start..peek_end)
17674 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17675 })
17676 }
17677
17678 pub fn has_stageable_diff_hunks_in_ranges(
17679 &self,
17680 ranges: &[Range<Anchor>],
17681 snapshot: &MultiBufferSnapshot,
17682 ) -> bool {
17683 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17684 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17685 }
17686
17687 pub fn toggle_staged_selected_diff_hunks(
17688 &mut self,
17689 _: &::git::ToggleStaged,
17690 _: &mut Window,
17691 cx: &mut Context<Self>,
17692 ) {
17693 let snapshot = self.buffer.read(cx).snapshot(cx);
17694 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17695 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17696 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17697 }
17698
17699 pub fn set_render_diff_hunk_controls(
17700 &mut self,
17701 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17702 cx: &mut Context<Self>,
17703 ) {
17704 self.render_diff_hunk_controls = render_diff_hunk_controls;
17705 cx.notify();
17706 }
17707
17708 pub fn stage_and_next(
17709 &mut self,
17710 _: &::git::StageAndNext,
17711 window: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 self.do_stage_or_unstage_and_next(true, window, cx);
17715 }
17716
17717 pub fn unstage_and_next(
17718 &mut self,
17719 _: &::git::UnstageAndNext,
17720 window: &mut Window,
17721 cx: &mut Context<Self>,
17722 ) {
17723 self.do_stage_or_unstage_and_next(false, window, cx);
17724 }
17725
17726 pub fn stage_or_unstage_diff_hunks(
17727 &mut self,
17728 stage: bool,
17729 ranges: Vec<Range<Anchor>>,
17730 cx: &mut Context<Self>,
17731 ) {
17732 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17733 cx.spawn(async move |this, cx| {
17734 task.await?;
17735 this.update(cx, |this, cx| {
17736 let snapshot = this.buffer.read(cx).snapshot(cx);
17737 let chunk_by = this
17738 .diff_hunks_in_ranges(&ranges, &snapshot)
17739 .chunk_by(|hunk| hunk.buffer_id);
17740 for (buffer_id, hunks) in &chunk_by {
17741 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17742 }
17743 })
17744 })
17745 .detach_and_log_err(cx);
17746 }
17747
17748 fn save_buffers_for_ranges_if_needed(
17749 &mut self,
17750 ranges: &[Range<Anchor>],
17751 cx: &mut Context<Editor>,
17752 ) -> Task<Result<()>> {
17753 let multibuffer = self.buffer.read(cx);
17754 let snapshot = multibuffer.read(cx);
17755 let buffer_ids: HashSet<_> = ranges
17756 .iter()
17757 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17758 .collect();
17759 drop(snapshot);
17760
17761 let mut buffers = HashSet::default();
17762 for buffer_id in buffer_ids {
17763 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17764 let buffer = buffer_entity.read(cx);
17765 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17766 {
17767 buffers.insert(buffer_entity);
17768 }
17769 }
17770 }
17771
17772 if let Some(project) = &self.project {
17773 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17774 } else {
17775 Task::ready(Ok(()))
17776 }
17777 }
17778
17779 fn do_stage_or_unstage_and_next(
17780 &mut self,
17781 stage: bool,
17782 window: &mut Window,
17783 cx: &mut Context<Self>,
17784 ) {
17785 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17786
17787 if ranges.iter().any(|range| range.start != range.end) {
17788 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17789 return;
17790 }
17791
17792 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17793 let snapshot = self.snapshot(window, cx);
17794 let position = self.selections.newest::<Point>(cx).head();
17795 let mut row = snapshot
17796 .buffer_snapshot
17797 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17798 .find(|hunk| hunk.row_range.start.0 > position.row)
17799 .map(|hunk| hunk.row_range.start);
17800
17801 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17802 // Outside of the project diff editor, wrap around to the beginning.
17803 if !all_diff_hunks_expanded {
17804 row = row.or_else(|| {
17805 snapshot
17806 .buffer_snapshot
17807 .diff_hunks_in_range(Point::zero()..position)
17808 .find(|hunk| hunk.row_range.end.0 < position.row)
17809 .map(|hunk| hunk.row_range.start)
17810 });
17811 }
17812
17813 if let Some(row) = row {
17814 let destination = Point::new(row.0, 0);
17815 let autoscroll = Autoscroll::center();
17816
17817 self.unfold_ranges(&[destination..destination], false, false, cx);
17818 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17819 s.select_ranges([destination..destination]);
17820 });
17821 }
17822 }
17823
17824 fn do_stage_or_unstage(
17825 &self,
17826 stage: bool,
17827 buffer_id: BufferId,
17828 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17829 cx: &mut App,
17830 ) -> Option<()> {
17831 let project = self.project.as_ref()?;
17832 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17833 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17834 let buffer_snapshot = buffer.read(cx).snapshot();
17835 let file_exists = buffer_snapshot
17836 .file()
17837 .is_some_and(|file| file.disk_state().exists());
17838 diff.update(cx, |diff, cx| {
17839 diff.stage_or_unstage_hunks(
17840 stage,
17841 &hunks
17842 .map(|hunk| buffer_diff::DiffHunk {
17843 buffer_range: hunk.buffer_range,
17844 diff_base_byte_range: hunk.diff_base_byte_range,
17845 secondary_status: hunk.secondary_status,
17846 range: Point::zero()..Point::zero(), // unused
17847 })
17848 .collect::<Vec<_>>(),
17849 &buffer_snapshot,
17850 file_exists,
17851 cx,
17852 )
17853 });
17854 None
17855 }
17856
17857 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17858 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17859 self.buffer
17860 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17861 }
17862
17863 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17864 self.buffer.update(cx, |buffer, cx| {
17865 let ranges = vec![Anchor::min()..Anchor::max()];
17866 if !buffer.all_diff_hunks_expanded()
17867 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17868 {
17869 buffer.collapse_diff_hunks(ranges, cx);
17870 true
17871 } else {
17872 false
17873 }
17874 })
17875 }
17876
17877 fn toggle_diff_hunks_in_ranges(
17878 &mut self,
17879 ranges: Vec<Range<Anchor>>,
17880 cx: &mut Context<Editor>,
17881 ) {
17882 self.buffer.update(cx, |buffer, cx| {
17883 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17884 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17885 })
17886 }
17887
17888 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17889 self.buffer.update(cx, |buffer, cx| {
17890 let snapshot = buffer.snapshot(cx);
17891 let excerpt_id = range.end.excerpt_id;
17892 let point_range = range.to_point(&snapshot);
17893 let expand = !buffer.single_hunk_is_expanded(range, cx);
17894 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17895 })
17896 }
17897
17898 pub(crate) fn apply_all_diff_hunks(
17899 &mut self,
17900 _: &ApplyAllDiffHunks,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17905
17906 let buffers = self.buffer.read(cx).all_buffers();
17907 for branch_buffer in buffers {
17908 branch_buffer.update(cx, |branch_buffer, cx| {
17909 branch_buffer.merge_into_base(Vec::new(), cx);
17910 });
17911 }
17912
17913 if let Some(project) = self.project.clone() {
17914 self.save(
17915 SaveOptions {
17916 format: true,
17917 autosave: false,
17918 },
17919 project,
17920 window,
17921 cx,
17922 )
17923 .detach_and_log_err(cx);
17924 }
17925 }
17926
17927 pub(crate) fn apply_selected_diff_hunks(
17928 &mut self,
17929 _: &ApplyDiffHunk,
17930 window: &mut Window,
17931 cx: &mut Context<Self>,
17932 ) {
17933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17934 let snapshot = self.snapshot(window, cx);
17935 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17936 let mut ranges_by_buffer = HashMap::default();
17937 self.transact(window, cx, |editor, _window, cx| {
17938 for hunk in hunks {
17939 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17940 ranges_by_buffer
17941 .entry(buffer.clone())
17942 .or_insert_with(Vec::new)
17943 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17944 }
17945 }
17946
17947 for (buffer, ranges) in ranges_by_buffer {
17948 buffer.update(cx, |buffer, cx| {
17949 buffer.merge_into_base(ranges, cx);
17950 });
17951 }
17952 });
17953
17954 if let Some(project) = self.project.clone() {
17955 self.save(
17956 SaveOptions {
17957 format: true,
17958 autosave: false,
17959 },
17960 project,
17961 window,
17962 cx,
17963 )
17964 .detach_and_log_err(cx);
17965 }
17966 }
17967
17968 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17969 if hovered != self.gutter_hovered {
17970 self.gutter_hovered = hovered;
17971 cx.notify();
17972 }
17973 }
17974
17975 pub fn insert_blocks(
17976 &mut self,
17977 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17978 autoscroll: Option<Autoscroll>,
17979 cx: &mut Context<Self>,
17980 ) -> Vec<CustomBlockId> {
17981 let blocks = self
17982 .display_map
17983 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17984 if let Some(autoscroll) = autoscroll {
17985 self.request_autoscroll(autoscroll, cx);
17986 }
17987 cx.notify();
17988 blocks
17989 }
17990
17991 pub fn resize_blocks(
17992 &mut self,
17993 heights: HashMap<CustomBlockId, u32>,
17994 autoscroll: Option<Autoscroll>,
17995 cx: &mut Context<Self>,
17996 ) {
17997 self.display_map
17998 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17999 if let Some(autoscroll) = autoscroll {
18000 self.request_autoscroll(autoscroll, cx);
18001 }
18002 cx.notify();
18003 }
18004
18005 pub fn replace_blocks(
18006 &mut self,
18007 renderers: HashMap<CustomBlockId, RenderBlock>,
18008 autoscroll: Option<Autoscroll>,
18009 cx: &mut Context<Self>,
18010 ) {
18011 self.display_map
18012 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18013 if let Some(autoscroll) = autoscroll {
18014 self.request_autoscroll(autoscroll, cx);
18015 }
18016 cx.notify();
18017 }
18018
18019 pub fn remove_blocks(
18020 &mut self,
18021 block_ids: HashSet<CustomBlockId>,
18022 autoscroll: Option<Autoscroll>,
18023 cx: &mut Context<Self>,
18024 ) {
18025 self.display_map.update(cx, |display_map, cx| {
18026 display_map.remove_blocks(block_ids, cx)
18027 });
18028 if let Some(autoscroll) = autoscroll {
18029 self.request_autoscroll(autoscroll, cx);
18030 }
18031 cx.notify();
18032 }
18033
18034 pub fn row_for_block(
18035 &self,
18036 block_id: CustomBlockId,
18037 cx: &mut Context<Self>,
18038 ) -> Option<DisplayRow> {
18039 self.display_map
18040 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18041 }
18042
18043 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18044 self.focused_block = Some(focused_block);
18045 }
18046
18047 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18048 self.focused_block.take()
18049 }
18050
18051 pub fn insert_creases(
18052 &mut self,
18053 creases: impl IntoIterator<Item = Crease<Anchor>>,
18054 cx: &mut Context<Self>,
18055 ) -> Vec<CreaseId> {
18056 self.display_map
18057 .update(cx, |map, cx| map.insert_creases(creases, cx))
18058 }
18059
18060 pub fn remove_creases(
18061 &mut self,
18062 ids: impl IntoIterator<Item = CreaseId>,
18063 cx: &mut Context<Self>,
18064 ) -> Vec<(CreaseId, Range<Anchor>)> {
18065 self.display_map
18066 .update(cx, |map, cx| map.remove_creases(ids, cx))
18067 }
18068
18069 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18070 self.display_map
18071 .update(cx, |map, cx| map.snapshot(cx))
18072 .longest_row()
18073 }
18074
18075 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18076 self.display_map
18077 .update(cx, |map, cx| map.snapshot(cx))
18078 .max_point()
18079 }
18080
18081 pub fn text(&self, cx: &App) -> String {
18082 self.buffer.read(cx).read(cx).text()
18083 }
18084
18085 pub fn is_empty(&self, cx: &App) -> bool {
18086 self.buffer.read(cx).read(cx).is_empty()
18087 }
18088
18089 pub fn text_option(&self, cx: &App) -> Option<String> {
18090 let text = self.text(cx);
18091 let text = text.trim();
18092
18093 if text.is_empty() {
18094 return None;
18095 }
18096
18097 Some(text.to_string())
18098 }
18099
18100 pub fn set_text(
18101 &mut self,
18102 text: impl Into<Arc<str>>,
18103 window: &mut Window,
18104 cx: &mut Context<Self>,
18105 ) {
18106 self.transact(window, cx, |this, _, cx| {
18107 this.buffer
18108 .read(cx)
18109 .as_singleton()
18110 .expect("you can only call set_text on editors for singleton buffers")
18111 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18112 });
18113 }
18114
18115 pub fn display_text(&self, cx: &mut App) -> String {
18116 self.display_map
18117 .update(cx, |map, cx| map.snapshot(cx))
18118 .text()
18119 }
18120
18121 fn create_minimap(
18122 &self,
18123 minimap_settings: MinimapSettings,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) -> Option<Entity<Self>> {
18127 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18128 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18129 }
18130
18131 fn initialize_new_minimap(
18132 &self,
18133 minimap_settings: MinimapSettings,
18134 window: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) -> Entity<Self> {
18137 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18138
18139 let mut minimap = Editor::new_internal(
18140 EditorMode::Minimap {
18141 parent: cx.weak_entity(),
18142 },
18143 self.buffer.clone(),
18144 None,
18145 Some(self.display_map.clone()),
18146 window,
18147 cx,
18148 );
18149 minimap.scroll_manager.clone_state(&self.scroll_manager);
18150 minimap.set_text_style_refinement(TextStyleRefinement {
18151 font_size: Some(MINIMAP_FONT_SIZE),
18152 font_weight: Some(MINIMAP_FONT_WEIGHT),
18153 ..Default::default()
18154 });
18155 minimap.update_minimap_configuration(minimap_settings, cx);
18156 cx.new(|_| minimap)
18157 }
18158
18159 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18160 let current_line_highlight = minimap_settings
18161 .current_line_highlight
18162 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18163 self.set_current_line_highlight(Some(current_line_highlight));
18164 }
18165
18166 pub fn minimap(&self) -> Option<&Entity<Self>> {
18167 self.minimap
18168 .as_ref()
18169 .filter(|_| self.minimap_visibility.visible())
18170 }
18171
18172 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18173 let mut wrap_guides = smallvec![];
18174
18175 if self.show_wrap_guides == Some(false) {
18176 return wrap_guides;
18177 }
18178
18179 let settings = self.buffer.read(cx).language_settings(cx);
18180 if settings.show_wrap_guides {
18181 match self.soft_wrap_mode(cx) {
18182 SoftWrap::Column(soft_wrap) => {
18183 wrap_guides.push((soft_wrap as usize, true));
18184 }
18185 SoftWrap::Bounded(soft_wrap) => {
18186 wrap_guides.push((soft_wrap as usize, true));
18187 }
18188 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18189 }
18190 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18191 }
18192
18193 wrap_guides
18194 }
18195
18196 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18197 let settings = self.buffer.read(cx).language_settings(cx);
18198 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18199 match mode {
18200 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18201 SoftWrap::None
18202 }
18203 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18204 language_settings::SoftWrap::PreferredLineLength => {
18205 SoftWrap::Column(settings.preferred_line_length)
18206 }
18207 language_settings::SoftWrap::Bounded => {
18208 SoftWrap::Bounded(settings.preferred_line_length)
18209 }
18210 }
18211 }
18212
18213 pub fn set_soft_wrap_mode(
18214 &mut self,
18215 mode: language_settings::SoftWrap,
18216
18217 cx: &mut Context<Self>,
18218 ) {
18219 self.soft_wrap_mode_override = Some(mode);
18220 cx.notify();
18221 }
18222
18223 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18224 self.hard_wrap = hard_wrap;
18225 cx.notify();
18226 }
18227
18228 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18229 self.text_style_refinement = Some(style);
18230 }
18231
18232 /// called by the Element so we know what style we were most recently rendered with.
18233 pub(crate) fn set_style(
18234 &mut self,
18235 style: EditorStyle,
18236 window: &mut Window,
18237 cx: &mut Context<Self>,
18238 ) {
18239 // We intentionally do not inform the display map about the minimap style
18240 // so that wrapping is not recalculated and stays consistent for the editor
18241 // and its linked minimap.
18242 if !self.mode.is_minimap() {
18243 let rem_size = window.rem_size();
18244 self.display_map.update(cx, |map, cx| {
18245 map.set_font(
18246 style.text.font(),
18247 style.text.font_size.to_pixels(rem_size),
18248 cx,
18249 )
18250 });
18251 }
18252 self.style = Some(style);
18253 }
18254
18255 pub fn style(&self) -> Option<&EditorStyle> {
18256 self.style.as_ref()
18257 }
18258
18259 // Called by the element. This method is not designed to be called outside of the editor
18260 // element's layout code because it does not notify when rewrapping is computed synchronously.
18261 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18262 self.display_map
18263 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18264 }
18265
18266 pub fn set_soft_wrap(&mut self) {
18267 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18268 }
18269
18270 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18271 if self.soft_wrap_mode_override.is_some() {
18272 self.soft_wrap_mode_override.take();
18273 } else {
18274 let soft_wrap = match self.soft_wrap_mode(cx) {
18275 SoftWrap::GitDiff => return,
18276 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18277 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18278 language_settings::SoftWrap::None
18279 }
18280 };
18281 self.soft_wrap_mode_override = Some(soft_wrap);
18282 }
18283 cx.notify();
18284 }
18285
18286 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18287 let Some(workspace) = self.workspace() else {
18288 return;
18289 };
18290 let fs = workspace.read(cx).app_state().fs.clone();
18291 let current_show = TabBarSettings::get_global(cx).show;
18292 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18293 setting.show = Some(!current_show);
18294 });
18295 }
18296
18297 pub fn toggle_indent_guides(
18298 &mut self,
18299 _: &ToggleIndentGuides,
18300 _: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) {
18303 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18304 self.buffer
18305 .read(cx)
18306 .language_settings(cx)
18307 .indent_guides
18308 .enabled
18309 });
18310 self.show_indent_guides = Some(!currently_enabled);
18311 cx.notify();
18312 }
18313
18314 fn should_show_indent_guides(&self) -> Option<bool> {
18315 self.show_indent_guides
18316 }
18317
18318 pub fn toggle_line_numbers(
18319 &mut self,
18320 _: &ToggleLineNumbers,
18321 _: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 let mut editor_settings = EditorSettings::get_global(cx).clone();
18325 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18326 EditorSettings::override_global(editor_settings, cx);
18327 }
18328
18329 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18330 if let Some(show_line_numbers) = self.show_line_numbers {
18331 return show_line_numbers;
18332 }
18333 EditorSettings::get_global(cx).gutter.line_numbers
18334 }
18335
18336 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18337 self.use_relative_line_numbers
18338 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18339 }
18340
18341 pub fn toggle_relative_line_numbers(
18342 &mut self,
18343 _: &ToggleRelativeLineNumbers,
18344 _: &mut Window,
18345 cx: &mut Context<Self>,
18346 ) {
18347 let is_relative = self.should_use_relative_line_numbers(cx);
18348 self.set_relative_line_number(Some(!is_relative), cx)
18349 }
18350
18351 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18352 self.use_relative_line_numbers = is_relative;
18353 cx.notify();
18354 }
18355
18356 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18357 self.show_gutter = show_gutter;
18358 cx.notify();
18359 }
18360
18361 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18362 self.show_scrollbars = ScrollbarAxes {
18363 horizontal: show,
18364 vertical: show,
18365 };
18366 cx.notify();
18367 }
18368
18369 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18370 self.show_scrollbars.vertical = show;
18371 cx.notify();
18372 }
18373
18374 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18375 self.show_scrollbars.horizontal = show;
18376 cx.notify();
18377 }
18378
18379 pub fn set_minimap_visibility(
18380 &mut self,
18381 minimap_visibility: MinimapVisibility,
18382 window: &mut Window,
18383 cx: &mut Context<Self>,
18384 ) {
18385 if self.minimap_visibility != minimap_visibility {
18386 if minimap_visibility.visible() && self.minimap.is_none() {
18387 let minimap_settings = EditorSettings::get_global(cx).minimap;
18388 self.minimap =
18389 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18390 }
18391 self.minimap_visibility = minimap_visibility;
18392 cx.notify();
18393 }
18394 }
18395
18396 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18397 self.set_show_scrollbars(false, cx);
18398 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18399 }
18400
18401 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18402 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18403 }
18404
18405 /// Normally the text in full mode and auto height editors is padded on the
18406 /// left side by roughly half a character width for improved hit testing.
18407 ///
18408 /// Use this method to disable this for cases where this is not wanted (e.g.
18409 /// if you want to align the editor text with some other text above or below)
18410 /// or if you want to add this padding to single-line editors.
18411 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18412 self.offset_content = offset_content;
18413 cx.notify();
18414 }
18415
18416 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18417 self.show_line_numbers = Some(show_line_numbers);
18418 cx.notify();
18419 }
18420
18421 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18422 self.disable_expand_excerpt_buttons = true;
18423 cx.notify();
18424 }
18425
18426 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18427 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18428 cx.notify();
18429 }
18430
18431 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18432 self.show_code_actions = Some(show_code_actions);
18433 cx.notify();
18434 }
18435
18436 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18437 self.show_runnables = Some(show_runnables);
18438 cx.notify();
18439 }
18440
18441 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18442 self.show_breakpoints = Some(show_breakpoints);
18443 cx.notify();
18444 }
18445
18446 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18447 if self.display_map.read(cx).masked != masked {
18448 self.display_map.update(cx, |map, _| map.masked = masked);
18449 }
18450 cx.notify()
18451 }
18452
18453 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18454 self.show_wrap_guides = Some(show_wrap_guides);
18455 cx.notify();
18456 }
18457
18458 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18459 self.show_indent_guides = Some(show_indent_guides);
18460 cx.notify();
18461 }
18462
18463 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18464 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18465 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18466 if let Some(dir) = file.abs_path(cx).parent() {
18467 return Some(dir.to_owned());
18468 }
18469 }
18470
18471 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18472 return Some(project_path.path.to_path_buf());
18473 }
18474 }
18475
18476 None
18477 }
18478
18479 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18480 self.active_excerpt(cx)?
18481 .1
18482 .read(cx)
18483 .file()
18484 .and_then(|f| f.as_local())
18485 }
18486
18487 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18488 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18489 let buffer = buffer.read(cx);
18490 if let Some(project_path) = buffer.project_path(cx) {
18491 let project = self.project.as_ref()?.read(cx);
18492 project.absolute_path(&project_path, cx)
18493 } else {
18494 buffer
18495 .file()
18496 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18497 }
18498 })
18499 }
18500
18501 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18502 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18503 let project_path = buffer.read(cx).project_path(cx)?;
18504 let project = self.project.as_ref()?.read(cx);
18505 let entry = project.entry_for_path(&project_path, cx)?;
18506 let path = entry.path.to_path_buf();
18507 Some(path)
18508 })
18509 }
18510
18511 pub fn reveal_in_finder(
18512 &mut self,
18513 _: &RevealInFileManager,
18514 _window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 if let Some(target) = self.target_file(cx) {
18518 cx.reveal_path(&target.abs_path(cx));
18519 }
18520 }
18521
18522 pub fn copy_path(
18523 &mut self,
18524 _: &zed_actions::workspace::CopyPath,
18525 _window: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) {
18528 if let Some(path) = self.target_file_abs_path(cx) {
18529 if let Some(path) = path.to_str() {
18530 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18531 }
18532 }
18533 }
18534
18535 pub fn copy_relative_path(
18536 &mut self,
18537 _: &zed_actions::workspace::CopyRelativePath,
18538 _window: &mut Window,
18539 cx: &mut Context<Self>,
18540 ) {
18541 if let Some(path) = self.target_file_path(cx) {
18542 if let Some(path) = path.to_str() {
18543 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18544 }
18545 }
18546 }
18547
18548 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18549 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18550 buffer.read(cx).project_path(cx)
18551 } else {
18552 None
18553 }
18554 }
18555
18556 // Returns true if the editor handled a go-to-line request
18557 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18558 maybe!({
18559 let breakpoint_store = self.breakpoint_store.as_ref()?;
18560
18561 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18562 else {
18563 self.clear_row_highlights::<ActiveDebugLine>();
18564 return None;
18565 };
18566
18567 let position = active_stack_frame.position;
18568 let buffer_id = position.buffer_id?;
18569 let snapshot = self
18570 .project
18571 .as_ref()?
18572 .read(cx)
18573 .buffer_for_id(buffer_id, cx)?
18574 .read(cx)
18575 .snapshot();
18576
18577 let mut handled = false;
18578 for (id, ExcerptRange { context, .. }) in
18579 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18580 {
18581 if context.start.cmp(&position, &snapshot).is_ge()
18582 || context.end.cmp(&position, &snapshot).is_lt()
18583 {
18584 continue;
18585 }
18586 let snapshot = self.buffer.read(cx).snapshot(cx);
18587 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18588
18589 handled = true;
18590 self.clear_row_highlights::<ActiveDebugLine>();
18591
18592 self.go_to_line::<ActiveDebugLine>(
18593 multibuffer_anchor,
18594 Some(cx.theme().colors().editor_debugger_active_line_background),
18595 window,
18596 cx,
18597 );
18598
18599 cx.notify();
18600 }
18601
18602 handled.then_some(())
18603 })
18604 .is_some()
18605 }
18606
18607 pub fn copy_file_name_without_extension(
18608 &mut self,
18609 _: &CopyFileNameWithoutExtension,
18610 _: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 if let Some(file) = self.target_file(cx) {
18614 if let Some(file_stem) = file.path().file_stem() {
18615 if let Some(name) = file_stem.to_str() {
18616 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18617 }
18618 }
18619 }
18620 }
18621
18622 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18623 if let Some(file) = self.target_file(cx) {
18624 if let Some(file_name) = file.path().file_name() {
18625 if let Some(name) = file_name.to_str() {
18626 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18627 }
18628 }
18629 }
18630 }
18631
18632 pub fn toggle_git_blame(
18633 &mut self,
18634 _: &::git::Blame,
18635 window: &mut Window,
18636 cx: &mut Context<Self>,
18637 ) {
18638 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18639
18640 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18641 self.start_git_blame(true, window, cx);
18642 }
18643
18644 cx.notify();
18645 }
18646
18647 pub fn toggle_git_blame_inline(
18648 &mut self,
18649 _: &ToggleGitBlameInline,
18650 window: &mut Window,
18651 cx: &mut Context<Self>,
18652 ) {
18653 self.toggle_git_blame_inline_internal(true, window, cx);
18654 cx.notify();
18655 }
18656
18657 pub fn open_git_blame_commit(
18658 &mut self,
18659 _: &OpenGitBlameCommit,
18660 window: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) {
18663 self.open_git_blame_commit_internal(window, cx);
18664 }
18665
18666 fn open_git_blame_commit_internal(
18667 &mut self,
18668 window: &mut Window,
18669 cx: &mut Context<Self>,
18670 ) -> Option<()> {
18671 let blame = self.blame.as_ref()?;
18672 let snapshot = self.snapshot(window, cx);
18673 let cursor = self.selections.newest::<Point>(cx).head();
18674 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18675 let blame_entry = blame
18676 .update(cx, |blame, cx| {
18677 blame
18678 .blame_for_rows(
18679 &[RowInfo {
18680 buffer_id: Some(buffer.remote_id()),
18681 buffer_row: Some(point.row),
18682 ..Default::default()
18683 }],
18684 cx,
18685 )
18686 .next()
18687 })
18688 .flatten()?;
18689 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18690 let repo = blame.read(cx).repository(cx)?;
18691 let workspace = self.workspace()?.downgrade();
18692 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18693 None
18694 }
18695
18696 pub fn git_blame_inline_enabled(&self) -> bool {
18697 self.git_blame_inline_enabled
18698 }
18699
18700 pub fn toggle_selection_menu(
18701 &mut self,
18702 _: &ToggleSelectionMenu,
18703 _: &mut Window,
18704 cx: &mut Context<Self>,
18705 ) {
18706 self.show_selection_menu = self
18707 .show_selection_menu
18708 .map(|show_selections_menu| !show_selections_menu)
18709 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18710
18711 cx.notify();
18712 }
18713
18714 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18715 self.show_selection_menu
18716 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18717 }
18718
18719 fn start_git_blame(
18720 &mut self,
18721 user_triggered: bool,
18722 window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 if let Some(project) = self.project.as_ref() {
18726 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18727 return;
18728 };
18729
18730 if buffer.read(cx).file().is_none() {
18731 return;
18732 }
18733
18734 let focused = self.focus_handle(cx).contains_focused(window, cx);
18735
18736 let project = project.clone();
18737 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18738 self.blame_subscription =
18739 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18740 self.blame = Some(blame);
18741 }
18742 }
18743
18744 fn toggle_git_blame_inline_internal(
18745 &mut self,
18746 user_triggered: bool,
18747 window: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 if self.git_blame_inline_enabled {
18751 self.git_blame_inline_enabled = false;
18752 self.show_git_blame_inline = false;
18753 self.show_git_blame_inline_delay_task.take();
18754 } else {
18755 self.git_blame_inline_enabled = true;
18756 self.start_git_blame_inline(user_triggered, window, cx);
18757 }
18758
18759 cx.notify();
18760 }
18761
18762 fn start_git_blame_inline(
18763 &mut self,
18764 user_triggered: bool,
18765 window: &mut Window,
18766 cx: &mut Context<Self>,
18767 ) {
18768 self.start_git_blame(user_triggered, window, cx);
18769
18770 if ProjectSettings::get_global(cx)
18771 .git
18772 .inline_blame_delay()
18773 .is_some()
18774 {
18775 self.start_inline_blame_timer(window, cx);
18776 } else {
18777 self.show_git_blame_inline = true
18778 }
18779 }
18780
18781 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18782 self.blame.as_ref()
18783 }
18784
18785 pub fn show_git_blame_gutter(&self) -> bool {
18786 self.show_git_blame_gutter
18787 }
18788
18789 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18790 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18791 }
18792
18793 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18794 self.show_git_blame_inline
18795 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18796 && !self.newest_selection_head_on_empty_line(cx)
18797 && self.has_blame_entries(cx)
18798 }
18799
18800 fn has_blame_entries(&self, cx: &App) -> bool {
18801 self.blame()
18802 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18803 }
18804
18805 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18806 let cursor_anchor = self.selections.newest_anchor().head();
18807
18808 let snapshot = self.buffer.read(cx).snapshot(cx);
18809 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18810
18811 snapshot.line_len(buffer_row) == 0
18812 }
18813
18814 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18815 let buffer_and_selection = maybe!({
18816 let selection = self.selections.newest::<Point>(cx);
18817 let selection_range = selection.range();
18818
18819 let multi_buffer = self.buffer().read(cx);
18820 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18821 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18822
18823 let (buffer, range, _) = if selection.reversed {
18824 buffer_ranges.first()
18825 } else {
18826 buffer_ranges.last()
18827 }?;
18828
18829 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18830 ..text::ToPoint::to_point(&range.end, &buffer).row;
18831 Some((
18832 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18833 selection,
18834 ))
18835 });
18836
18837 let Some((buffer, selection)) = buffer_and_selection else {
18838 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18839 };
18840
18841 let Some(project) = self.project.as_ref() else {
18842 return Task::ready(Err(anyhow!("editor does not have project")));
18843 };
18844
18845 project.update(cx, |project, cx| {
18846 project.get_permalink_to_line(&buffer, selection, cx)
18847 })
18848 }
18849
18850 pub fn copy_permalink_to_line(
18851 &mut self,
18852 _: &CopyPermalinkToLine,
18853 window: &mut Window,
18854 cx: &mut Context<Self>,
18855 ) {
18856 let permalink_task = self.get_permalink_to_line(cx);
18857 let workspace = self.workspace();
18858
18859 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18860 Ok(permalink) => {
18861 cx.update(|_, cx| {
18862 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18863 })
18864 .ok();
18865 }
18866 Err(err) => {
18867 let message = format!("Failed to copy permalink: {err}");
18868
18869 anyhow::Result::<()>::Err(err).log_err();
18870
18871 if let Some(workspace) = workspace {
18872 workspace
18873 .update_in(cx, |workspace, _, cx| {
18874 struct CopyPermalinkToLine;
18875
18876 workspace.show_toast(
18877 Toast::new(
18878 NotificationId::unique::<CopyPermalinkToLine>(),
18879 message,
18880 ),
18881 cx,
18882 )
18883 })
18884 .ok();
18885 }
18886 }
18887 })
18888 .detach();
18889 }
18890
18891 pub fn copy_file_location(
18892 &mut self,
18893 _: &CopyFileLocation,
18894 _: &mut Window,
18895 cx: &mut Context<Self>,
18896 ) {
18897 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18898 if let Some(file) = self.target_file(cx) {
18899 if let Some(path) = file.path().to_str() {
18900 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18901 }
18902 }
18903 }
18904
18905 pub fn open_permalink_to_line(
18906 &mut self,
18907 _: &OpenPermalinkToLine,
18908 window: &mut Window,
18909 cx: &mut Context<Self>,
18910 ) {
18911 let permalink_task = self.get_permalink_to_line(cx);
18912 let workspace = self.workspace();
18913
18914 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18915 Ok(permalink) => {
18916 cx.update(|_, cx| {
18917 cx.open_url(permalink.as_ref());
18918 })
18919 .ok();
18920 }
18921 Err(err) => {
18922 let message = format!("Failed to open permalink: {err}");
18923
18924 anyhow::Result::<()>::Err(err).log_err();
18925
18926 if let Some(workspace) = workspace {
18927 workspace
18928 .update(cx, |workspace, cx| {
18929 struct OpenPermalinkToLine;
18930
18931 workspace.show_toast(
18932 Toast::new(
18933 NotificationId::unique::<OpenPermalinkToLine>(),
18934 message,
18935 ),
18936 cx,
18937 )
18938 })
18939 .ok();
18940 }
18941 }
18942 })
18943 .detach();
18944 }
18945
18946 pub fn insert_uuid_v4(
18947 &mut self,
18948 _: &InsertUuidV4,
18949 window: &mut Window,
18950 cx: &mut Context<Self>,
18951 ) {
18952 self.insert_uuid(UuidVersion::V4, window, cx);
18953 }
18954
18955 pub fn insert_uuid_v7(
18956 &mut self,
18957 _: &InsertUuidV7,
18958 window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) {
18961 self.insert_uuid(UuidVersion::V7, window, cx);
18962 }
18963
18964 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18966 self.transact(window, cx, |this, window, cx| {
18967 let edits = this
18968 .selections
18969 .all::<Point>(cx)
18970 .into_iter()
18971 .map(|selection| {
18972 let uuid = match version {
18973 UuidVersion::V4 => uuid::Uuid::new_v4(),
18974 UuidVersion::V7 => uuid::Uuid::now_v7(),
18975 };
18976
18977 (selection.range(), uuid.to_string())
18978 });
18979 this.edit(edits, cx);
18980 this.refresh_inline_completion(true, false, window, cx);
18981 });
18982 }
18983
18984 pub fn open_selections_in_multibuffer(
18985 &mut self,
18986 _: &OpenSelectionsInMultibuffer,
18987 window: &mut Window,
18988 cx: &mut Context<Self>,
18989 ) {
18990 let multibuffer = self.buffer.read(cx);
18991
18992 let Some(buffer) = multibuffer.as_singleton() else {
18993 return;
18994 };
18995
18996 let Some(workspace) = self.workspace() else {
18997 return;
18998 };
18999
19000 let title = multibuffer.title(cx).to_string();
19001
19002 let locations = self
19003 .selections
19004 .all_anchors(cx)
19005 .into_iter()
19006 .map(|selection| Location {
19007 buffer: buffer.clone(),
19008 range: selection.start.text_anchor..selection.end.text_anchor,
19009 })
19010 .collect::<Vec<_>>();
19011
19012 cx.spawn_in(window, async move |_, cx| {
19013 workspace.update_in(cx, |workspace, window, cx| {
19014 Self::open_locations_in_multibuffer(
19015 workspace,
19016 locations,
19017 format!("Selections for '{title}'"),
19018 false,
19019 MultibufferSelectionMode::All,
19020 window,
19021 cx,
19022 );
19023 })
19024 })
19025 .detach();
19026 }
19027
19028 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19029 /// last highlight added will be used.
19030 ///
19031 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19032 pub fn highlight_rows<T: 'static>(
19033 &mut self,
19034 range: Range<Anchor>,
19035 color: Hsla,
19036 options: RowHighlightOptions,
19037 cx: &mut Context<Self>,
19038 ) {
19039 let snapshot = self.buffer().read(cx).snapshot(cx);
19040 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19041 let ix = row_highlights.binary_search_by(|highlight| {
19042 Ordering::Equal
19043 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19044 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19045 });
19046
19047 if let Err(mut ix) = ix {
19048 let index = post_inc(&mut self.highlight_order);
19049
19050 // If this range intersects with the preceding highlight, then merge it with
19051 // the preceding highlight. Otherwise insert a new highlight.
19052 let mut merged = false;
19053 if ix > 0 {
19054 let prev_highlight = &mut row_highlights[ix - 1];
19055 if prev_highlight
19056 .range
19057 .end
19058 .cmp(&range.start, &snapshot)
19059 .is_ge()
19060 {
19061 ix -= 1;
19062 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19063 prev_highlight.range.end = range.end;
19064 }
19065 merged = true;
19066 prev_highlight.index = index;
19067 prev_highlight.color = color;
19068 prev_highlight.options = options;
19069 }
19070 }
19071
19072 if !merged {
19073 row_highlights.insert(
19074 ix,
19075 RowHighlight {
19076 range: range.clone(),
19077 index,
19078 color,
19079 options,
19080 type_id: TypeId::of::<T>(),
19081 },
19082 );
19083 }
19084
19085 // If any of the following highlights intersect with this one, merge them.
19086 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19087 let highlight = &row_highlights[ix];
19088 if next_highlight
19089 .range
19090 .start
19091 .cmp(&highlight.range.end, &snapshot)
19092 .is_le()
19093 {
19094 if next_highlight
19095 .range
19096 .end
19097 .cmp(&highlight.range.end, &snapshot)
19098 .is_gt()
19099 {
19100 row_highlights[ix].range.end = next_highlight.range.end;
19101 }
19102 row_highlights.remove(ix + 1);
19103 } else {
19104 break;
19105 }
19106 }
19107 }
19108 }
19109
19110 /// Remove any highlighted row ranges of the given type that intersect the
19111 /// given ranges.
19112 pub fn remove_highlighted_rows<T: 'static>(
19113 &mut self,
19114 ranges_to_remove: Vec<Range<Anchor>>,
19115 cx: &mut Context<Self>,
19116 ) {
19117 let snapshot = self.buffer().read(cx).snapshot(cx);
19118 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19119 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19120 row_highlights.retain(|highlight| {
19121 while let Some(range_to_remove) = ranges_to_remove.peek() {
19122 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19123 Ordering::Less | Ordering::Equal => {
19124 ranges_to_remove.next();
19125 }
19126 Ordering::Greater => {
19127 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19128 Ordering::Less | Ordering::Equal => {
19129 return false;
19130 }
19131 Ordering::Greater => break,
19132 }
19133 }
19134 }
19135 }
19136
19137 true
19138 })
19139 }
19140
19141 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19142 pub fn clear_row_highlights<T: 'static>(&mut self) {
19143 self.highlighted_rows.remove(&TypeId::of::<T>());
19144 }
19145
19146 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19147 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19148 self.highlighted_rows
19149 .get(&TypeId::of::<T>())
19150 .map_or(&[] as &[_], |vec| vec.as_slice())
19151 .iter()
19152 .map(|highlight| (highlight.range.clone(), highlight.color))
19153 }
19154
19155 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19156 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19157 /// Allows to ignore certain kinds of highlights.
19158 pub fn highlighted_display_rows(
19159 &self,
19160 window: &mut Window,
19161 cx: &mut App,
19162 ) -> BTreeMap<DisplayRow, LineHighlight> {
19163 let snapshot = self.snapshot(window, cx);
19164 let mut used_highlight_orders = HashMap::default();
19165 self.highlighted_rows
19166 .iter()
19167 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19168 .fold(
19169 BTreeMap::<DisplayRow, LineHighlight>::new(),
19170 |mut unique_rows, highlight| {
19171 let start = highlight.range.start.to_display_point(&snapshot);
19172 let end = highlight.range.end.to_display_point(&snapshot);
19173 let start_row = start.row().0;
19174 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19175 && end.column() == 0
19176 {
19177 end.row().0.saturating_sub(1)
19178 } else {
19179 end.row().0
19180 };
19181 for row in start_row..=end_row {
19182 let used_index =
19183 used_highlight_orders.entry(row).or_insert(highlight.index);
19184 if highlight.index >= *used_index {
19185 *used_index = highlight.index;
19186 unique_rows.insert(
19187 DisplayRow(row),
19188 LineHighlight {
19189 include_gutter: highlight.options.include_gutter,
19190 border: None,
19191 background: highlight.color.into(),
19192 type_id: Some(highlight.type_id),
19193 },
19194 );
19195 }
19196 }
19197 unique_rows
19198 },
19199 )
19200 }
19201
19202 pub fn highlighted_display_row_for_autoscroll(
19203 &self,
19204 snapshot: &DisplaySnapshot,
19205 ) -> Option<DisplayRow> {
19206 self.highlighted_rows
19207 .values()
19208 .flat_map(|highlighted_rows| highlighted_rows.iter())
19209 .filter_map(|highlight| {
19210 if highlight.options.autoscroll {
19211 Some(highlight.range.start.to_display_point(snapshot).row())
19212 } else {
19213 None
19214 }
19215 })
19216 .min()
19217 }
19218
19219 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19220 self.highlight_background::<SearchWithinRange>(
19221 ranges,
19222 |colors| colors.colors().editor_document_highlight_read_background,
19223 cx,
19224 )
19225 }
19226
19227 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19228 self.breadcrumb_header = Some(new_header);
19229 }
19230
19231 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19232 self.clear_background_highlights::<SearchWithinRange>(cx);
19233 }
19234
19235 pub fn highlight_background<T: 'static>(
19236 &mut self,
19237 ranges: &[Range<Anchor>],
19238 color_fetcher: fn(&Theme) -> Hsla,
19239 cx: &mut Context<Self>,
19240 ) {
19241 self.background_highlights.insert(
19242 HighlightKey::Type(TypeId::of::<T>()),
19243 (color_fetcher, Arc::from(ranges)),
19244 );
19245 self.scrollbar_marker_state.dirty = true;
19246 cx.notify();
19247 }
19248
19249 pub fn highlight_background_key<T: 'static>(
19250 &mut self,
19251 key: usize,
19252 ranges: &[Range<Anchor>],
19253 color_fetcher: fn(&Theme) -> Hsla,
19254 cx: &mut Context<Self>,
19255 ) {
19256 self.background_highlights.insert(
19257 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19258 (color_fetcher, Arc::from(ranges)),
19259 );
19260 self.scrollbar_marker_state.dirty = true;
19261 cx.notify();
19262 }
19263
19264 pub fn clear_background_highlights<T: 'static>(
19265 &mut self,
19266 cx: &mut Context<Self>,
19267 ) -> Option<BackgroundHighlight> {
19268 let text_highlights = self
19269 .background_highlights
19270 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19271 if !text_highlights.1.is_empty() {
19272 self.scrollbar_marker_state.dirty = true;
19273 cx.notify();
19274 }
19275 Some(text_highlights)
19276 }
19277
19278 pub fn highlight_gutter<T: 'static>(
19279 &mut self,
19280 ranges: impl Into<Vec<Range<Anchor>>>,
19281 color_fetcher: fn(&App) -> Hsla,
19282 cx: &mut Context<Self>,
19283 ) {
19284 self.gutter_highlights
19285 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19286 cx.notify();
19287 }
19288
19289 pub fn clear_gutter_highlights<T: 'static>(
19290 &mut self,
19291 cx: &mut Context<Self>,
19292 ) -> Option<GutterHighlight> {
19293 cx.notify();
19294 self.gutter_highlights.remove(&TypeId::of::<T>())
19295 }
19296
19297 pub fn insert_gutter_highlight<T: 'static>(
19298 &mut self,
19299 range: Range<Anchor>,
19300 color_fetcher: fn(&App) -> Hsla,
19301 cx: &mut Context<Self>,
19302 ) {
19303 let snapshot = self.buffer().read(cx).snapshot(cx);
19304 let mut highlights = self
19305 .gutter_highlights
19306 .remove(&TypeId::of::<T>())
19307 .map(|(_, highlights)| highlights)
19308 .unwrap_or_default();
19309 let ix = highlights.binary_search_by(|highlight| {
19310 Ordering::Equal
19311 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19312 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19313 });
19314 if let Err(ix) = ix {
19315 highlights.insert(ix, range);
19316 }
19317 self.gutter_highlights
19318 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19319 }
19320
19321 pub fn remove_gutter_highlights<T: 'static>(
19322 &mut self,
19323 ranges_to_remove: Vec<Range<Anchor>>,
19324 cx: &mut Context<Self>,
19325 ) {
19326 let snapshot = self.buffer().read(cx).snapshot(cx);
19327 let Some((color_fetcher, mut gutter_highlights)) =
19328 self.gutter_highlights.remove(&TypeId::of::<T>())
19329 else {
19330 return;
19331 };
19332 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19333 gutter_highlights.retain(|highlight| {
19334 while let Some(range_to_remove) = ranges_to_remove.peek() {
19335 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19336 Ordering::Less | Ordering::Equal => {
19337 ranges_to_remove.next();
19338 }
19339 Ordering::Greater => {
19340 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19341 Ordering::Less | Ordering::Equal => {
19342 return false;
19343 }
19344 Ordering::Greater => break,
19345 }
19346 }
19347 }
19348 }
19349
19350 true
19351 });
19352 self.gutter_highlights
19353 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19354 }
19355
19356 #[cfg(feature = "test-support")]
19357 pub fn all_text_highlights(
19358 &self,
19359 window: &mut Window,
19360 cx: &mut Context<Self>,
19361 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19362 let snapshot = self.snapshot(window, cx);
19363 self.display_map.update(cx, |display_map, _| {
19364 display_map
19365 .all_text_highlights()
19366 .map(|highlight| {
19367 let (style, ranges) = highlight.as_ref();
19368 (
19369 *style,
19370 ranges
19371 .iter()
19372 .map(|range| range.clone().to_display_points(&snapshot))
19373 .collect(),
19374 )
19375 })
19376 .collect()
19377 })
19378 }
19379
19380 #[cfg(feature = "test-support")]
19381 pub fn all_text_background_highlights(
19382 &self,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19386 let snapshot = self.snapshot(window, cx);
19387 let buffer = &snapshot.buffer_snapshot;
19388 let start = buffer.anchor_before(0);
19389 let end = buffer.anchor_after(buffer.len());
19390 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19391 }
19392
19393 #[cfg(feature = "test-support")]
19394 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19395 let snapshot = self.buffer().read(cx).snapshot(cx);
19396
19397 let highlights = self
19398 .background_highlights
19399 .get(&HighlightKey::Type(TypeId::of::<
19400 items::BufferSearchHighlights,
19401 >()));
19402
19403 if let Some((_color, ranges)) = highlights {
19404 ranges
19405 .iter()
19406 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19407 .collect_vec()
19408 } else {
19409 vec![]
19410 }
19411 }
19412
19413 fn document_highlights_for_position<'a>(
19414 &'a self,
19415 position: Anchor,
19416 buffer: &'a MultiBufferSnapshot,
19417 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19418 let read_highlights = self
19419 .background_highlights
19420 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19421 .map(|h| &h.1);
19422 let write_highlights = self
19423 .background_highlights
19424 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19425 .map(|h| &h.1);
19426 let left_position = position.bias_left(buffer);
19427 let right_position = position.bias_right(buffer);
19428 read_highlights
19429 .into_iter()
19430 .chain(write_highlights)
19431 .flat_map(move |ranges| {
19432 let start_ix = match ranges.binary_search_by(|probe| {
19433 let cmp = probe.end.cmp(&left_position, buffer);
19434 if cmp.is_ge() {
19435 Ordering::Greater
19436 } else {
19437 Ordering::Less
19438 }
19439 }) {
19440 Ok(i) | Err(i) => i,
19441 };
19442
19443 ranges[start_ix..]
19444 .iter()
19445 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19446 })
19447 }
19448
19449 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19450 self.background_highlights
19451 .get(&HighlightKey::Type(TypeId::of::<T>()))
19452 .map_or(false, |(_, highlights)| !highlights.is_empty())
19453 }
19454
19455 pub fn background_highlights_in_range(
19456 &self,
19457 search_range: Range<Anchor>,
19458 display_snapshot: &DisplaySnapshot,
19459 theme: &Theme,
19460 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19461 let mut results = Vec::new();
19462 for (color_fetcher, ranges) in self.background_highlights.values() {
19463 let color = color_fetcher(theme);
19464 let start_ix = match ranges.binary_search_by(|probe| {
19465 let cmp = probe
19466 .end
19467 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19468 if cmp.is_gt() {
19469 Ordering::Greater
19470 } else {
19471 Ordering::Less
19472 }
19473 }) {
19474 Ok(i) | Err(i) => i,
19475 };
19476 for range in &ranges[start_ix..] {
19477 if range
19478 .start
19479 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19480 .is_ge()
19481 {
19482 break;
19483 }
19484
19485 let start = range.start.to_display_point(display_snapshot);
19486 let end = range.end.to_display_point(display_snapshot);
19487 results.push((start..end, color))
19488 }
19489 }
19490 results
19491 }
19492
19493 pub fn background_highlight_row_ranges<T: 'static>(
19494 &self,
19495 search_range: Range<Anchor>,
19496 display_snapshot: &DisplaySnapshot,
19497 count: usize,
19498 ) -> Vec<RangeInclusive<DisplayPoint>> {
19499 let mut results = Vec::new();
19500 let Some((_, ranges)) = self
19501 .background_highlights
19502 .get(&HighlightKey::Type(TypeId::of::<T>()))
19503 else {
19504 return vec![];
19505 };
19506
19507 let start_ix = match ranges.binary_search_by(|probe| {
19508 let cmp = probe
19509 .end
19510 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19511 if cmp.is_gt() {
19512 Ordering::Greater
19513 } else {
19514 Ordering::Less
19515 }
19516 }) {
19517 Ok(i) | Err(i) => i,
19518 };
19519 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19520 if let (Some(start_display), Some(end_display)) = (start, end) {
19521 results.push(
19522 start_display.to_display_point(display_snapshot)
19523 ..=end_display.to_display_point(display_snapshot),
19524 );
19525 }
19526 };
19527 let mut start_row: Option<Point> = None;
19528 let mut end_row: Option<Point> = None;
19529 if ranges.len() > count {
19530 return Vec::new();
19531 }
19532 for range in &ranges[start_ix..] {
19533 if range
19534 .start
19535 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19536 .is_ge()
19537 {
19538 break;
19539 }
19540 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19541 if let Some(current_row) = &end_row {
19542 if end.row == current_row.row {
19543 continue;
19544 }
19545 }
19546 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19547 if start_row.is_none() {
19548 assert_eq!(end_row, None);
19549 start_row = Some(start);
19550 end_row = Some(end);
19551 continue;
19552 }
19553 if let Some(current_end) = end_row.as_mut() {
19554 if start.row > current_end.row + 1 {
19555 push_region(start_row, end_row);
19556 start_row = Some(start);
19557 end_row = Some(end);
19558 } else {
19559 // Merge two hunks.
19560 *current_end = end;
19561 }
19562 } else {
19563 unreachable!();
19564 }
19565 }
19566 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19567 push_region(start_row, end_row);
19568 results
19569 }
19570
19571 pub fn gutter_highlights_in_range(
19572 &self,
19573 search_range: Range<Anchor>,
19574 display_snapshot: &DisplaySnapshot,
19575 cx: &App,
19576 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19577 let mut results = Vec::new();
19578 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19579 let color = color_fetcher(cx);
19580 let start_ix = match ranges.binary_search_by(|probe| {
19581 let cmp = probe
19582 .end
19583 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19584 if cmp.is_gt() {
19585 Ordering::Greater
19586 } else {
19587 Ordering::Less
19588 }
19589 }) {
19590 Ok(i) | Err(i) => i,
19591 };
19592 for range in &ranges[start_ix..] {
19593 if range
19594 .start
19595 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19596 .is_ge()
19597 {
19598 break;
19599 }
19600
19601 let start = range.start.to_display_point(display_snapshot);
19602 let end = range.end.to_display_point(display_snapshot);
19603 results.push((start..end, color))
19604 }
19605 }
19606 results
19607 }
19608
19609 /// Get the text ranges corresponding to the redaction query
19610 pub fn redacted_ranges(
19611 &self,
19612 search_range: Range<Anchor>,
19613 display_snapshot: &DisplaySnapshot,
19614 cx: &App,
19615 ) -> Vec<Range<DisplayPoint>> {
19616 display_snapshot
19617 .buffer_snapshot
19618 .redacted_ranges(search_range, |file| {
19619 if let Some(file) = file {
19620 file.is_private()
19621 && EditorSettings::get(
19622 Some(SettingsLocation {
19623 worktree_id: file.worktree_id(cx),
19624 path: file.path().as_ref(),
19625 }),
19626 cx,
19627 )
19628 .redact_private_values
19629 } else {
19630 false
19631 }
19632 })
19633 .map(|range| {
19634 range.start.to_display_point(display_snapshot)
19635 ..range.end.to_display_point(display_snapshot)
19636 })
19637 .collect()
19638 }
19639
19640 pub fn highlight_text_key<T: 'static>(
19641 &mut self,
19642 key: usize,
19643 ranges: Vec<Range<Anchor>>,
19644 style: HighlightStyle,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.display_map.update(cx, |map, _| {
19648 map.highlight_text(
19649 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19650 ranges,
19651 style,
19652 );
19653 });
19654 cx.notify();
19655 }
19656
19657 pub fn highlight_text<T: 'static>(
19658 &mut self,
19659 ranges: Vec<Range<Anchor>>,
19660 style: HighlightStyle,
19661 cx: &mut Context<Self>,
19662 ) {
19663 self.display_map.update(cx, |map, _| {
19664 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19665 });
19666 cx.notify();
19667 }
19668
19669 pub(crate) fn highlight_inlays<T: 'static>(
19670 &mut self,
19671 highlights: Vec<InlayHighlight>,
19672 style: HighlightStyle,
19673 cx: &mut Context<Self>,
19674 ) {
19675 self.display_map.update(cx, |map, _| {
19676 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19677 });
19678 cx.notify();
19679 }
19680
19681 pub fn text_highlights<'a, T: 'static>(
19682 &'a self,
19683 cx: &'a App,
19684 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19685 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19686 }
19687
19688 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19689 let cleared = self
19690 .display_map
19691 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19692 if cleared {
19693 cx.notify();
19694 }
19695 }
19696
19697 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19698 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19699 && self.focus_handle.is_focused(window)
19700 }
19701
19702 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19703 self.show_cursor_when_unfocused = is_enabled;
19704 cx.notify();
19705 }
19706
19707 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19708 cx.notify();
19709 }
19710
19711 fn on_debug_session_event(
19712 &mut self,
19713 _session: Entity<Session>,
19714 event: &SessionEvent,
19715 cx: &mut Context<Self>,
19716 ) {
19717 match event {
19718 SessionEvent::InvalidateInlineValue => {
19719 self.refresh_inline_values(cx);
19720 }
19721 _ => {}
19722 }
19723 }
19724
19725 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19726 let Some(project) = self.project.clone() else {
19727 return;
19728 };
19729
19730 if !self.inline_value_cache.enabled {
19731 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19732 self.splice_inlays(&inlays, Vec::new(), cx);
19733 return;
19734 }
19735
19736 let current_execution_position = self
19737 .highlighted_rows
19738 .get(&TypeId::of::<ActiveDebugLine>())
19739 .and_then(|lines| lines.last().map(|line| line.range.end));
19740
19741 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19742 let inline_values = editor
19743 .update(cx, |editor, cx| {
19744 let Some(current_execution_position) = current_execution_position else {
19745 return Some(Task::ready(Ok(Vec::new())));
19746 };
19747
19748 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19749 let snapshot = buffer.snapshot(cx);
19750
19751 let excerpt = snapshot.excerpt_containing(
19752 current_execution_position..current_execution_position,
19753 )?;
19754
19755 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19756 })?;
19757
19758 let range =
19759 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19760
19761 project.inline_values(buffer, range, cx)
19762 })
19763 .ok()
19764 .flatten()?
19765 .await
19766 .context("refreshing debugger inlays")
19767 .log_err()?;
19768
19769 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19770
19771 for (buffer_id, inline_value) in inline_values
19772 .into_iter()
19773 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19774 {
19775 buffer_inline_values
19776 .entry(buffer_id)
19777 .or_default()
19778 .push(inline_value);
19779 }
19780
19781 editor
19782 .update(cx, |editor, cx| {
19783 let snapshot = editor.buffer.read(cx).snapshot(cx);
19784 let mut new_inlays = Vec::default();
19785
19786 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19787 let buffer_id = buffer_snapshot.remote_id();
19788 buffer_inline_values
19789 .get(&buffer_id)
19790 .into_iter()
19791 .flatten()
19792 .for_each(|hint| {
19793 let inlay = Inlay::debugger(
19794 post_inc(&mut editor.next_inlay_id),
19795 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19796 hint.text(),
19797 );
19798 if !inlay.text.chars().contains(&'\n') {
19799 new_inlays.push(inlay);
19800 }
19801 });
19802 }
19803
19804 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19805 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19806
19807 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19808 })
19809 .ok()?;
19810 Some(())
19811 });
19812 }
19813
19814 fn on_buffer_event(
19815 &mut self,
19816 multibuffer: &Entity<MultiBuffer>,
19817 event: &multi_buffer::Event,
19818 window: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) {
19821 match event {
19822 multi_buffer::Event::Edited {
19823 singleton_buffer_edited,
19824 edited_buffer,
19825 } => {
19826 self.scrollbar_marker_state.dirty = true;
19827 self.active_indent_guides_state.dirty = true;
19828 self.refresh_active_diagnostics(cx);
19829 self.refresh_code_actions(window, cx);
19830 self.refresh_selected_text_highlights(true, window, cx);
19831 self.refresh_single_line_folds(window, cx);
19832 refresh_matching_bracket_highlights(self, window, cx);
19833 if self.has_active_inline_completion() {
19834 self.update_visible_inline_completion(window, cx);
19835 }
19836 if let Some(project) = self.project.as_ref() {
19837 if let Some(edited_buffer) = edited_buffer {
19838 project.update(cx, |project, cx| {
19839 self.registered_buffers
19840 .entry(edited_buffer.read(cx).remote_id())
19841 .or_insert_with(|| {
19842 project
19843 .register_buffer_with_language_servers(&edited_buffer, cx)
19844 });
19845 });
19846 }
19847 }
19848 cx.emit(EditorEvent::BufferEdited);
19849 cx.emit(SearchEvent::MatchesInvalidated);
19850
19851 if let Some(buffer) = edited_buffer {
19852 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19853 }
19854
19855 if *singleton_buffer_edited {
19856 if let Some(buffer) = edited_buffer {
19857 if buffer.read(cx).file().is_none() {
19858 cx.emit(EditorEvent::TitleChanged);
19859 }
19860 }
19861 if let Some(project) = &self.project {
19862 #[allow(clippy::mutable_key_type)]
19863 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19864 multibuffer
19865 .all_buffers()
19866 .into_iter()
19867 .filter_map(|buffer| {
19868 buffer.update(cx, |buffer, cx| {
19869 let language = buffer.language()?;
19870 let should_discard = project.update(cx, |project, cx| {
19871 project.is_local()
19872 && !project.has_language_servers_for(buffer, cx)
19873 });
19874 should_discard.not().then_some(language.clone())
19875 })
19876 })
19877 .collect::<HashSet<_>>()
19878 });
19879 if !languages_affected.is_empty() {
19880 self.refresh_inlay_hints(
19881 InlayHintRefreshReason::BufferEdited(languages_affected),
19882 cx,
19883 );
19884 }
19885 }
19886 }
19887
19888 let Some(project) = &self.project else { return };
19889 let (telemetry, is_via_ssh) = {
19890 let project = project.read(cx);
19891 let telemetry = project.client().telemetry().clone();
19892 let is_via_ssh = project.is_via_ssh();
19893 (telemetry, is_via_ssh)
19894 };
19895 refresh_linked_ranges(self, window, cx);
19896 telemetry.log_edit_event("editor", is_via_ssh);
19897 }
19898 multi_buffer::Event::ExcerptsAdded {
19899 buffer,
19900 predecessor,
19901 excerpts,
19902 } => {
19903 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19904 let buffer_id = buffer.read(cx).remote_id();
19905 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19906 if let Some(project) = &self.project {
19907 update_uncommitted_diff_for_buffer(
19908 cx.entity(),
19909 project,
19910 [buffer.clone()],
19911 self.buffer.clone(),
19912 cx,
19913 )
19914 .detach();
19915 }
19916 }
19917 self.update_lsp_data(false, Some(buffer_id), window, cx);
19918 cx.emit(EditorEvent::ExcerptsAdded {
19919 buffer: buffer.clone(),
19920 predecessor: *predecessor,
19921 excerpts: excerpts.clone(),
19922 });
19923 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19924 }
19925 multi_buffer::Event::ExcerptsRemoved {
19926 ids,
19927 removed_buffer_ids,
19928 } => {
19929 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19930 let buffer = self.buffer.read(cx);
19931 self.registered_buffers
19932 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19933 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19934 cx.emit(EditorEvent::ExcerptsRemoved {
19935 ids: ids.clone(),
19936 removed_buffer_ids: removed_buffer_ids.clone(),
19937 });
19938 }
19939 multi_buffer::Event::ExcerptsEdited {
19940 excerpt_ids,
19941 buffer_ids,
19942 } => {
19943 self.display_map.update(cx, |map, cx| {
19944 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19945 });
19946 cx.emit(EditorEvent::ExcerptsEdited {
19947 ids: excerpt_ids.clone(),
19948 });
19949 }
19950 multi_buffer::Event::ExcerptsExpanded { ids } => {
19951 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19952 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19953 }
19954 multi_buffer::Event::Reparsed(buffer_id) => {
19955 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19956 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19957
19958 cx.emit(EditorEvent::Reparsed(*buffer_id));
19959 }
19960 multi_buffer::Event::DiffHunksToggled => {
19961 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19962 }
19963 multi_buffer::Event::LanguageChanged(buffer_id) => {
19964 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19965 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19966 cx.emit(EditorEvent::Reparsed(*buffer_id));
19967 cx.notify();
19968 }
19969 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19970 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19971 multi_buffer::Event::FileHandleChanged
19972 | multi_buffer::Event::Reloaded
19973 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19974 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19975 multi_buffer::Event::DiagnosticsUpdated => {
19976 self.update_diagnostics_state(window, cx);
19977 }
19978 _ => {}
19979 };
19980 }
19981
19982 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19983 if !self.diagnostics_enabled() {
19984 return;
19985 }
19986 self.refresh_active_diagnostics(cx);
19987 self.refresh_inline_diagnostics(true, window, cx);
19988 self.scrollbar_marker_state.dirty = true;
19989 cx.notify();
19990 }
19991
19992 pub fn start_temporary_diff_override(&mut self) {
19993 self.load_diff_task.take();
19994 self.temporary_diff_override = true;
19995 }
19996
19997 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19998 self.temporary_diff_override = false;
19999 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20000 self.buffer.update(cx, |buffer, cx| {
20001 buffer.set_all_diff_hunks_collapsed(cx);
20002 });
20003
20004 if let Some(project) = self.project.clone() {
20005 self.load_diff_task = Some(
20006 update_uncommitted_diff_for_buffer(
20007 cx.entity(),
20008 &project,
20009 self.buffer.read(cx).all_buffers(),
20010 self.buffer.clone(),
20011 cx,
20012 )
20013 .shared(),
20014 );
20015 }
20016 }
20017
20018 fn on_display_map_changed(
20019 &mut self,
20020 _: Entity<DisplayMap>,
20021 _: &mut Window,
20022 cx: &mut Context<Self>,
20023 ) {
20024 cx.notify();
20025 }
20026
20027 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20028 if self.diagnostics_enabled() {
20029 let new_severity = EditorSettings::get_global(cx)
20030 .diagnostics_max_severity
20031 .unwrap_or(DiagnosticSeverity::Hint);
20032 self.set_max_diagnostics_severity(new_severity, cx);
20033 }
20034 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20035 self.update_edit_prediction_settings(cx);
20036 self.refresh_inline_completion(true, false, window, cx);
20037 self.refresh_inline_values(cx);
20038 self.refresh_inlay_hints(
20039 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20040 self.selections.newest_anchor().head(),
20041 &self.buffer.read(cx).snapshot(cx),
20042 cx,
20043 )),
20044 cx,
20045 );
20046
20047 let old_cursor_shape = self.cursor_shape;
20048
20049 {
20050 let editor_settings = EditorSettings::get_global(cx);
20051 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20052 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20053 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20054 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20055 }
20056
20057 if old_cursor_shape != self.cursor_shape {
20058 cx.emit(EditorEvent::CursorShapeChanged);
20059 }
20060
20061 let project_settings = ProjectSettings::get_global(cx);
20062 self.serialize_dirty_buffers =
20063 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20064
20065 if self.mode.is_full() {
20066 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20067 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20068 if self.show_inline_diagnostics != show_inline_diagnostics {
20069 self.show_inline_diagnostics = show_inline_diagnostics;
20070 self.refresh_inline_diagnostics(false, window, cx);
20071 }
20072
20073 if self.git_blame_inline_enabled != inline_blame_enabled {
20074 self.toggle_git_blame_inline_internal(false, window, cx);
20075 }
20076
20077 let minimap_settings = EditorSettings::get_global(cx).minimap;
20078 if self.minimap_visibility != MinimapVisibility::Disabled {
20079 if self.minimap_visibility.settings_visibility()
20080 != minimap_settings.minimap_enabled()
20081 {
20082 self.set_minimap_visibility(
20083 MinimapVisibility::for_mode(self.mode(), cx),
20084 window,
20085 cx,
20086 );
20087 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20088 minimap_entity.update(cx, |minimap_editor, cx| {
20089 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20090 })
20091 }
20092 }
20093 }
20094
20095 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20096 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20097 }) {
20098 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20099 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20100 }
20101 self.refresh_colors(false, None, window, cx);
20102 }
20103
20104 cx.notify();
20105 }
20106
20107 pub fn set_searchable(&mut self, searchable: bool) {
20108 self.searchable = searchable;
20109 }
20110
20111 pub fn searchable(&self) -> bool {
20112 self.searchable
20113 }
20114
20115 fn open_proposed_changes_editor(
20116 &mut self,
20117 _: &OpenProposedChangesEditor,
20118 window: &mut Window,
20119 cx: &mut Context<Self>,
20120 ) {
20121 let Some(workspace) = self.workspace() else {
20122 cx.propagate();
20123 return;
20124 };
20125
20126 let selections = self.selections.all::<usize>(cx);
20127 let multi_buffer = self.buffer.read(cx);
20128 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20129 let mut new_selections_by_buffer = HashMap::default();
20130 for selection in selections {
20131 for (buffer, range, _) in
20132 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20133 {
20134 let mut range = range.to_point(buffer);
20135 range.start.column = 0;
20136 range.end.column = buffer.line_len(range.end.row);
20137 new_selections_by_buffer
20138 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20139 .or_insert(Vec::new())
20140 .push(range)
20141 }
20142 }
20143
20144 let proposed_changes_buffers = new_selections_by_buffer
20145 .into_iter()
20146 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20147 .collect::<Vec<_>>();
20148 let proposed_changes_editor = cx.new(|cx| {
20149 ProposedChangesEditor::new(
20150 "Proposed changes",
20151 proposed_changes_buffers,
20152 self.project.clone(),
20153 window,
20154 cx,
20155 )
20156 });
20157
20158 window.defer(cx, move |window, cx| {
20159 workspace.update(cx, |workspace, cx| {
20160 workspace.active_pane().update(cx, |pane, cx| {
20161 pane.add_item(
20162 Box::new(proposed_changes_editor),
20163 true,
20164 true,
20165 None,
20166 window,
20167 cx,
20168 );
20169 });
20170 });
20171 });
20172 }
20173
20174 pub fn open_excerpts_in_split(
20175 &mut self,
20176 _: &OpenExcerptsSplit,
20177 window: &mut Window,
20178 cx: &mut Context<Self>,
20179 ) {
20180 self.open_excerpts_common(None, true, window, cx)
20181 }
20182
20183 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20184 self.open_excerpts_common(None, false, window, cx)
20185 }
20186
20187 fn open_excerpts_common(
20188 &mut self,
20189 jump_data: Option<JumpData>,
20190 split: bool,
20191 window: &mut Window,
20192 cx: &mut Context<Self>,
20193 ) {
20194 let Some(workspace) = self.workspace() else {
20195 cx.propagate();
20196 return;
20197 };
20198
20199 if self.buffer.read(cx).is_singleton() {
20200 cx.propagate();
20201 return;
20202 }
20203
20204 let mut new_selections_by_buffer = HashMap::default();
20205 match &jump_data {
20206 Some(JumpData::MultiBufferPoint {
20207 excerpt_id,
20208 position,
20209 anchor,
20210 line_offset_from_top,
20211 }) => {
20212 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20213 if let Some(buffer) = multi_buffer_snapshot
20214 .buffer_id_for_excerpt(*excerpt_id)
20215 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20216 {
20217 let buffer_snapshot = buffer.read(cx).snapshot();
20218 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20219 language::ToPoint::to_point(anchor, &buffer_snapshot)
20220 } else {
20221 buffer_snapshot.clip_point(*position, Bias::Left)
20222 };
20223 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20224 new_selections_by_buffer.insert(
20225 buffer,
20226 (
20227 vec![jump_to_offset..jump_to_offset],
20228 Some(*line_offset_from_top),
20229 ),
20230 );
20231 }
20232 }
20233 Some(JumpData::MultiBufferRow {
20234 row,
20235 line_offset_from_top,
20236 }) => {
20237 let point = MultiBufferPoint::new(row.0, 0);
20238 if let Some((buffer, buffer_point, _)) =
20239 self.buffer.read(cx).point_to_buffer_point(point, cx)
20240 {
20241 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20242 new_selections_by_buffer
20243 .entry(buffer)
20244 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20245 .0
20246 .push(buffer_offset..buffer_offset)
20247 }
20248 }
20249 None => {
20250 let selections = self.selections.all::<usize>(cx);
20251 let multi_buffer = self.buffer.read(cx);
20252 for selection in selections {
20253 for (snapshot, range, _, anchor) in multi_buffer
20254 .snapshot(cx)
20255 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20256 {
20257 if let Some(anchor) = anchor {
20258 // selection is in a deleted hunk
20259 let Some(buffer_id) = anchor.buffer_id else {
20260 continue;
20261 };
20262 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20263 continue;
20264 };
20265 let offset = text::ToOffset::to_offset(
20266 &anchor.text_anchor,
20267 &buffer_handle.read(cx).snapshot(),
20268 );
20269 let range = offset..offset;
20270 new_selections_by_buffer
20271 .entry(buffer_handle)
20272 .or_insert((Vec::new(), None))
20273 .0
20274 .push(range)
20275 } else {
20276 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20277 else {
20278 continue;
20279 };
20280 new_selections_by_buffer
20281 .entry(buffer_handle)
20282 .or_insert((Vec::new(), None))
20283 .0
20284 .push(range)
20285 }
20286 }
20287 }
20288 }
20289 }
20290
20291 new_selections_by_buffer
20292 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20293
20294 if new_selections_by_buffer.is_empty() {
20295 return;
20296 }
20297
20298 // We defer the pane interaction because we ourselves are a workspace item
20299 // and activating a new item causes the pane to call a method on us reentrantly,
20300 // which panics if we're on the stack.
20301 window.defer(cx, move |window, cx| {
20302 workspace.update(cx, |workspace, cx| {
20303 let pane = if split {
20304 workspace.adjacent_pane(window, cx)
20305 } else {
20306 workspace.active_pane().clone()
20307 };
20308
20309 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20310 let editor = buffer
20311 .read(cx)
20312 .file()
20313 .is_none()
20314 .then(|| {
20315 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20316 // so `workspace.open_project_item` will never find them, always opening a new editor.
20317 // Instead, we try to activate the existing editor in the pane first.
20318 let (editor, pane_item_index) =
20319 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20320 let editor = item.downcast::<Editor>()?;
20321 let singleton_buffer =
20322 editor.read(cx).buffer().read(cx).as_singleton()?;
20323 if singleton_buffer == buffer {
20324 Some((editor, i))
20325 } else {
20326 None
20327 }
20328 })?;
20329 pane.update(cx, |pane, cx| {
20330 pane.activate_item(pane_item_index, true, true, window, cx)
20331 });
20332 Some(editor)
20333 })
20334 .flatten()
20335 .unwrap_or_else(|| {
20336 workspace.open_project_item::<Self>(
20337 pane.clone(),
20338 buffer,
20339 true,
20340 true,
20341 window,
20342 cx,
20343 )
20344 });
20345
20346 editor.update(cx, |editor, cx| {
20347 let autoscroll = match scroll_offset {
20348 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20349 None => Autoscroll::newest(),
20350 };
20351 let nav_history = editor.nav_history.take();
20352 editor.change_selections(
20353 SelectionEffects::scroll(autoscroll),
20354 window,
20355 cx,
20356 |s| {
20357 s.select_ranges(ranges);
20358 },
20359 );
20360 editor.nav_history = nav_history;
20361 });
20362 }
20363 })
20364 });
20365 }
20366
20367 // For now, don't allow opening excerpts in buffers that aren't backed by
20368 // regular project files.
20369 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20370 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20371 }
20372
20373 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20374 let snapshot = self.buffer.read(cx).read(cx);
20375 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20376 Some(
20377 ranges
20378 .iter()
20379 .map(move |range| {
20380 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20381 })
20382 .collect(),
20383 )
20384 }
20385
20386 fn selection_replacement_ranges(
20387 &self,
20388 range: Range<OffsetUtf16>,
20389 cx: &mut App,
20390 ) -> Vec<Range<OffsetUtf16>> {
20391 let selections = self.selections.all::<OffsetUtf16>(cx);
20392 let newest_selection = selections
20393 .iter()
20394 .max_by_key(|selection| selection.id)
20395 .unwrap();
20396 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20397 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20398 let snapshot = self.buffer.read(cx).read(cx);
20399 selections
20400 .into_iter()
20401 .map(|mut selection| {
20402 selection.start.0 =
20403 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20404 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20405 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20406 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20407 })
20408 .collect()
20409 }
20410
20411 fn report_editor_event(
20412 &self,
20413 event_type: &'static str,
20414 file_extension: Option<String>,
20415 cx: &App,
20416 ) {
20417 if cfg!(any(test, feature = "test-support")) {
20418 return;
20419 }
20420
20421 let Some(project) = &self.project else { return };
20422
20423 // If None, we are in a file without an extension
20424 let file = self
20425 .buffer
20426 .read(cx)
20427 .as_singleton()
20428 .and_then(|b| b.read(cx).file());
20429 let file_extension = file_extension.or(file
20430 .as_ref()
20431 .and_then(|file| Path::new(file.file_name(cx)).extension())
20432 .and_then(|e| e.to_str())
20433 .map(|a| a.to_string()));
20434
20435 let vim_mode = vim_enabled(cx);
20436
20437 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20438 let copilot_enabled = edit_predictions_provider
20439 == language::language_settings::EditPredictionProvider::Copilot;
20440 let copilot_enabled_for_language = self
20441 .buffer
20442 .read(cx)
20443 .language_settings(cx)
20444 .show_edit_predictions;
20445
20446 let project = project.read(cx);
20447 telemetry::event!(
20448 event_type,
20449 file_extension,
20450 vim_mode,
20451 copilot_enabled,
20452 copilot_enabled_for_language,
20453 edit_predictions_provider,
20454 is_via_ssh = project.is_via_ssh(),
20455 );
20456 }
20457
20458 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20459 /// with each line being an array of {text, highlight} objects.
20460 fn copy_highlight_json(
20461 &mut self,
20462 _: &CopyHighlightJson,
20463 window: &mut Window,
20464 cx: &mut Context<Self>,
20465 ) {
20466 #[derive(Serialize)]
20467 struct Chunk<'a> {
20468 text: String,
20469 highlight: Option<&'a str>,
20470 }
20471
20472 let snapshot = self.buffer.read(cx).snapshot(cx);
20473 let range = self
20474 .selected_text_range(false, window, cx)
20475 .and_then(|selection| {
20476 if selection.range.is_empty() {
20477 None
20478 } else {
20479 Some(selection.range)
20480 }
20481 })
20482 .unwrap_or_else(|| 0..snapshot.len());
20483
20484 let chunks = snapshot.chunks(range, true);
20485 let mut lines = Vec::new();
20486 let mut line: VecDeque<Chunk> = VecDeque::new();
20487
20488 let Some(style) = self.style.as_ref() else {
20489 return;
20490 };
20491
20492 for chunk in chunks {
20493 let highlight = chunk
20494 .syntax_highlight_id
20495 .and_then(|id| id.name(&style.syntax));
20496 let mut chunk_lines = chunk.text.split('\n').peekable();
20497 while let Some(text) = chunk_lines.next() {
20498 let mut merged_with_last_token = false;
20499 if let Some(last_token) = line.back_mut() {
20500 if last_token.highlight == highlight {
20501 last_token.text.push_str(text);
20502 merged_with_last_token = true;
20503 }
20504 }
20505
20506 if !merged_with_last_token {
20507 line.push_back(Chunk {
20508 text: text.into(),
20509 highlight,
20510 });
20511 }
20512
20513 if chunk_lines.peek().is_some() {
20514 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20515 line.pop_front();
20516 }
20517 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20518 line.pop_back();
20519 }
20520
20521 lines.push(mem::take(&mut line));
20522 }
20523 }
20524 }
20525
20526 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20527 return;
20528 };
20529 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20530 }
20531
20532 pub fn open_context_menu(
20533 &mut self,
20534 _: &OpenContextMenu,
20535 window: &mut Window,
20536 cx: &mut Context<Self>,
20537 ) {
20538 self.request_autoscroll(Autoscroll::newest(), cx);
20539 let position = self.selections.newest_display(cx).start;
20540 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20541 }
20542
20543 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20544 &self.inlay_hint_cache
20545 }
20546
20547 pub fn replay_insert_event(
20548 &mut self,
20549 text: &str,
20550 relative_utf16_range: Option<Range<isize>>,
20551 window: &mut Window,
20552 cx: &mut Context<Self>,
20553 ) {
20554 if !self.input_enabled {
20555 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20556 return;
20557 }
20558 if let Some(relative_utf16_range) = relative_utf16_range {
20559 let selections = self.selections.all::<OffsetUtf16>(cx);
20560 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20561 let new_ranges = selections.into_iter().map(|range| {
20562 let start = OffsetUtf16(
20563 range
20564 .head()
20565 .0
20566 .saturating_add_signed(relative_utf16_range.start),
20567 );
20568 let end = OffsetUtf16(
20569 range
20570 .head()
20571 .0
20572 .saturating_add_signed(relative_utf16_range.end),
20573 );
20574 start..end
20575 });
20576 s.select_ranges(new_ranges);
20577 });
20578 }
20579
20580 self.handle_input(text, window, cx);
20581 }
20582
20583 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20584 let Some(provider) = self.semantics_provider.as_ref() else {
20585 return false;
20586 };
20587
20588 let mut supports = false;
20589 self.buffer().update(cx, |this, cx| {
20590 this.for_each_buffer(|buffer| {
20591 supports |= provider.supports_inlay_hints(buffer, cx);
20592 });
20593 });
20594
20595 supports
20596 }
20597
20598 pub fn is_focused(&self, window: &Window) -> bool {
20599 self.focus_handle.is_focused(window)
20600 }
20601
20602 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20603 cx.emit(EditorEvent::Focused);
20604
20605 if let Some(descendant) = self
20606 .last_focused_descendant
20607 .take()
20608 .and_then(|descendant| descendant.upgrade())
20609 {
20610 window.focus(&descendant);
20611 } else {
20612 if let Some(blame) = self.blame.as_ref() {
20613 blame.update(cx, GitBlame::focus)
20614 }
20615
20616 self.blink_manager.update(cx, BlinkManager::enable);
20617 self.show_cursor_names(window, cx);
20618 self.buffer.update(cx, |buffer, cx| {
20619 buffer.finalize_last_transaction(cx);
20620 if self.leader_id.is_none() {
20621 buffer.set_active_selections(
20622 &self.selections.disjoint_anchors(),
20623 self.selections.line_mode,
20624 self.cursor_shape,
20625 cx,
20626 );
20627 }
20628 });
20629 }
20630 }
20631
20632 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20633 cx.emit(EditorEvent::FocusedIn)
20634 }
20635
20636 fn handle_focus_out(
20637 &mut self,
20638 event: FocusOutEvent,
20639 _window: &mut Window,
20640 cx: &mut Context<Self>,
20641 ) {
20642 if event.blurred != self.focus_handle {
20643 self.last_focused_descendant = Some(event.blurred);
20644 }
20645 self.selection_drag_state = SelectionDragState::None;
20646 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20647 }
20648
20649 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20650 self.blink_manager.update(cx, BlinkManager::disable);
20651 self.buffer
20652 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20653
20654 if let Some(blame) = self.blame.as_ref() {
20655 blame.update(cx, GitBlame::blur)
20656 }
20657 if !self.hover_state.focused(window, cx) {
20658 hide_hover(self, cx);
20659 }
20660 if !self
20661 .context_menu
20662 .borrow()
20663 .as_ref()
20664 .is_some_and(|context_menu| context_menu.focused(window, cx))
20665 {
20666 self.hide_context_menu(window, cx);
20667 }
20668 self.discard_inline_completion(false, cx);
20669 cx.emit(EditorEvent::Blurred);
20670 cx.notify();
20671 }
20672
20673 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20674 let mut pending: String = window
20675 .pending_input_keystrokes()
20676 .into_iter()
20677 .flatten()
20678 .filter_map(|keystroke| {
20679 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20680 keystroke.key_char.clone()
20681 } else {
20682 None
20683 }
20684 })
20685 .collect();
20686
20687 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20688 pending = "".to_string();
20689 }
20690
20691 let existing_pending = self
20692 .text_highlights::<PendingInput>(cx)
20693 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20694 if existing_pending.is_none() && pending.is_empty() {
20695 return;
20696 }
20697 let transaction =
20698 self.transact(window, cx, |this, window, cx| {
20699 let selections = this.selections.all::<usize>(cx);
20700 let edits = selections
20701 .iter()
20702 .map(|selection| (selection.end..selection.end, pending.clone()));
20703 this.edit(edits, cx);
20704 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20705 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20706 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20707 }));
20708 });
20709 if let Some(existing_ranges) = existing_pending {
20710 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20711 this.edit(edits, cx);
20712 }
20713 });
20714
20715 let snapshot = self.snapshot(window, cx);
20716 let ranges = self
20717 .selections
20718 .all::<usize>(cx)
20719 .into_iter()
20720 .map(|selection| {
20721 snapshot.buffer_snapshot.anchor_after(selection.end)
20722 ..snapshot
20723 .buffer_snapshot
20724 .anchor_before(selection.end + pending.len())
20725 })
20726 .collect();
20727
20728 if pending.is_empty() {
20729 self.clear_highlights::<PendingInput>(cx);
20730 } else {
20731 self.highlight_text::<PendingInput>(
20732 ranges,
20733 HighlightStyle {
20734 underline: Some(UnderlineStyle {
20735 thickness: px(1.),
20736 color: None,
20737 wavy: false,
20738 }),
20739 ..Default::default()
20740 },
20741 cx,
20742 );
20743 }
20744
20745 self.ime_transaction = self.ime_transaction.or(transaction);
20746 if let Some(transaction) = self.ime_transaction {
20747 self.buffer.update(cx, |buffer, cx| {
20748 buffer.group_until_transaction(transaction, cx);
20749 });
20750 }
20751
20752 if self.text_highlights::<PendingInput>(cx).is_none() {
20753 self.ime_transaction.take();
20754 }
20755 }
20756
20757 pub fn register_action_renderer(
20758 &mut self,
20759 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20760 ) -> Subscription {
20761 let id = self.next_editor_action_id.post_inc();
20762 self.editor_actions
20763 .borrow_mut()
20764 .insert(id, Box::new(listener));
20765
20766 let editor_actions = self.editor_actions.clone();
20767 Subscription::new(move || {
20768 editor_actions.borrow_mut().remove(&id);
20769 })
20770 }
20771
20772 pub fn register_action<A: Action>(
20773 &mut self,
20774 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20775 ) -> Subscription {
20776 let id = self.next_editor_action_id.post_inc();
20777 let listener = Arc::new(listener);
20778 self.editor_actions.borrow_mut().insert(
20779 id,
20780 Box::new(move |_, window, _| {
20781 let listener = listener.clone();
20782 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20783 let action = action.downcast_ref().unwrap();
20784 if phase == DispatchPhase::Bubble {
20785 listener(action, window, cx)
20786 }
20787 })
20788 }),
20789 );
20790
20791 let editor_actions = self.editor_actions.clone();
20792 Subscription::new(move || {
20793 editor_actions.borrow_mut().remove(&id);
20794 })
20795 }
20796
20797 pub fn file_header_size(&self) -> u32 {
20798 FILE_HEADER_HEIGHT
20799 }
20800
20801 pub fn restore(
20802 &mut self,
20803 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20804 window: &mut Window,
20805 cx: &mut Context<Self>,
20806 ) {
20807 let workspace = self.workspace();
20808 let project = self.project.as_ref();
20809 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20810 let mut tasks = Vec::new();
20811 for (buffer_id, changes) in revert_changes {
20812 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20813 buffer.update(cx, |buffer, cx| {
20814 buffer.edit(
20815 changes
20816 .into_iter()
20817 .map(|(range, text)| (range, text.to_string())),
20818 None,
20819 cx,
20820 );
20821 });
20822
20823 if let Some(project) =
20824 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20825 {
20826 project.update(cx, |project, cx| {
20827 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20828 })
20829 }
20830 }
20831 }
20832 tasks
20833 });
20834 cx.spawn_in(window, async move |_, cx| {
20835 for (buffer, task) in save_tasks {
20836 let result = task.await;
20837 if result.is_err() {
20838 let Some(path) = buffer
20839 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20840 .ok()
20841 else {
20842 continue;
20843 };
20844 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20845 let Some(task) = cx
20846 .update_window_entity(&workspace, |workspace, window, cx| {
20847 workspace
20848 .open_path_preview(path, None, false, false, false, window, cx)
20849 })
20850 .ok()
20851 else {
20852 continue;
20853 };
20854 task.await.log_err();
20855 }
20856 }
20857 }
20858 })
20859 .detach();
20860 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20861 selections.refresh()
20862 });
20863 }
20864
20865 pub fn to_pixel_point(
20866 &self,
20867 source: multi_buffer::Anchor,
20868 editor_snapshot: &EditorSnapshot,
20869 window: &mut Window,
20870 ) -> Option<gpui::Point<Pixels>> {
20871 let source_point = source.to_display_point(editor_snapshot);
20872 self.display_to_pixel_point(source_point, editor_snapshot, window)
20873 }
20874
20875 pub fn display_to_pixel_point(
20876 &self,
20877 source: DisplayPoint,
20878 editor_snapshot: &EditorSnapshot,
20879 window: &mut Window,
20880 ) -> Option<gpui::Point<Pixels>> {
20881 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20882 let text_layout_details = self.text_layout_details(window);
20883 let scroll_top = text_layout_details
20884 .scroll_anchor
20885 .scroll_position(editor_snapshot)
20886 .y;
20887
20888 if source.row().as_f32() < scroll_top.floor() {
20889 return None;
20890 }
20891 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20892 let source_y = line_height * (source.row().as_f32() - scroll_top);
20893 Some(gpui::Point::new(source_x, source_y))
20894 }
20895
20896 pub fn has_visible_completions_menu(&self) -> bool {
20897 !self.edit_prediction_preview_is_active()
20898 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20899 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20900 })
20901 }
20902
20903 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20904 if self.mode.is_minimap() {
20905 return;
20906 }
20907 self.addons
20908 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20909 }
20910
20911 pub fn unregister_addon<T: Addon>(&mut self) {
20912 self.addons.remove(&std::any::TypeId::of::<T>());
20913 }
20914
20915 pub fn addon<T: Addon>(&self) -> Option<&T> {
20916 let type_id = std::any::TypeId::of::<T>();
20917 self.addons
20918 .get(&type_id)
20919 .and_then(|item| item.to_any().downcast_ref::<T>())
20920 }
20921
20922 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20923 let type_id = std::any::TypeId::of::<T>();
20924 self.addons
20925 .get_mut(&type_id)
20926 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20927 }
20928
20929 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20930 let text_layout_details = self.text_layout_details(window);
20931 let style = &text_layout_details.editor_style;
20932 let font_id = window.text_system().resolve_font(&style.text.font());
20933 let font_size = style.text.font_size.to_pixels(window.rem_size());
20934 let line_height = style.text.line_height_in_pixels(window.rem_size());
20935 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20936 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20937
20938 CharacterDimensions {
20939 em_width,
20940 em_advance,
20941 line_height,
20942 }
20943 }
20944
20945 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20946 self.load_diff_task.clone()
20947 }
20948
20949 fn read_metadata_from_db(
20950 &mut self,
20951 item_id: u64,
20952 workspace_id: WorkspaceId,
20953 window: &mut Window,
20954 cx: &mut Context<Editor>,
20955 ) {
20956 if self.is_singleton(cx)
20957 && !self.mode.is_minimap()
20958 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20959 {
20960 let buffer_snapshot = OnceCell::new();
20961
20962 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20963 if !folds.is_empty() {
20964 let snapshot =
20965 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20966 self.fold_ranges(
20967 folds
20968 .into_iter()
20969 .map(|(start, end)| {
20970 snapshot.clip_offset(start, Bias::Left)
20971 ..snapshot.clip_offset(end, Bias::Right)
20972 })
20973 .collect(),
20974 false,
20975 window,
20976 cx,
20977 );
20978 }
20979 }
20980
20981 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20982 if !selections.is_empty() {
20983 let snapshot =
20984 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20985 // skip adding the initial selection to selection history
20986 self.selection_history.mode = SelectionHistoryMode::Skipping;
20987 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20988 s.select_ranges(selections.into_iter().map(|(start, end)| {
20989 snapshot.clip_offset(start, Bias::Left)
20990 ..snapshot.clip_offset(end, Bias::Right)
20991 }));
20992 });
20993 self.selection_history.mode = SelectionHistoryMode::Normal;
20994 }
20995 };
20996 }
20997
20998 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20999 }
21000
21001 fn update_lsp_data(
21002 &mut self,
21003 ignore_cache: bool,
21004 for_buffer: Option<BufferId>,
21005 window: &mut Window,
21006 cx: &mut Context<'_, Self>,
21007 ) {
21008 self.pull_diagnostics(for_buffer, window, cx);
21009 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21010 }
21011}
21012
21013fn vim_enabled(cx: &App) -> bool {
21014 cx.global::<SettingsStore>()
21015 .raw_user_settings()
21016 .get("vim_mode")
21017 == Some(&serde_json::Value::Bool(true))
21018}
21019
21020fn process_completion_for_edit(
21021 completion: &Completion,
21022 intent: CompletionIntent,
21023 buffer: &Entity<Buffer>,
21024 cursor_position: &text::Anchor,
21025 cx: &mut Context<Editor>,
21026) -> CompletionEdit {
21027 let buffer = buffer.read(cx);
21028 let buffer_snapshot = buffer.snapshot();
21029 let (snippet, new_text) = if completion.is_snippet() {
21030 // Workaround for typescript language server issues so that methods don't expand within
21031 // strings and functions with type expressions. The previous point is used because the query
21032 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21033 let mut snippet_source = completion.new_text.clone();
21034 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21035 previous_point.column = previous_point.column.saturating_sub(1);
21036 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21037 if scope.prefers_label_for_snippet_in_completion() {
21038 if let Some(label) = completion.label() {
21039 if matches!(
21040 completion.kind(),
21041 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21042 ) {
21043 snippet_source = label;
21044 }
21045 }
21046 }
21047 }
21048 match Snippet::parse(&snippet_source).log_err() {
21049 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21050 None => (None, completion.new_text.clone()),
21051 }
21052 } else {
21053 (None, completion.new_text.clone())
21054 };
21055
21056 let mut range_to_replace = {
21057 let replace_range = &completion.replace_range;
21058 if let CompletionSource::Lsp {
21059 insert_range: Some(insert_range),
21060 ..
21061 } = &completion.source
21062 {
21063 debug_assert_eq!(
21064 insert_range.start, replace_range.start,
21065 "insert_range and replace_range should start at the same position"
21066 );
21067 debug_assert!(
21068 insert_range
21069 .start
21070 .cmp(&cursor_position, &buffer_snapshot)
21071 .is_le(),
21072 "insert_range should start before or at cursor position"
21073 );
21074 debug_assert!(
21075 replace_range
21076 .start
21077 .cmp(&cursor_position, &buffer_snapshot)
21078 .is_le(),
21079 "replace_range should start before or at cursor position"
21080 );
21081 debug_assert!(
21082 insert_range
21083 .end
21084 .cmp(&cursor_position, &buffer_snapshot)
21085 .is_le(),
21086 "insert_range should end before or at cursor position"
21087 );
21088
21089 let should_replace = match intent {
21090 CompletionIntent::CompleteWithInsert => false,
21091 CompletionIntent::CompleteWithReplace => true,
21092 CompletionIntent::Complete | CompletionIntent::Compose => {
21093 let insert_mode =
21094 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21095 .completions
21096 .lsp_insert_mode;
21097 match insert_mode {
21098 LspInsertMode::Insert => false,
21099 LspInsertMode::Replace => true,
21100 LspInsertMode::ReplaceSubsequence => {
21101 let mut text_to_replace = buffer.chars_for_range(
21102 buffer.anchor_before(replace_range.start)
21103 ..buffer.anchor_after(replace_range.end),
21104 );
21105 let mut current_needle = text_to_replace.next();
21106 for haystack_ch in completion.label.text.chars() {
21107 if let Some(needle_ch) = current_needle {
21108 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21109 current_needle = text_to_replace.next();
21110 }
21111 }
21112 }
21113 current_needle.is_none()
21114 }
21115 LspInsertMode::ReplaceSuffix => {
21116 if replace_range
21117 .end
21118 .cmp(&cursor_position, &buffer_snapshot)
21119 .is_gt()
21120 {
21121 let range_after_cursor = *cursor_position..replace_range.end;
21122 let text_after_cursor = buffer
21123 .text_for_range(
21124 buffer.anchor_before(range_after_cursor.start)
21125 ..buffer.anchor_after(range_after_cursor.end),
21126 )
21127 .collect::<String>()
21128 .to_ascii_lowercase();
21129 completion
21130 .label
21131 .text
21132 .to_ascii_lowercase()
21133 .ends_with(&text_after_cursor)
21134 } else {
21135 true
21136 }
21137 }
21138 }
21139 }
21140 };
21141
21142 if should_replace {
21143 replace_range.clone()
21144 } else {
21145 insert_range.clone()
21146 }
21147 } else {
21148 replace_range.clone()
21149 }
21150 };
21151
21152 if range_to_replace
21153 .end
21154 .cmp(&cursor_position, &buffer_snapshot)
21155 .is_lt()
21156 {
21157 range_to_replace.end = *cursor_position;
21158 }
21159
21160 CompletionEdit {
21161 new_text,
21162 replace_range: range_to_replace.to_offset(&buffer),
21163 snippet,
21164 }
21165}
21166
21167struct CompletionEdit {
21168 new_text: String,
21169 replace_range: Range<usize>,
21170 snippet: Option<Snippet>,
21171}
21172
21173fn insert_extra_newline_brackets(
21174 buffer: &MultiBufferSnapshot,
21175 range: Range<usize>,
21176 language: &language::LanguageScope,
21177) -> bool {
21178 let leading_whitespace_len = buffer
21179 .reversed_chars_at(range.start)
21180 .take_while(|c| c.is_whitespace() && *c != '\n')
21181 .map(|c| c.len_utf8())
21182 .sum::<usize>();
21183 let trailing_whitespace_len = buffer
21184 .chars_at(range.end)
21185 .take_while(|c| c.is_whitespace() && *c != '\n')
21186 .map(|c| c.len_utf8())
21187 .sum::<usize>();
21188 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21189
21190 language.brackets().any(|(pair, enabled)| {
21191 let pair_start = pair.start.trim_end();
21192 let pair_end = pair.end.trim_start();
21193
21194 enabled
21195 && pair.newline
21196 && buffer.contains_str_at(range.end, pair_end)
21197 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21198 })
21199}
21200
21201fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21202 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21203 [(buffer, range, _)] => (*buffer, range.clone()),
21204 _ => return false,
21205 };
21206 let pair = {
21207 let mut result: Option<BracketMatch> = None;
21208
21209 for pair in buffer
21210 .all_bracket_ranges(range.clone())
21211 .filter(move |pair| {
21212 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21213 })
21214 {
21215 let len = pair.close_range.end - pair.open_range.start;
21216
21217 if let Some(existing) = &result {
21218 let existing_len = existing.close_range.end - existing.open_range.start;
21219 if len > existing_len {
21220 continue;
21221 }
21222 }
21223
21224 result = Some(pair);
21225 }
21226
21227 result
21228 };
21229 let Some(pair) = pair else {
21230 return false;
21231 };
21232 pair.newline_only
21233 && buffer
21234 .chars_for_range(pair.open_range.end..range.start)
21235 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21236 .all(|c| c.is_whitespace() && c != '\n')
21237}
21238
21239fn update_uncommitted_diff_for_buffer(
21240 editor: Entity<Editor>,
21241 project: &Entity<Project>,
21242 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21243 buffer: Entity<MultiBuffer>,
21244 cx: &mut App,
21245) -> Task<()> {
21246 let mut tasks = Vec::new();
21247 project.update(cx, |project, cx| {
21248 for buffer in buffers {
21249 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21250 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21251 }
21252 }
21253 });
21254 cx.spawn(async move |cx| {
21255 let diffs = future::join_all(tasks).await;
21256 if editor
21257 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21258 .unwrap_or(false)
21259 {
21260 return;
21261 }
21262
21263 buffer
21264 .update(cx, |buffer, cx| {
21265 for diff in diffs.into_iter().flatten() {
21266 buffer.add_diff(diff, cx);
21267 }
21268 })
21269 .ok();
21270 })
21271}
21272
21273fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21274 let tab_size = tab_size.get() as usize;
21275 let mut width = offset;
21276
21277 for ch in text.chars() {
21278 width += if ch == '\t' {
21279 tab_size - (width % tab_size)
21280 } else {
21281 1
21282 };
21283 }
21284
21285 width - offset
21286}
21287
21288#[cfg(test)]
21289mod tests {
21290 use super::*;
21291
21292 #[test]
21293 fn test_string_size_with_expanded_tabs() {
21294 let nz = |val| NonZeroU32::new(val).unwrap();
21295 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21296 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21297 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21298 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21299 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21300 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21301 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21302 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21303 }
21304}
21305
21306/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21307struct WordBreakingTokenizer<'a> {
21308 input: &'a str,
21309}
21310
21311impl<'a> WordBreakingTokenizer<'a> {
21312 fn new(input: &'a str) -> Self {
21313 Self { input }
21314 }
21315}
21316
21317fn is_char_ideographic(ch: char) -> bool {
21318 use unicode_script::Script::*;
21319 use unicode_script::UnicodeScript;
21320 matches!(ch.script(), Han | Tangut | Yi)
21321}
21322
21323fn is_grapheme_ideographic(text: &str) -> bool {
21324 text.chars().any(is_char_ideographic)
21325}
21326
21327fn is_grapheme_whitespace(text: &str) -> bool {
21328 text.chars().any(|x| x.is_whitespace())
21329}
21330
21331fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21332 text.chars().next().map_or(false, |ch| {
21333 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21334 })
21335}
21336
21337#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21338enum WordBreakToken<'a> {
21339 Word { token: &'a str, grapheme_len: usize },
21340 InlineWhitespace { token: &'a str, grapheme_len: usize },
21341 Newline,
21342}
21343
21344impl<'a> Iterator for WordBreakingTokenizer<'a> {
21345 /// Yields a span, the count of graphemes in the token, and whether it was
21346 /// whitespace. Note that it also breaks at word boundaries.
21347 type Item = WordBreakToken<'a>;
21348
21349 fn next(&mut self) -> Option<Self::Item> {
21350 use unicode_segmentation::UnicodeSegmentation;
21351 if self.input.is_empty() {
21352 return None;
21353 }
21354
21355 let mut iter = self.input.graphemes(true).peekable();
21356 let mut offset = 0;
21357 let mut grapheme_len = 0;
21358 if let Some(first_grapheme) = iter.next() {
21359 let is_newline = first_grapheme == "\n";
21360 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21361 offset += first_grapheme.len();
21362 grapheme_len += 1;
21363 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21364 if let Some(grapheme) = iter.peek().copied() {
21365 if should_stay_with_preceding_ideograph(grapheme) {
21366 offset += grapheme.len();
21367 grapheme_len += 1;
21368 }
21369 }
21370 } else {
21371 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21372 let mut next_word_bound = words.peek().copied();
21373 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21374 next_word_bound = words.next();
21375 }
21376 while let Some(grapheme) = iter.peek().copied() {
21377 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21378 break;
21379 };
21380 if is_grapheme_whitespace(grapheme) != is_whitespace
21381 || (grapheme == "\n") != is_newline
21382 {
21383 break;
21384 };
21385 offset += grapheme.len();
21386 grapheme_len += 1;
21387 iter.next();
21388 }
21389 }
21390 let token = &self.input[..offset];
21391 self.input = &self.input[offset..];
21392 if token == "\n" {
21393 Some(WordBreakToken::Newline)
21394 } else if is_whitespace {
21395 Some(WordBreakToken::InlineWhitespace {
21396 token,
21397 grapheme_len,
21398 })
21399 } else {
21400 Some(WordBreakToken::Word {
21401 token,
21402 grapheme_len,
21403 })
21404 }
21405 } else {
21406 None
21407 }
21408 }
21409}
21410
21411#[test]
21412fn test_word_breaking_tokenizer() {
21413 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21414 ("", &[]),
21415 (" ", &[whitespace(" ", 2)]),
21416 ("Ʒ", &[word("Ʒ", 1)]),
21417 ("Ǽ", &[word("Ǽ", 1)]),
21418 ("⋑", &[word("⋑", 1)]),
21419 ("⋑⋑", &[word("⋑⋑", 2)]),
21420 (
21421 "原理,进而",
21422 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21423 ),
21424 (
21425 "hello world",
21426 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21427 ),
21428 (
21429 "hello, world",
21430 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21431 ),
21432 (
21433 " hello world",
21434 &[
21435 whitespace(" ", 2),
21436 word("hello", 5),
21437 whitespace(" ", 1),
21438 word("world", 5),
21439 ],
21440 ),
21441 (
21442 "这是什么 \n 钢笔",
21443 &[
21444 word("这", 1),
21445 word("是", 1),
21446 word("什", 1),
21447 word("么", 1),
21448 whitespace(" ", 1),
21449 newline(),
21450 whitespace(" ", 1),
21451 word("钢", 1),
21452 word("笔", 1),
21453 ],
21454 ),
21455 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21456 ];
21457
21458 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21459 WordBreakToken::Word {
21460 token,
21461 grapheme_len,
21462 }
21463 }
21464
21465 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21466 WordBreakToken::InlineWhitespace {
21467 token,
21468 grapheme_len,
21469 }
21470 }
21471
21472 fn newline() -> WordBreakToken<'static> {
21473 WordBreakToken::Newline
21474 }
21475
21476 for (input, result) in tests {
21477 assert_eq!(
21478 WordBreakingTokenizer::new(input)
21479 .collect::<Vec<_>>()
21480 .as_slice(),
21481 *result,
21482 );
21483 }
21484}
21485
21486fn wrap_with_prefix(
21487 first_line_prefix: String,
21488 subsequent_lines_prefix: String,
21489 unwrapped_text: String,
21490 wrap_column: usize,
21491 tab_size: NonZeroU32,
21492 preserve_existing_whitespace: bool,
21493) -> String {
21494 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21495 let subsequent_lines_prefix_len =
21496 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21497 let mut wrapped_text = String::new();
21498 let mut current_line = first_line_prefix.clone();
21499 let mut is_first_line = true;
21500
21501 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21502 let mut current_line_len = first_line_prefix_len;
21503 let mut in_whitespace = false;
21504 for token in tokenizer {
21505 let have_preceding_whitespace = in_whitespace;
21506 match token {
21507 WordBreakToken::Word {
21508 token,
21509 grapheme_len,
21510 } => {
21511 in_whitespace = false;
21512 let current_prefix_len = if is_first_line {
21513 first_line_prefix_len
21514 } else {
21515 subsequent_lines_prefix_len
21516 };
21517 if current_line_len + grapheme_len > wrap_column
21518 && current_line_len != current_prefix_len
21519 {
21520 wrapped_text.push_str(current_line.trim_end());
21521 wrapped_text.push('\n');
21522 is_first_line = false;
21523 current_line = subsequent_lines_prefix.clone();
21524 current_line_len = subsequent_lines_prefix_len;
21525 }
21526 current_line.push_str(token);
21527 current_line_len += grapheme_len;
21528 }
21529 WordBreakToken::InlineWhitespace {
21530 mut token,
21531 mut grapheme_len,
21532 } => {
21533 in_whitespace = true;
21534 if have_preceding_whitespace && !preserve_existing_whitespace {
21535 continue;
21536 }
21537 if !preserve_existing_whitespace {
21538 token = " ";
21539 grapheme_len = 1;
21540 }
21541 let current_prefix_len = if is_first_line {
21542 first_line_prefix_len
21543 } else {
21544 subsequent_lines_prefix_len
21545 };
21546 if current_line_len + grapheme_len > wrap_column {
21547 wrapped_text.push_str(current_line.trim_end());
21548 wrapped_text.push('\n');
21549 is_first_line = false;
21550 current_line = subsequent_lines_prefix.clone();
21551 current_line_len = subsequent_lines_prefix_len;
21552 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21553 current_line.push_str(token);
21554 current_line_len += grapheme_len;
21555 }
21556 }
21557 WordBreakToken::Newline => {
21558 in_whitespace = true;
21559 let current_prefix_len = if is_first_line {
21560 first_line_prefix_len
21561 } else {
21562 subsequent_lines_prefix_len
21563 };
21564 if preserve_existing_whitespace {
21565 wrapped_text.push_str(current_line.trim_end());
21566 wrapped_text.push('\n');
21567 is_first_line = false;
21568 current_line = subsequent_lines_prefix.clone();
21569 current_line_len = subsequent_lines_prefix_len;
21570 } else if have_preceding_whitespace {
21571 continue;
21572 } else if current_line_len + 1 > wrap_column
21573 && current_line_len != current_prefix_len
21574 {
21575 wrapped_text.push_str(current_line.trim_end());
21576 wrapped_text.push('\n');
21577 is_first_line = false;
21578 current_line = subsequent_lines_prefix.clone();
21579 current_line_len = subsequent_lines_prefix_len;
21580 } else if current_line_len != current_prefix_len {
21581 current_line.push(' ');
21582 current_line_len += 1;
21583 }
21584 }
21585 }
21586 }
21587
21588 if !current_line.is_empty() {
21589 wrapped_text.push_str(¤t_line);
21590 }
21591 wrapped_text
21592}
21593
21594#[test]
21595fn test_wrap_with_prefix() {
21596 assert_eq!(
21597 wrap_with_prefix(
21598 "# ".to_string(),
21599 "# ".to_string(),
21600 "abcdefg".to_string(),
21601 4,
21602 NonZeroU32::new(4).unwrap(),
21603 false,
21604 ),
21605 "# abcdefg"
21606 );
21607 assert_eq!(
21608 wrap_with_prefix(
21609 "".to_string(),
21610 "".to_string(),
21611 "\thello world".to_string(),
21612 8,
21613 NonZeroU32::new(4).unwrap(),
21614 false,
21615 ),
21616 "hello\nworld"
21617 );
21618 assert_eq!(
21619 wrap_with_prefix(
21620 "// ".to_string(),
21621 "// ".to_string(),
21622 "xx \nyy zz aa bb cc".to_string(),
21623 12,
21624 NonZeroU32::new(4).unwrap(),
21625 false,
21626 ),
21627 "// xx yy zz\n// aa bb cc"
21628 );
21629 assert_eq!(
21630 wrap_with_prefix(
21631 String::new(),
21632 String::new(),
21633 "这是什么 \n 钢笔".to_string(),
21634 3,
21635 NonZeroU32::new(4).unwrap(),
21636 false,
21637 ),
21638 "这是什\n么 钢\n笔"
21639 );
21640}
21641
21642pub trait CollaborationHub {
21643 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21644 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21645 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21646}
21647
21648impl CollaborationHub for Entity<Project> {
21649 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21650 self.read(cx).collaborators()
21651 }
21652
21653 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21654 self.read(cx).user_store().read(cx).participant_indices()
21655 }
21656
21657 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21658 let this = self.read(cx);
21659 let user_ids = this.collaborators().values().map(|c| c.user_id);
21660 this.user_store().read(cx).participant_names(user_ids, cx)
21661 }
21662}
21663
21664pub trait SemanticsProvider {
21665 fn hover(
21666 &self,
21667 buffer: &Entity<Buffer>,
21668 position: text::Anchor,
21669 cx: &mut App,
21670 ) -> Option<Task<Vec<project::Hover>>>;
21671
21672 fn inline_values(
21673 &self,
21674 buffer_handle: Entity<Buffer>,
21675 range: Range<text::Anchor>,
21676 cx: &mut App,
21677 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21678
21679 fn inlay_hints(
21680 &self,
21681 buffer_handle: Entity<Buffer>,
21682 range: Range<text::Anchor>,
21683 cx: &mut App,
21684 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21685
21686 fn resolve_inlay_hint(
21687 &self,
21688 hint: InlayHint,
21689 buffer_handle: Entity<Buffer>,
21690 server_id: LanguageServerId,
21691 cx: &mut App,
21692 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21693
21694 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21695
21696 fn document_highlights(
21697 &self,
21698 buffer: &Entity<Buffer>,
21699 position: text::Anchor,
21700 cx: &mut App,
21701 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21702
21703 fn definitions(
21704 &self,
21705 buffer: &Entity<Buffer>,
21706 position: text::Anchor,
21707 kind: GotoDefinitionKind,
21708 cx: &mut App,
21709 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21710
21711 fn range_for_rename(
21712 &self,
21713 buffer: &Entity<Buffer>,
21714 position: text::Anchor,
21715 cx: &mut App,
21716 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21717
21718 fn perform_rename(
21719 &self,
21720 buffer: &Entity<Buffer>,
21721 position: text::Anchor,
21722 new_name: String,
21723 cx: &mut App,
21724 ) -> Option<Task<Result<ProjectTransaction>>>;
21725}
21726
21727pub trait CompletionProvider {
21728 fn completions(
21729 &self,
21730 excerpt_id: ExcerptId,
21731 buffer: &Entity<Buffer>,
21732 buffer_position: text::Anchor,
21733 trigger: CompletionContext,
21734 window: &mut Window,
21735 cx: &mut Context<Editor>,
21736 ) -> Task<Result<Vec<CompletionResponse>>>;
21737
21738 fn resolve_completions(
21739 &self,
21740 _buffer: Entity<Buffer>,
21741 _completion_indices: Vec<usize>,
21742 _completions: Rc<RefCell<Box<[Completion]>>>,
21743 _cx: &mut Context<Editor>,
21744 ) -> Task<Result<bool>> {
21745 Task::ready(Ok(false))
21746 }
21747
21748 fn apply_additional_edits_for_completion(
21749 &self,
21750 _buffer: Entity<Buffer>,
21751 _completions: Rc<RefCell<Box<[Completion]>>>,
21752 _completion_index: usize,
21753 _push_to_history: bool,
21754 _cx: &mut Context<Editor>,
21755 ) -> Task<Result<Option<language::Transaction>>> {
21756 Task::ready(Ok(None))
21757 }
21758
21759 fn is_completion_trigger(
21760 &self,
21761 buffer: &Entity<Buffer>,
21762 position: language::Anchor,
21763 text: &str,
21764 trigger_in_words: bool,
21765 menu_is_open: bool,
21766 cx: &mut Context<Editor>,
21767 ) -> bool;
21768
21769 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21770
21771 fn sort_completions(&self) -> bool {
21772 true
21773 }
21774
21775 fn filter_completions(&self) -> bool {
21776 true
21777 }
21778}
21779
21780pub trait CodeActionProvider {
21781 fn id(&self) -> Arc<str>;
21782
21783 fn code_actions(
21784 &self,
21785 buffer: &Entity<Buffer>,
21786 range: Range<text::Anchor>,
21787 window: &mut Window,
21788 cx: &mut App,
21789 ) -> Task<Result<Vec<CodeAction>>>;
21790
21791 fn apply_code_action(
21792 &self,
21793 buffer_handle: Entity<Buffer>,
21794 action: CodeAction,
21795 excerpt_id: ExcerptId,
21796 push_to_history: bool,
21797 window: &mut Window,
21798 cx: &mut App,
21799 ) -> Task<Result<ProjectTransaction>>;
21800}
21801
21802impl CodeActionProvider for Entity<Project> {
21803 fn id(&self) -> Arc<str> {
21804 "project".into()
21805 }
21806
21807 fn code_actions(
21808 &self,
21809 buffer: &Entity<Buffer>,
21810 range: Range<text::Anchor>,
21811 _window: &mut Window,
21812 cx: &mut App,
21813 ) -> Task<Result<Vec<CodeAction>>> {
21814 self.update(cx, |project, cx| {
21815 let code_lens = project.code_lens(buffer, range.clone(), cx);
21816 let code_actions = project.code_actions(buffer, range, None, cx);
21817 cx.background_spawn(async move {
21818 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21819 Ok(code_lens
21820 .context("code lens fetch")?
21821 .into_iter()
21822 .chain(code_actions.context("code action fetch")?)
21823 .collect())
21824 })
21825 })
21826 }
21827
21828 fn apply_code_action(
21829 &self,
21830 buffer_handle: Entity<Buffer>,
21831 action: CodeAction,
21832 _excerpt_id: ExcerptId,
21833 push_to_history: bool,
21834 _window: &mut Window,
21835 cx: &mut App,
21836 ) -> Task<Result<ProjectTransaction>> {
21837 self.update(cx, |project, cx| {
21838 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21839 })
21840 }
21841}
21842
21843fn snippet_completions(
21844 project: &Project,
21845 buffer: &Entity<Buffer>,
21846 buffer_position: text::Anchor,
21847 cx: &mut App,
21848) -> Task<Result<CompletionResponse>> {
21849 let languages = buffer.read(cx).languages_at(buffer_position);
21850 let snippet_store = project.snippets().read(cx);
21851
21852 let scopes: Vec<_> = languages
21853 .iter()
21854 .filter_map(|language| {
21855 let language_name = language.lsp_id();
21856 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21857
21858 if snippets.is_empty() {
21859 None
21860 } else {
21861 Some((language.default_scope(), snippets))
21862 }
21863 })
21864 .collect();
21865
21866 if scopes.is_empty() {
21867 return Task::ready(Ok(CompletionResponse {
21868 completions: vec![],
21869 is_incomplete: false,
21870 }));
21871 }
21872
21873 let snapshot = buffer.read(cx).text_snapshot();
21874 let chars: String = snapshot
21875 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21876 .collect();
21877 let executor = cx.background_executor().clone();
21878
21879 cx.background_spawn(async move {
21880 let mut is_incomplete = false;
21881 let mut completions: Vec<Completion> = Vec::new();
21882 for (scope, snippets) in scopes.into_iter() {
21883 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21884 let mut last_word = chars
21885 .chars()
21886 .take_while(|c| classifier.is_word(*c))
21887 .collect::<String>();
21888 last_word = last_word.chars().rev().collect();
21889
21890 if last_word.is_empty() {
21891 return Ok(CompletionResponse {
21892 completions: vec![],
21893 is_incomplete: true,
21894 });
21895 }
21896
21897 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21898 let to_lsp = |point: &text::Anchor| {
21899 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21900 point_to_lsp(end)
21901 };
21902 let lsp_end = to_lsp(&buffer_position);
21903
21904 let candidates = snippets
21905 .iter()
21906 .enumerate()
21907 .flat_map(|(ix, snippet)| {
21908 snippet
21909 .prefix
21910 .iter()
21911 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21912 })
21913 .collect::<Vec<StringMatchCandidate>>();
21914
21915 const MAX_RESULTS: usize = 100;
21916 let mut matches = fuzzy::match_strings(
21917 &candidates,
21918 &last_word,
21919 last_word.chars().any(|c| c.is_uppercase()),
21920 true,
21921 MAX_RESULTS,
21922 &Default::default(),
21923 executor.clone(),
21924 )
21925 .await;
21926
21927 if matches.len() >= MAX_RESULTS {
21928 is_incomplete = true;
21929 }
21930
21931 // Remove all candidates where the query's start does not match the start of any word in the candidate
21932 if let Some(query_start) = last_word.chars().next() {
21933 matches.retain(|string_match| {
21934 split_words(&string_match.string).any(|word| {
21935 // Check that the first codepoint of the word as lowercase matches the first
21936 // codepoint of the query as lowercase
21937 word.chars()
21938 .flat_map(|codepoint| codepoint.to_lowercase())
21939 .zip(query_start.to_lowercase())
21940 .all(|(word_cp, query_cp)| word_cp == query_cp)
21941 })
21942 });
21943 }
21944
21945 let matched_strings = matches
21946 .into_iter()
21947 .map(|m| m.string)
21948 .collect::<HashSet<_>>();
21949
21950 completions.extend(snippets.iter().filter_map(|snippet| {
21951 let matching_prefix = snippet
21952 .prefix
21953 .iter()
21954 .find(|prefix| matched_strings.contains(*prefix))?;
21955 let start = as_offset - last_word.len();
21956 let start = snapshot.anchor_before(start);
21957 let range = start..buffer_position;
21958 let lsp_start = to_lsp(&start);
21959 let lsp_range = lsp::Range {
21960 start: lsp_start,
21961 end: lsp_end,
21962 };
21963 Some(Completion {
21964 replace_range: range,
21965 new_text: snippet.body.clone(),
21966 source: CompletionSource::Lsp {
21967 insert_range: None,
21968 server_id: LanguageServerId(usize::MAX),
21969 resolved: true,
21970 lsp_completion: Box::new(lsp::CompletionItem {
21971 label: snippet.prefix.first().unwrap().clone(),
21972 kind: Some(CompletionItemKind::SNIPPET),
21973 label_details: snippet.description.as_ref().map(|description| {
21974 lsp::CompletionItemLabelDetails {
21975 detail: Some(description.clone()),
21976 description: None,
21977 }
21978 }),
21979 insert_text_format: Some(InsertTextFormat::SNIPPET),
21980 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21981 lsp::InsertReplaceEdit {
21982 new_text: snippet.body.clone(),
21983 insert: lsp_range,
21984 replace: lsp_range,
21985 },
21986 )),
21987 filter_text: Some(snippet.body.clone()),
21988 sort_text: Some(char::MAX.to_string()),
21989 ..lsp::CompletionItem::default()
21990 }),
21991 lsp_defaults: None,
21992 },
21993 label: CodeLabel {
21994 text: matching_prefix.clone(),
21995 runs: Vec::new(),
21996 filter_range: 0..matching_prefix.len(),
21997 },
21998 icon_path: None,
21999 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22000 single_line: snippet.name.clone().into(),
22001 plain_text: snippet
22002 .description
22003 .clone()
22004 .map(|description| description.into()),
22005 }),
22006 insert_text_mode: None,
22007 confirm: None,
22008 })
22009 }))
22010 }
22011
22012 Ok(CompletionResponse {
22013 completions,
22014 is_incomplete,
22015 })
22016 })
22017}
22018
22019impl CompletionProvider for Entity<Project> {
22020 fn completions(
22021 &self,
22022 _excerpt_id: ExcerptId,
22023 buffer: &Entity<Buffer>,
22024 buffer_position: text::Anchor,
22025 options: CompletionContext,
22026 _window: &mut Window,
22027 cx: &mut Context<Editor>,
22028 ) -> Task<Result<Vec<CompletionResponse>>> {
22029 self.update(cx, |project, cx| {
22030 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22031 let project_completions = project.completions(buffer, buffer_position, options, cx);
22032 cx.background_spawn(async move {
22033 let mut responses = project_completions.await?;
22034 let snippets = snippets.await?;
22035 if !snippets.completions.is_empty() {
22036 responses.push(snippets);
22037 }
22038 Ok(responses)
22039 })
22040 })
22041 }
22042
22043 fn resolve_completions(
22044 &self,
22045 buffer: Entity<Buffer>,
22046 completion_indices: Vec<usize>,
22047 completions: Rc<RefCell<Box<[Completion]>>>,
22048 cx: &mut Context<Editor>,
22049 ) -> Task<Result<bool>> {
22050 self.update(cx, |project, cx| {
22051 project.lsp_store().update(cx, |lsp_store, cx| {
22052 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22053 })
22054 })
22055 }
22056
22057 fn apply_additional_edits_for_completion(
22058 &self,
22059 buffer: Entity<Buffer>,
22060 completions: Rc<RefCell<Box<[Completion]>>>,
22061 completion_index: usize,
22062 push_to_history: bool,
22063 cx: &mut Context<Editor>,
22064 ) -> Task<Result<Option<language::Transaction>>> {
22065 self.update(cx, |project, cx| {
22066 project.lsp_store().update(cx, |lsp_store, cx| {
22067 lsp_store.apply_additional_edits_for_completion(
22068 buffer,
22069 completions,
22070 completion_index,
22071 push_to_history,
22072 cx,
22073 )
22074 })
22075 })
22076 }
22077
22078 fn is_completion_trigger(
22079 &self,
22080 buffer: &Entity<Buffer>,
22081 position: language::Anchor,
22082 text: &str,
22083 trigger_in_words: bool,
22084 menu_is_open: bool,
22085 cx: &mut Context<Editor>,
22086 ) -> bool {
22087 let mut chars = text.chars();
22088 let char = if let Some(char) = chars.next() {
22089 char
22090 } else {
22091 return false;
22092 };
22093 if chars.next().is_some() {
22094 return false;
22095 }
22096
22097 let buffer = buffer.read(cx);
22098 let snapshot = buffer.snapshot();
22099 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22100 return false;
22101 }
22102 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22103 if trigger_in_words && classifier.is_word(char) {
22104 return true;
22105 }
22106
22107 buffer.completion_triggers().contains(text)
22108 }
22109}
22110
22111impl SemanticsProvider for Entity<Project> {
22112 fn hover(
22113 &self,
22114 buffer: &Entity<Buffer>,
22115 position: text::Anchor,
22116 cx: &mut App,
22117 ) -> Option<Task<Vec<project::Hover>>> {
22118 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22119 }
22120
22121 fn document_highlights(
22122 &self,
22123 buffer: &Entity<Buffer>,
22124 position: text::Anchor,
22125 cx: &mut App,
22126 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22127 Some(self.update(cx, |project, cx| {
22128 project.document_highlights(buffer, position, cx)
22129 }))
22130 }
22131
22132 fn definitions(
22133 &self,
22134 buffer: &Entity<Buffer>,
22135 position: text::Anchor,
22136 kind: GotoDefinitionKind,
22137 cx: &mut App,
22138 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22139 Some(self.update(cx, |project, cx| match kind {
22140 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22141 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22142 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22143 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22144 }))
22145 }
22146
22147 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22148 // TODO: make this work for remote projects
22149 self.update(cx, |project, cx| {
22150 if project
22151 .active_debug_session(cx)
22152 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22153 {
22154 return true;
22155 }
22156
22157 buffer.update(cx, |buffer, cx| {
22158 project.any_language_server_supports_inlay_hints(buffer, cx)
22159 })
22160 })
22161 }
22162
22163 fn inline_values(
22164 &self,
22165 buffer_handle: Entity<Buffer>,
22166 range: Range<text::Anchor>,
22167 cx: &mut App,
22168 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22169 self.update(cx, |project, cx| {
22170 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22171
22172 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22173 })
22174 }
22175
22176 fn inlay_hints(
22177 &self,
22178 buffer_handle: Entity<Buffer>,
22179 range: Range<text::Anchor>,
22180 cx: &mut App,
22181 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22182 Some(self.update(cx, |project, cx| {
22183 project.inlay_hints(buffer_handle, range, cx)
22184 }))
22185 }
22186
22187 fn resolve_inlay_hint(
22188 &self,
22189 hint: InlayHint,
22190 buffer_handle: Entity<Buffer>,
22191 server_id: LanguageServerId,
22192 cx: &mut App,
22193 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22194 Some(self.update(cx, |project, cx| {
22195 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22196 }))
22197 }
22198
22199 fn range_for_rename(
22200 &self,
22201 buffer: &Entity<Buffer>,
22202 position: text::Anchor,
22203 cx: &mut App,
22204 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22205 Some(self.update(cx, |project, cx| {
22206 let buffer = buffer.clone();
22207 let task = project.prepare_rename(buffer.clone(), position, cx);
22208 cx.spawn(async move |_, cx| {
22209 Ok(match task.await? {
22210 PrepareRenameResponse::Success(range) => Some(range),
22211 PrepareRenameResponse::InvalidPosition => None,
22212 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22213 // Fallback on using TreeSitter info to determine identifier range
22214 buffer.read_with(cx, |buffer, _| {
22215 let snapshot = buffer.snapshot();
22216 let (range, kind) = snapshot.surrounding_word(position, false);
22217 if kind != Some(CharKind::Word) {
22218 return None;
22219 }
22220 Some(
22221 snapshot.anchor_before(range.start)
22222 ..snapshot.anchor_after(range.end),
22223 )
22224 })?
22225 }
22226 })
22227 })
22228 }))
22229 }
22230
22231 fn perform_rename(
22232 &self,
22233 buffer: &Entity<Buffer>,
22234 position: text::Anchor,
22235 new_name: String,
22236 cx: &mut App,
22237 ) -> Option<Task<Result<ProjectTransaction>>> {
22238 Some(self.update(cx, |project, cx| {
22239 project.perform_rename(buffer.clone(), position, new_name, cx)
22240 }))
22241 }
22242}
22243
22244fn inlay_hint_settings(
22245 location: Anchor,
22246 snapshot: &MultiBufferSnapshot,
22247 cx: &mut Context<Editor>,
22248) -> InlayHintSettings {
22249 let file = snapshot.file_at(location);
22250 let language = snapshot.language_at(location).map(|l| l.name());
22251 language_settings(language, file, cx).inlay_hints
22252}
22253
22254fn consume_contiguous_rows(
22255 contiguous_row_selections: &mut Vec<Selection<Point>>,
22256 selection: &Selection<Point>,
22257 display_map: &DisplaySnapshot,
22258 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22259) -> (MultiBufferRow, MultiBufferRow) {
22260 contiguous_row_selections.push(selection.clone());
22261 let start_row = MultiBufferRow(selection.start.row);
22262 let mut end_row = ending_row(selection, display_map);
22263
22264 while let Some(next_selection) = selections.peek() {
22265 if next_selection.start.row <= end_row.0 {
22266 end_row = ending_row(next_selection, display_map);
22267 contiguous_row_selections.push(selections.next().unwrap().clone());
22268 } else {
22269 break;
22270 }
22271 }
22272 (start_row, end_row)
22273}
22274
22275fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22276 if next_selection.end.column > 0 || next_selection.is_empty() {
22277 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22278 } else {
22279 MultiBufferRow(next_selection.end.row)
22280 }
22281}
22282
22283impl EditorSnapshot {
22284 pub fn remote_selections_in_range<'a>(
22285 &'a self,
22286 range: &'a Range<Anchor>,
22287 collaboration_hub: &dyn CollaborationHub,
22288 cx: &'a App,
22289 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22290 let participant_names = collaboration_hub.user_names(cx);
22291 let participant_indices = collaboration_hub.user_participant_indices(cx);
22292 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22293 let collaborators_by_replica_id = collaborators_by_peer_id
22294 .values()
22295 .map(|collaborator| (collaborator.replica_id, collaborator))
22296 .collect::<HashMap<_, _>>();
22297 self.buffer_snapshot
22298 .selections_in_range(range, false)
22299 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22300 if replica_id == AGENT_REPLICA_ID {
22301 Some(RemoteSelection {
22302 replica_id,
22303 selection,
22304 cursor_shape,
22305 line_mode,
22306 collaborator_id: CollaboratorId::Agent,
22307 user_name: Some("Agent".into()),
22308 color: cx.theme().players().agent(),
22309 })
22310 } else {
22311 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22312 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22313 let user_name = participant_names.get(&collaborator.user_id).cloned();
22314 Some(RemoteSelection {
22315 replica_id,
22316 selection,
22317 cursor_shape,
22318 line_mode,
22319 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22320 user_name,
22321 color: if let Some(index) = participant_index {
22322 cx.theme().players().color_for_participant(index.0)
22323 } else {
22324 cx.theme().players().absent()
22325 },
22326 })
22327 }
22328 })
22329 }
22330
22331 pub fn hunks_for_ranges(
22332 &self,
22333 ranges: impl IntoIterator<Item = Range<Point>>,
22334 ) -> Vec<MultiBufferDiffHunk> {
22335 let mut hunks = Vec::new();
22336 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22337 HashMap::default();
22338 for query_range in ranges {
22339 let query_rows =
22340 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22341 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22342 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22343 ) {
22344 // Include deleted hunks that are adjacent to the query range, because
22345 // otherwise they would be missed.
22346 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22347 if hunk.status().is_deleted() {
22348 intersects_range |= hunk.row_range.start == query_rows.end;
22349 intersects_range |= hunk.row_range.end == query_rows.start;
22350 }
22351 if intersects_range {
22352 if !processed_buffer_rows
22353 .entry(hunk.buffer_id)
22354 .or_default()
22355 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22356 {
22357 continue;
22358 }
22359 hunks.push(hunk);
22360 }
22361 }
22362 }
22363
22364 hunks
22365 }
22366
22367 fn display_diff_hunks_for_rows<'a>(
22368 &'a self,
22369 display_rows: Range<DisplayRow>,
22370 folded_buffers: &'a HashSet<BufferId>,
22371 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22372 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22373 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22374
22375 self.buffer_snapshot
22376 .diff_hunks_in_range(buffer_start..buffer_end)
22377 .filter_map(|hunk| {
22378 if folded_buffers.contains(&hunk.buffer_id) {
22379 return None;
22380 }
22381
22382 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22383 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22384
22385 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22386 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22387
22388 let display_hunk = if hunk_display_start.column() != 0 {
22389 DisplayDiffHunk::Folded {
22390 display_row: hunk_display_start.row(),
22391 }
22392 } else {
22393 let mut end_row = hunk_display_end.row();
22394 if hunk_display_end.column() > 0 {
22395 end_row.0 += 1;
22396 }
22397 let is_created_file = hunk.is_created_file();
22398 DisplayDiffHunk::Unfolded {
22399 status: hunk.status(),
22400 diff_base_byte_range: hunk.diff_base_byte_range,
22401 display_row_range: hunk_display_start.row()..end_row,
22402 multi_buffer_range: Anchor::range_in_buffer(
22403 hunk.excerpt_id,
22404 hunk.buffer_id,
22405 hunk.buffer_range,
22406 ),
22407 is_created_file,
22408 }
22409 };
22410
22411 Some(display_hunk)
22412 })
22413 }
22414
22415 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22416 self.display_snapshot.buffer_snapshot.language_at(position)
22417 }
22418
22419 pub fn is_focused(&self) -> bool {
22420 self.is_focused
22421 }
22422
22423 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22424 self.placeholder_text.as_ref()
22425 }
22426
22427 pub fn scroll_position(&self) -> gpui::Point<f32> {
22428 self.scroll_anchor.scroll_position(&self.display_snapshot)
22429 }
22430
22431 fn gutter_dimensions(
22432 &self,
22433 font_id: FontId,
22434 font_size: Pixels,
22435 max_line_number_width: Pixels,
22436 cx: &App,
22437 ) -> Option<GutterDimensions> {
22438 if !self.show_gutter {
22439 return None;
22440 }
22441
22442 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22443 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22444
22445 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22446 matches!(
22447 ProjectSettings::get_global(cx).git.git_gutter,
22448 Some(GitGutterSetting::TrackedFiles)
22449 )
22450 });
22451 let gutter_settings = EditorSettings::get_global(cx).gutter;
22452 let show_line_numbers = self
22453 .show_line_numbers
22454 .unwrap_or(gutter_settings.line_numbers);
22455 let line_gutter_width = if show_line_numbers {
22456 // Avoid flicker-like gutter resizes when the line number gains another digit by
22457 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22458 let min_width_for_number_on_gutter =
22459 ch_advance * gutter_settings.min_line_number_digits as f32;
22460 max_line_number_width.max(min_width_for_number_on_gutter)
22461 } else {
22462 0.0.into()
22463 };
22464
22465 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22466 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22467
22468 let git_blame_entries_width =
22469 self.git_blame_gutter_max_author_length
22470 .map(|max_author_length| {
22471 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22472 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22473
22474 /// The number of characters to dedicate to gaps and margins.
22475 const SPACING_WIDTH: usize = 4;
22476
22477 let max_char_count = max_author_length.min(renderer.max_author_length())
22478 + ::git::SHORT_SHA_LENGTH
22479 + MAX_RELATIVE_TIMESTAMP.len()
22480 + SPACING_WIDTH;
22481
22482 ch_advance * max_char_count
22483 });
22484
22485 let is_singleton = self.buffer_snapshot.is_singleton();
22486
22487 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22488 left_padding += if !is_singleton {
22489 ch_width * 4.0
22490 } else if show_runnables || show_breakpoints {
22491 ch_width * 3.0
22492 } else if show_git_gutter && show_line_numbers {
22493 ch_width * 2.0
22494 } else if show_git_gutter || show_line_numbers {
22495 ch_width
22496 } else {
22497 px(0.)
22498 };
22499
22500 let shows_folds = is_singleton && gutter_settings.folds;
22501
22502 let right_padding = if shows_folds && show_line_numbers {
22503 ch_width * 4.0
22504 } else if shows_folds || (!is_singleton && show_line_numbers) {
22505 ch_width * 3.0
22506 } else if show_line_numbers {
22507 ch_width
22508 } else {
22509 px(0.)
22510 };
22511
22512 Some(GutterDimensions {
22513 left_padding,
22514 right_padding,
22515 width: line_gutter_width + left_padding + right_padding,
22516 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22517 git_blame_entries_width,
22518 })
22519 }
22520
22521 pub fn render_crease_toggle(
22522 &self,
22523 buffer_row: MultiBufferRow,
22524 row_contains_cursor: bool,
22525 editor: Entity<Editor>,
22526 window: &mut Window,
22527 cx: &mut App,
22528 ) -> Option<AnyElement> {
22529 let folded = self.is_line_folded(buffer_row);
22530 let mut is_foldable = false;
22531
22532 if let Some(crease) = self
22533 .crease_snapshot
22534 .query_row(buffer_row, &self.buffer_snapshot)
22535 {
22536 is_foldable = true;
22537 match crease {
22538 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22539 if let Some(render_toggle) = render_toggle {
22540 let toggle_callback =
22541 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22542 if folded {
22543 editor.update(cx, |editor, cx| {
22544 editor.fold_at(buffer_row, window, cx)
22545 });
22546 } else {
22547 editor.update(cx, |editor, cx| {
22548 editor.unfold_at(buffer_row, window, cx)
22549 });
22550 }
22551 });
22552 return Some((render_toggle)(
22553 buffer_row,
22554 folded,
22555 toggle_callback,
22556 window,
22557 cx,
22558 ));
22559 }
22560 }
22561 }
22562 }
22563
22564 is_foldable |= self.starts_indent(buffer_row);
22565
22566 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22567 Some(
22568 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22569 .toggle_state(folded)
22570 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22571 if folded {
22572 this.unfold_at(buffer_row, window, cx);
22573 } else {
22574 this.fold_at(buffer_row, window, cx);
22575 }
22576 }))
22577 .into_any_element(),
22578 )
22579 } else {
22580 None
22581 }
22582 }
22583
22584 pub fn render_crease_trailer(
22585 &self,
22586 buffer_row: MultiBufferRow,
22587 window: &mut Window,
22588 cx: &mut App,
22589 ) -> Option<AnyElement> {
22590 let folded = self.is_line_folded(buffer_row);
22591 if let Crease::Inline { render_trailer, .. } = self
22592 .crease_snapshot
22593 .query_row(buffer_row, &self.buffer_snapshot)?
22594 {
22595 let render_trailer = render_trailer.as_ref()?;
22596 Some(render_trailer(buffer_row, folded, window, cx))
22597 } else {
22598 None
22599 }
22600 }
22601}
22602
22603impl Deref for EditorSnapshot {
22604 type Target = DisplaySnapshot;
22605
22606 fn deref(&self) -> &Self::Target {
22607 &self.display_snapshot
22608 }
22609}
22610
22611#[derive(Clone, Debug, PartialEq, Eq)]
22612pub enum EditorEvent {
22613 InputIgnored {
22614 text: Arc<str>,
22615 },
22616 InputHandled {
22617 utf16_range_to_replace: Option<Range<isize>>,
22618 text: Arc<str>,
22619 },
22620 ExcerptsAdded {
22621 buffer: Entity<Buffer>,
22622 predecessor: ExcerptId,
22623 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22624 },
22625 ExcerptsRemoved {
22626 ids: Vec<ExcerptId>,
22627 removed_buffer_ids: Vec<BufferId>,
22628 },
22629 BufferFoldToggled {
22630 ids: Vec<ExcerptId>,
22631 folded: bool,
22632 },
22633 ExcerptsEdited {
22634 ids: Vec<ExcerptId>,
22635 },
22636 ExcerptsExpanded {
22637 ids: Vec<ExcerptId>,
22638 },
22639 BufferEdited,
22640 Edited {
22641 transaction_id: clock::Lamport,
22642 },
22643 Reparsed(BufferId),
22644 Focused,
22645 FocusedIn,
22646 Blurred,
22647 DirtyChanged,
22648 Saved,
22649 TitleChanged,
22650 DiffBaseChanged,
22651 SelectionsChanged {
22652 local: bool,
22653 },
22654 ScrollPositionChanged {
22655 local: bool,
22656 autoscroll: bool,
22657 },
22658 Closed,
22659 TransactionUndone {
22660 transaction_id: clock::Lamport,
22661 },
22662 TransactionBegun {
22663 transaction_id: clock::Lamport,
22664 },
22665 Reloaded,
22666 CursorShapeChanged,
22667 PushedToNavHistory {
22668 anchor: Anchor,
22669 is_deactivate: bool,
22670 },
22671}
22672
22673impl EventEmitter<EditorEvent> for Editor {}
22674
22675impl Focusable for Editor {
22676 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22677 self.focus_handle.clone()
22678 }
22679}
22680
22681impl Render for Editor {
22682 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22683 let settings = ThemeSettings::get_global(cx);
22684
22685 let mut text_style = match self.mode {
22686 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22687 color: cx.theme().colors().editor_foreground,
22688 font_family: settings.ui_font.family.clone(),
22689 font_features: settings.ui_font.features.clone(),
22690 font_fallbacks: settings.ui_font.fallbacks.clone(),
22691 font_size: rems(0.875).into(),
22692 font_weight: settings.ui_font.weight,
22693 line_height: relative(settings.buffer_line_height.value()),
22694 ..Default::default()
22695 },
22696 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22697 color: cx.theme().colors().editor_foreground,
22698 font_family: settings.buffer_font.family.clone(),
22699 font_features: settings.buffer_font.features.clone(),
22700 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22701 font_size: settings.buffer_font_size(cx).into(),
22702 font_weight: settings.buffer_font.weight,
22703 line_height: relative(settings.buffer_line_height.value()),
22704 ..Default::default()
22705 },
22706 };
22707 if let Some(text_style_refinement) = &self.text_style_refinement {
22708 text_style.refine(text_style_refinement)
22709 }
22710
22711 let background = match self.mode {
22712 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22713 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22714 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22715 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22716 };
22717
22718 EditorElement::new(
22719 &cx.entity(),
22720 EditorStyle {
22721 background,
22722 border: cx.theme().colors().border,
22723 local_player: cx.theme().players().local(),
22724 text: text_style,
22725 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22726 syntax: cx.theme().syntax().clone(),
22727 status: cx.theme().status().clone(),
22728 inlay_hints_style: make_inlay_hints_style(cx),
22729 inline_completion_styles: make_suggestion_styles(cx),
22730 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22731 show_underlines: self.diagnostics_enabled(),
22732 },
22733 )
22734 }
22735}
22736
22737impl EntityInputHandler for Editor {
22738 fn text_for_range(
22739 &mut self,
22740 range_utf16: Range<usize>,
22741 adjusted_range: &mut Option<Range<usize>>,
22742 _: &mut Window,
22743 cx: &mut Context<Self>,
22744 ) -> Option<String> {
22745 let snapshot = self.buffer.read(cx).read(cx);
22746 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22747 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22748 if (start.0..end.0) != range_utf16 {
22749 adjusted_range.replace(start.0..end.0);
22750 }
22751 Some(snapshot.text_for_range(start..end).collect())
22752 }
22753
22754 fn selected_text_range(
22755 &mut self,
22756 ignore_disabled_input: bool,
22757 _: &mut Window,
22758 cx: &mut Context<Self>,
22759 ) -> Option<UTF16Selection> {
22760 // Prevent the IME menu from appearing when holding down an alphabetic key
22761 // while input is disabled.
22762 if !ignore_disabled_input && !self.input_enabled {
22763 return None;
22764 }
22765
22766 let selection = self.selections.newest::<OffsetUtf16>(cx);
22767 let range = selection.range();
22768
22769 Some(UTF16Selection {
22770 range: range.start.0..range.end.0,
22771 reversed: selection.reversed,
22772 })
22773 }
22774
22775 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22776 let snapshot = self.buffer.read(cx).read(cx);
22777 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22778 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22779 }
22780
22781 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22782 self.clear_highlights::<InputComposition>(cx);
22783 self.ime_transaction.take();
22784 }
22785
22786 fn replace_text_in_range(
22787 &mut self,
22788 range_utf16: Option<Range<usize>>,
22789 text: &str,
22790 window: &mut Window,
22791 cx: &mut Context<Self>,
22792 ) {
22793 if !self.input_enabled {
22794 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22795 return;
22796 }
22797
22798 self.transact(window, cx, |this, window, cx| {
22799 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22800 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22801 Some(this.selection_replacement_ranges(range_utf16, cx))
22802 } else {
22803 this.marked_text_ranges(cx)
22804 };
22805
22806 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22807 let newest_selection_id = this.selections.newest_anchor().id;
22808 this.selections
22809 .all::<OffsetUtf16>(cx)
22810 .iter()
22811 .zip(ranges_to_replace.iter())
22812 .find_map(|(selection, range)| {
22813 if selection.id == newest_selection_id {
22814 Some(
22815 (range.start.0 as isize - selection.head().0 as isize)
22816 ..(range.end.0 as isize - selection.head().0 as isize),
22817 )
22818 } else {
22819 None
22820 }
22821 })
22822 });
22823
22824 cx.emit(EditorEvent::InputHandled {
22825 utf16_range_to_replace: range_to_replace,
22826 text: text.into(),
22827 });
22828
22829 if let Some(new_selected_ranges) = new_selected_ranges {
22830 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22831 selections.select_ranges(new_selected_ranges)
22832 });
22833 this.backspace(&Default::default(), window, cx);
22834 }
22835
22836 this.handle_input(text, window, cx);
22837 });
22838
22839 if let Some(transaction) = self.ime_transaction {
22840 self.buffer.update(cx, |buffer, cx| {
22841 buffer.group_until_transaction(transaction, cx);
22842 });
22843 }
22844
22845 self.unmark_text(window, cx);
22846 }
22847
22848 fn replace_and_mark_text_in_range(
22849 &mut self,
22850 range_utf16: Option<Range<usize>>,
22851 text: &str,
22852 new_selected_range_utf16: Option<Range<usize>>,
22853 window: &mut Window,
22854 cx: &mut Context<Self>,
22855 ) {
22856 if !self.input_enabled {
22857 return;
22858 }
22859
22860 let transaction = self.transact(window, cx, |this, window, cx| {
22861 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22862 let snapshot = this.buffer.read(cx).read(cx);
22863 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22864 for marked_range in &mut marked_ranges {
22865 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22866 marked_range.start.0 += relative_range_utf16.start;
22867 marked_range.start =
22868 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22869 marked_range.end =
22870 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22871 }
22872 }
22873 Some(marked_ranges)
22874 } else if let Some(range_utf16) = range_utf16 {
22875 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22876 Some(this.selection_replacement_ranges(range_utf16, cx))
22877 } else {
22878 None
22879 };
22880
22881 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22882 let newest_selection_id = this.selections.newest_anchor().id;
22883 this.selections
22884 .all::<OffsetUtf16>(cx)
22885 .iter()
22886 .zip(ranges_to_replace.iter())
22887 .find_map(|(selection, range)| {
22888 if selection.id == newest_selection_id {
22889 Some(
22890 (range.start.0 as isize - selection.head().0 as isize)
22891 ..(range.end.0 as isize - selection.head().0 as isize),
22892 )
22893 } else {
22894 None
22895 }
22896 })
22897 });
22898
22899 cx.emit(EditorEvent::InputHandled {
22900 utf16_range_to_replace: range_to_replace,
22901 text: text.into(),
22902 });
22903
22904 if let Some(ranges) = ranges_to_replace {
22905 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22906 s.select_ranges(ranges)
22907 });
22908 }
22909
22910 let marked_ranges = {
22911 let snapshot = this.buffer.read(cx).read(cx);
22912 this.selections
22913 .disjoint_anchors()
22914 .iter()
22915 .map(|selection| {
22916 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22917 })
22918 .collect::<Vec<_>>()
22919 };
22920
22921 if text.is_empty() {
22922 this.unmark_text(window, cx);
22923 } else {
22924 this.highlight_text::<InputComposition>(
22925 marked_ranges.clone(),
22926 HighlightStyle {
22927 underline: Some(UnderlineStyle {
22928 thickness: px(1.),
22929 color: None,
22930 wavy: false,
22931 }),
22932 ..Default::default()
22933 },
22934 cx,
22935 );
22936 }
22937
22938 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22939 let use_autoclose = this.use_autoclose;
22940 let use_auto_surround = this.use_auto_surround;
22941 this.set_use_autoclose(false);
22942 this.set_use_auto_surround(false);
22943 this.handle_input(text, window, cx);
22944 this.set_use_autoclose(use_autoclose);
22945 this.set_use_auto_surround(use_auto_surround);
22946
22947 if let Some(new_selected_range) = new_selected_range_utf16 {
22948 let snapshot = this.buffer.read(cx).read(cx);
22949 let new_selected_ranges = marked_ranges
22950 .into_iter()
22951 .map(|marked_range| {
22952 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22953 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22954 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22955 snapshot.clip_offset_utf16(new_start, Bias::Left)
22956 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22957 })
22958 .collect::<Vec<_>>();
22959
22960 drop(snapshot);
22961 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22962 selections.select_ranges(new_selected_ranges)
22963 });
22964 }
22965 });
22966
22967 self.ime_transaction = self.ime_transaction.or(transaction);
22968 if let Some(transaction) = self.ime_transaction {
22969 self.buffer.update(cx, |buffer, cx| {
22970 buffer.group_until_transaction(transaction, cx);
22971 });
22972 }
22973
22974 if self.text_highlights::<InputComposition>(cx).is_none() {
22975 self.ime_transaction.take();
22976 }
22977 }
22978
22979 fn bounds_for_range(
22980 &mut self,
22981 range_utf16: Range<usize>,
22982 element_bounds: gpui::Bounds<Pixels>,
22983 window: &mut Window,
22984 cx: &mut Context<Self>,
22985 ) -> Option<gpui::Bounds<Pixels>> {
22986 let text_layout_details = self.text_layout_details(window);
22987 let CharacterDimensions {
22988 em_width,
22989 em_advance,
22990 line_height,
22991 } = self.character_dimensions(window);
22992
22993 let snapshot = self.snapshot(window, cx);
22994 let scroll_position = snapshot.scroll_position();
22995 let scroll_left = scroll_position.x * em_advance;
22996
22997 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22998 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22999 + self.gutter_dimensions.full_width();
23000 let y = line_height * (start.row().as_f32() - scroll_position.y);
23001
23002 Some(Bounds {
23003 origin: element_bounds.origin + point(x, y),
23004 size: size(em_width, line_height),
23005 })
23006 }
23007
23008 fn character_index_for_point(
23009 &mut self,
23010 point: gpui::Point<Pixels>,
23011 _window: &mut Window,
23012 _cx: &mut Context<Self>,
23013 ) -> Option<usize> {
23014 let position_map = self.last_position_map.as_ref()?;
23015 if !position_map.text_hitbox.contains(&point) {
23016 return None;
23017 }
23018 let display_point = position_map.point_for_position(point).previous_valid;
23019 let anchor = position_map
23020 .snapshot
23021 .display_point_to_anchor(display_point, Bias::Left);
23022 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23023 Some(utf16_offset.0)
23024 }
23025}
23026
23027trait SelectionExt {
23028 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23029 fn spanned_rows(
23030 &self,
23031 include_end_if_at_line_start: bool,
23032 map: &DisplaySnapshot,
23033 ) -> Range<MultiBufferRow>;
23034}
23035
23036impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23037 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23038 let start = self
23039 .start
23040 .to_point(&map.buffer_snapshot)
23041 .to_display_point(map);
23042 let end = self
23043 .end
23044 .to_point(&map.buffer_snapshot)
23045 .to_display_point(map);
23046 if self.reversed {
23047 end..start
23048 } else {
23049 start..end
23050 }
23051 }
23052
23053 fn spanned_rows(
23054 &self,
23055 include_end_if_at_line_start: bool,
23056 map: &DisplaySnapshot,
23057 ) -> Range<MultiBufferRow> {
23058 let start = self.start.to_point(&map.buffer_snapshot);
23059 let mut end = self.end.to_point(&map.buffer_snapshot);
23060 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23061 end.row -= 1;
23062 }
23063
23064 let buffer_start = map.prev_line_boundary(start).0;
23065 let buffer_end = map.next_line_boundary(end).0;
23066 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23067 }
23068}
23069
23070impl<T: InvalidationRegion> InvalidationStack<T> {
23071 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23072 where
23073 S: Clone + ToOffset,
23074 {
23075 while let Some(region) = self.last() {
23076 let all_selections_inside_invalidation_ranges =
23077 if selections.len() == region.ranges().len() {
23078 selections
23079 .iter()
23080 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23081 .all(|(selection, invalidation_range)| {
23082 let head = selection.head().to_offset(buffer);
23083 invalidation_range.start <= head && invalidation_range.end >= head
23084 })
23085 } else {
23086 false
23087 };
23088
23089 if all_selections_inside_invalidation_ranges {
23090 break;
23091 } else {
23092 self.pop();
23093 }
23094 }
23095 }
23096}
23097
23098impl<T> Default for InvalidationStack<T> {
23099 fn default() -> Self {
23100 Self(Default::default())
23101 }
23102}
23103
23104impl<T> Deref for InvalidationStack<T> {
23105 type Target = Vec<T>;
23106
23107 fn deref(&self) -> &Self::Target {
23108 &self.0
23109 }
23110}
23111
23112impl<T> DerefMut for InvalidationStack<T> {
23113 fn deref_mut(&mut self) -> &mut Self::Target {
23114 &mut self.0
23115 }
23116}
23117
23118impl InvalidationRegion for SnippetState {
23119 fn ranges(&self) -> &[Range<Anchor>] {
23120 &self.ranges[self.active_index]
23121 }
23122}
23123
23124fn inline_completion_edit_text(
23125 current_snapshot: &BufferSnapshot,
23126 edits: &[(Range<Anchor>, String)],
23127 edit_preview: &EditPreview,
23128 include_deletions: bool,
23129 cx: &App,
23130) -> HighlightedText {
23131 let edits = edits
23132 .iter()
23133 .map(|(anchor, text)| {
23134 (
23135 anchor.start.text_anchor..anchor.end.text_anchor,
23136 text.clone(),
23137 )
23138 })
23139 .collect::<Vec<_>>();
23140
23141 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23142}
23143
23144pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23145 match severity {
23146 lsp::DiagnosticSeverity::ERROR => colors.error,
23147 lsp::DiagnosticSeverity::WARNING => colors.warning,
23148 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23149 lsp::DiagnosticSeverity::HINT => colors.info,
23150 _ => colors.ignored,
23151 }
23152}
23153
23154pub fn styled_runs_for_code_label<'a>(
23155 label: &'a CodeLabel,
23156 syntax_theme: &'a theme::SyntaxTheme,
23157) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23158 let fade_out = HighlightStyle {
23159 fade_out: Some(0.35),
23160 ..Default::default()
23161 };
23162
23163 let mut prev_end = label.filter_range.end;
23164 label
23165 .runs
23166 .iter()
23167 .enumerate()
23168 .flat_map(move |(ix, (range, highlight_id))| {
23169 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23170 style
23171 } else {
23172 return Default::default();
23173 };
23174 let mut muted_style = style;
23175 muted_style.highlight(fade_out);
23176
23177 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23178 if range.start >= label.filter_range.end {
23179 if range.start > prev_end {
23180 runs.push((prev_end..range.start, fade_out));
23181 }
23182 runs.push((range.clone(), muted_style));
23183 } else if range.end <= label.filter_range.end {
23184 runs.push((range.clone(), style));
23185 } else {
23186 runs.push((range.start..label.filter_range.end, style));
23187 runs.push((label.filter_range.end..range.end, muted_style));
23188 }
23189 prev_end = cmp::max(prev_end, range.end);
23190
23191 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23192 runs.push((prev_end..label.text.len(), fade_out));
23193 }
23194
23195 runs
23196 })
23197}
23198
23199pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23200 let mut prev_index = 0;
23201 let mut prev_codepoint: Option<char> = None;
23202 text.char_indices()
23203 .chain([(text.len(), '\0')])
23204 .filter_map(move |(index, codepoint)| {
23205 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23206 let is_boundary = index == text.len()
23207 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23208 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23209 if is_boundary {
23210 let chunk = &text[prev_index..index];
23211 prev_index = index;
23212 Some(chunk)
23213 } else {
23214 None
23215 }
23216 })
23217}
23218
23219pub trait RangeToAnchorExt: Sized {
23220 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23221
23222 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23223 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23224 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23225 }
23226}
23227
23228impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23229 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23230 let start_offset = self.start.to_offset(snapshot);
23231 let end_offset = self.end.to_offset(snapshot);
23232 if start_offset == end_offset {
23233 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23234 } else {
23235 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23236 }
23237 }
23238}
23239
23240pub trait RowExt {
23241 fn as_f32(&self) -> f32;
23242
23243 fn next_row(&self) -> Self;
23244
23245 fn previous_row(&self) -> Self;
23246
23247 fn minus(&self, other: Self) -> u32;
23248}
23249
23250impl RowExt for DisplayRow {
23251 fn as_f32(&self) -> f32 {
23252 self.0 as f32
23253 }
23254
23255 fn next_row(&self) -> Self {
23256 Self(self.0 + 1)
23257 }
23258
23259 fn previous_row(&self) -> Self {
23260 Self(self.0.saturating_sub(1))
23261 }
23262
23263 fn minus(&self, other: Self) -> u32 {
23264 self.0 - other.0
23265 }
23266}
23267
23268impl RowExt for MultiBufferRow {
23269 fn as_f32(&self) -> f32 {
23270 self.0 as f32
23271 }
23272
23273 fn next_row(&self) -> Self {
23274 Self(self.0 + 1)
23275 }
23276
23277 fn previous_row(&self) -> Self {
23278 Self(self.0.saturating_sub(1))
23279 }
23280
23281 fn minus(&self, other: Self) -> u32 {
23282 self.0 - other.0
23283 }
23284}
23285
23286trait RowRangeExt {
23287 type Row;
23288
23289 fn len(&self) -> usize;
23290
23291 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23292}
23293
23294impl RowRangeExt for Range<MultiBufferRow> {
23295 type Row = MultiBufferRow;
23296
23297 fn len(&self) -> usize {
23298 (self.end.0 - self.start.0) as usize
23299 }
23300
23301 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23302 (self.start.0..self.end.0).map(MultiBufferRow)
23303 }
23304}
23305
23306impl RowRangeExt for Range<DisplayRow> {
23307 type Row = DisplayRow;
23308
23309 fn len(&self) -> usize {
23310 (self.end.0 - self.start.0) as usize
23311 }
23312
23313 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23314 (self.start.0..self.end.0).map(DisplayRow)
23315 }
23316}
23317
23318/// If select range has more than one line, we
23319/// just point the cursor to range.start.
23320fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23321 if range.start.row == range.end.row {
23322 range
23323 } else {
23324 range.start..range.start
23325 }
23326}
23327pub struct KillRing(ClipboardItem);
23328impl Global for KillRing {}
23329
23330const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23331
23332enum BreakpointPromptEditAction {
23333 Log,
23334 Condition,
23335 HitCondition,
23336}
23337
23338struct BreakpointPromptEditor {
23339 pub(crate) prompt: Entity<Editor>,
23340 editor: WeakEntity<Editor>,
23341 breakpoint_anchor: Anchor,
23342 breakpoint: Breakpoint,
23343 edit_action: BreakpointPromptEditAction,
23344 block_ids: HashSet<CustomBlockId>,
23345 editor_margins: Arc<Mutex<EditorMargins>>,
23346 _subscriptions: Vec<Subscription>,
23347}
23348
23349impl BreakpointPromptEditor {
23350 const MAX_LINES: u8 = 4;
23351
23352 fn new(
23353 editor: WeakEntity<Editor>,
23354 breakpoint_anchor: Anchor,
23355 breakpoint: Breakpoint,
23356 edit_action: BreakpointPromptEditAction,
23357 window: &mut Window,
23358 cx: &mut Context<Self>,
23359 ) -> Self {
23360 let base_text = match edit_action {
23361 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23362 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23363 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23364 }
23365 .map(|msg| msg.to_string())
23366 .unwrap_or_default();
23367
23368 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23369 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23370
23371 let prompt = cx.new(|cx| {
23372 let mut prompt = Editor::new(
23373 EditorMode::AutoHeight {
23374 min_lines: 1,
23375 max_lines: Some(Self::MAX_LINES as usize),
23376 },
23377 buffer,
23378 None,
23379 window,
23380 cx,
23381 );
23382 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23383 prompt.set_show_cursor_when_unfocused(false, cx);
23384 prompt.set_placeholder_text(
23385 match edit_action {
23386 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23387 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23388 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23389 },
23390 cx,
23391 );
23392
23393 prompt
23394 });
23395
23396 Self {
23397 prompt,
23398 editor,
23399 breakpoint_anchor,
23400 breakpoint,
23401 edit_action,
23402 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23403 block_ids: Default::default(),
23404 _subscriptions: vec![],
23405 }
23406 }
23407
23408 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23409 self.block_ids.extend(block_ids)
23410 }
23411
23412 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23413 if let Some(editor) = self.editor.upgrade() {
23414 let message = self
23415 .prompt
23416 .read(cx)
23417 .buffer
23418 .read(cx)
23419 .as_singleton()
23420 .expect("A multi buffer in breakpoint prompt isn't possible")
23421 .read(cx)
23422 .as_rope()
23423 .to_string();
23424
23425 editor.update(cx, |editor, cx| {
23426 editor.edit_breakpoint_at_anchor(
23427 self.breakpoint_anchor,
23428 self.breakpoint.clone(),
23429 match self.edit_action {
23430 BreakpointPromptEditAction::Log => {
23431 BreakpointEditAction::EditLogMessage(message.into())
23432 }
23433 BreakpointPromptEditAction::Condition => {
23434 BreakpointEditAction::EditCondition(message.into())
23435 }
23436 BreakpointPromptEditAction::HitCondition => {
23437 BreakpointEditAction::EditHitCondition(message.into())
23438 }
23439 },
23440 cx,
23441 );
23442
23443 editor.remove_blocks(self.block_ids.clone(), None, cx);
23444 cx.focus_self(window);
23445 });
23446 }
23447 }
23448
23449 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23450 self.editor
23451 .update(cx, |editor, cx| {
23452 editor.remove_blocks(self.block_ids.clone(), None, cx);
23453 window.focus(&editor.focus_handle);
23454 })
23455 .log_err();
23456 }
23457
23458 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23459 let settings = ThemeSettings::get_global(cx);
23460 let text_style = TextStyle {
23461 color: if self.prompt.read(cx).read_only(cx) {
23462 cx.theme().colors().text_disabled
23463 } else {
23464 cx.theme().colors().text
23465 },
23466 font_family: settings.buffer_font.family.clone(),
23467 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23468 font_size: settings.buffer_font_size(cx).into(),
23469 font_weight: settings.buffer_font.weight,
23470 line_height: relative(settings.buffer_line_height.value()),
23471 ..Default::default()
23472 };
23473 EditorElement::new(
23474 &self.prompt,
23475 EditorStyle {
23476 background: cx.theme().colors().editor_background,
23477 local_player: cx.theme().players().local(),
23478 text: text_style,
23479 ..Default::default()
23480 },
23481 )
23482 }
23483}
23484
23485impl Render for BreakpointPromptEditor {
23486 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23487 let editor_margins = *self.editor_margins.lock();
23488 let gutter_dimensions = editor_margins.gutter;
23489 h_flex()
23490 .key_context("Editor")
23491 .bg(cx.theme().colors().editor_background)
23492 .border_y_1()
23493 .border_color(cx.theme().status().info_border)
23494 .size_full()
23495 .py(window.line_height() / 2.5)
23496 .on_action(cx.listener(Self::confirm))
23497 .on_action(cx.listener(Self::cancel))
23498 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23499 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23500 }
23501}
23502
23503impl Focusable for BreakpointPromptEditor {
23504 fn focus_handle(&self, cx: &App) -> FocusHandle {
23505 self.prompt.focus_handle(cx)
23506 }
23507}
23508
23509fn all_edits_insertions_or_deletions(
23510 edits: &Vec<(Range<Anchor>, String)>,
23511 snapshot: &MultiBufferSnapshot,
23512) -> bool {
23513 let mut all_insertions = true;
23514 let mut all_deletions = true;
23515
23516 for (range, new_text) in edits.iter() {
23517 let range_is_empty = range.to_offset(&snapshot).is_empty();
23518 let text_is_empty = new_text.is_empty();
23519
23520 if range_is_empty != text_is_empty {
23521 if range_is_empty {
23522 all_deletions = false;
23523 } else {
23524 all_insertions = false;
23525 }
23526 } else {
23527 return false;
23528 }
23529
23530 if !all_insertions && !all_deletions {
23531 return false;
23532 }
23533 }
23534 all_insertions || all_deletions
23535}
23536
23537struct MissingEditPredictionKeybindingTooltip;
23538
23539impl Render for MissingEditPredictionKeybindingTooltip {
23540 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23541 ui::tooltip_container(window, cx, |container, _, cx| {
23542 container
23543 .flex_shrink_0()
23544 .max_w_80()
23545 .min_h(rems_from_px(124.))
23546 .justify_between()
23547 .child(
23548 v_flex()
23549 .flex_1()
23550 .text_ui_sm(cx)
23551 .child(Label::new("Conflict with Accept Keybinding"))
23552 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23553 )
23554 .child(
23555 h_flex()
23556 .pb_1()
23557 .gap_1()
23558 .items_end()
23559 .w_full()
23560 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23561 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23562 }))
23563 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23564 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23565 })),
23566 )
23567 })
23568 }
23569}
23570
23571#[derive(Debug, Clone, Copy, PartialEq)]
23572pub struct LineHighlight {
23573 pub background: Background,
23574 pub border: Option<gpui::Hsla>,
23575 pub include_gutter: bool,
23576 pub type_id: Option<TypeId>,
23577}
23578
23579struct LineManipulationResult {
23580 pub new_text: String,
23581 pub line_count_before: usize,
23582 pub line_count_after: usize,
23583}
23584
23585fn render_diff_hunk_controls(
23586 row: u32,
23587 status: &DiffHunkStatus,
23588 hunk_range: Range<Anchor>,
23589 is_created_file: bool,
23590 line_height: Pixels,
23591 editor: &Entity<Editor>,
23592 _window: &mut Window,
23593 cx: &mut App,
23594) -> AnyElement {
23595 h_flex()
23596 .h(line_height)
23597 .mr_1()
23598 .gap_1()
23599 .px_0p5()
23600 .pb_1()
23601 .border_x_1()
23602 .border_b_1()
23603 .border_color(cx.theme().colors().border_variant)
23604 .rounded_b_lg()
23605 .bg(cx.theme().colors().editor_background)
23606 .gap_1()
23607 .block_mouse_except_scroll()
23608 .shadow_md()
23609 .child(if status.has_secondary_hunk() {
23610 Button::new(("stage", row as u64), "Stage")
23611 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23612 .tooltip({
23613 let focus_handle = editor.focus_handle(cx);
23614 move |window, cx| {
23615 Tooltip::for_action_in(
23616 "Stage Hunk",
23617 &::git::ToggleStaged,
23618 &focus_handle,
23619 window,
23620 cx,
23621 )
23622 }
23623 })
23624 .on_click({
23625 let editor = editor.clone();
23626 move |_event, _window, cx| {
23627 editor.update(cx, |editor, cx| {
23628 editor.stage_or_unstage_diff_hunks(
23629 true,
23630 vec![hunk_range.start..hunk_range.start],
23631 cx,
23632 );
23633 });
23634 }
23635 })
23636 } else {
23637 Button::new(("unstage", row as u64), "Unstage")
23638 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23639 .tooltip({
23640 let focus_handle = editor.focus_handle(cx);
23641 move |window, cx| {
23642 Tooltip::for_action_in(
23643 "Unstage Hunk",
23644 &::git::ToggleStaged,
23645 &focus_handle,
23646 window,
23647 cx,
23648 )
23649 }
23650 })
23651 .on_click({
23652 let editor = editor.clone();
23653 move |_event, _window, cx| {
23654 editor.update(cx, |editor, cx| {
23655 editor.stage_or_unstage_diff_hunks(
23656 false,
23657 vec![hunk_range.start..hunk_range.start],
23658 cx,
23659 );
23660 });
23661 }
23662 })
23663 })
23664 .child(
23665 Button::new(("restore", row as u64), "Restore")
23666 .tooltip({
23667 let focus_handle = editor.focus_handle(cx);
23668 move |window, cx| {
23669 Tooltip::for_action_in(
23670 "Restore Hunk",
23671 &::git::Restore,
23672 &focus_handle,
23673 window,
23674 cx,
23675 )
23676 }
23677 })
23678 .on_click({
23679 let editor = editor.clone();
23680 move |_event, window, cx| {
23681 editor.update(cx, |editor, cx| {
23682 let snapshot = editor.snapshot(window, cx);
23683 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23684 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23685 });
23686 }
23687 })
23688 .disabled(is_created_file),
23689 )
23690 .when(
23691 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23692 |el| {
23693 el.child(
23694 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23695 .shape(IconButtonShape::Square)
23696 .icon_size(IconSize::Small)
23697 // .disabled(!has_multiple_hunks)
23698 .tooltip({
23699 let focus_handle = editor.focus_handle(cx);
23700 move |window, cx| {
23701 Tooltip::for_action_in(
23702 "Next Hunk",
23703 &GoToHunk,
23704 &focus_handle,
23705 window,
23706 cx,
23707 )
23708 }
23709 })
23710 .on_click({
23711 let editor = editor.clone();
23712 move |_event, window, cx| {
23713 editor.update(cx, |editor, cx| {
23714 let snapshot = editor.snapshot(window, cx);
23715 let position =
23716 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23717 editor.go_to_hunk_before_or_after_position(
23718 &snapshot,
23719 position,
23720 Direction::Next,
23721 window,
23722 cx,
23723 );
23724 editor.expand_selected_diff_hunks(cx);
23725 });
23726 }
23727 }),
23728 )
23729 .child(
23730 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23731 .shape(IconButtonShape::Square)
23732 .icon_size(IconSize::Small)
23733 // .disabled(!has_multiple_hunks)
23734 .tooltip({
23735 let focus_handle = editor.focus_handle(cx);
23736 move |window, cx| {
23737 Tooltip::for_action_in(
23738 "Previous Hunk",
23739 &GoToPreviousHunk,
23740 &focus_handle,
23741 window,
23742 cx,
23743 )
23744 }
23745 })
23746 .on_click({
23747 let editor = editor.clone();
23748 move |_event, window, cx| {
23749 editor.update(cx, |editor, cx| {
23750 let snapshot = editor.snapshot(window, cx);
23751 let point =
23752 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23753 editor.go_to_hunk_before_or_after_position(
23754 &snapshot,
23755 point,
23756 Direction::Prev,
23757 window,
23758 cx,
23759 );
23760 editor.expand_selected_diff_hunks(cx);
23761 });
23762 }
23763 }),
23764 )
23765 },
23766 )
23767 .into_any_element()
23768}