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, DisableAiSettings, 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.
1308#[derive(Clone)]
1309pub struct SelectionEffects {
1310 nav_history: Option<bool>,
1311 completions: bool,
1312 scroll: Option<Autoscroll>,
1313}
1314
1315impl Default for SelectionEffects {
1316 fn default() -> Self {
1317 Self {
1318 nav_history: None,
1319 completions: true,
1320 scroll: Some(Autoscroll::fit()),
1321 }
1322 }
1323}
1324impl SelectionEffects {
1325 pub fn scroll(scroll: Autoscroll) -> Self {
1326 Self {
1327 scroll: Some(scroll),
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn no_scroll() -> Self {
1333 Self {
1334 scroll: None,
1335 ..Default::default()
1336 }
1337 }
1338
1339 pub fn completions(self, completions: bool) -> Self {
1340 Self {
1341 completions,
1342 ..self
1343 }
1344 }
1345
1346 pub fn nav_history(self, nav_history: bool) -> Self {
1347 Self {
1348 nav_history: Some(nav_history),
1349 ..self
1350 }
1351 }
1352}
1353
1354struct DeferredSelectionEffectsState {
1355 changed: bool,
1356 effects: SelectionEffects,
1357 old_cursor_position: Anchor,
1358 history_entry: SelectionHistoryEntry,
1359}
1360
1361#[derive(Default)]
1362struct SelectionHistory {
1363 #[allow(clippy::type_complexity)]
1364 selections_by_transaction:
1365 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1366 mode: SelectionHistoryMode,
1367 undo_stack: VecDeque<SelectionHistoryEntry>,
1368 redo_stack: VecDeque<SelectionHistoryEntry>,
1369}
1370
1371impl SelectionHistory {
1372 #[track_caller]
1373 fn insert_transaction(
1374 &mut self,
1375 transaction_id: TransactionId,
1376 selections: Arc<[Selection<Anchor>]>,
1377 ) {
1378 if selections.is_empty() {
1379 log::error!(
1380 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1381 std::panic::Location::caller()
1382 );
1383 return;
1384 }
1385 self.selections_by_transaction
1386 .insert(transaction_id, (selections, None));
1387 }
1388
1389 #[allow(clippy::type_complexity)]
1390 fn transaction(
1391 &self,
1392 transaction_id: TransactionId,
1393 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1394 self.selections_by_transaction.get(&transaction_id)
1395 }
1396
1397 #[allow(clippy::type_complexity)]
1398 fn transaction_mut(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1402 self.selections_by_transaction.get_mut(&transaction_id)
1403 }
1404
1405 fn push(&mut self, entry: SelectionHistoryEntry) {
1406 if !entry.selections.is_empty() {
1407 match self.mode {
1408 SelectionHistoryMode::Normal => {
1409 self.push_undo(entry);
1410 self.redo_stack.clear();
1411 }
1412 SelectionHistoryMode::Undoing => self.push_redo(entry),
1413 SelectionHistoryMode::Redoing => self.push_undo(entry),
1414 SelectionHistoryMode::Skipping => {}
1415 }
1416 }
1417 }
1418
1419 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1420 if self
1421 .undo_stack
1422 .back()
1423 .map_or(true, |e| e.selections != entry.selections)
1424 {
1425 self.undo_stack.push_back(entry);
1426 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1427 self.undo_stack.pop_front();
1428 }
1429 }
1430 }
1431
1432 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1433 if self
1434 .redo_stack
1435 .back()
1436 .map_or(true, |e| e.selections != entry.selections)
1437 {
1438 self.redo_stack.push_back(entry);
1439 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1440 self.redo_stack.pop_front();
1441 }
1442 }
1443 }
1444}
1445
1446#[derive(Clone, Copy)]
1447pub struct RowHighlightOptions {
1448 pub autoscroll: bool,
1449 pub include_gutter: bool,
1450}
1451
1452impl Default for RowHighlightOptions {
1453 fn default() -> Self {
1454 Self {
1455 autoscroll: Default::default(),
1456 include_gutter: true,
1457 }
1458 }
1459}
1460
1461struct RowHighlight {
1462 index: usize,
1463 range: Range<Anchor>,
1464 color: Hsla,
1465 options: RowHighlightOptions,
1466 type_id: TypeId,
1467}
1468
1469#[derive(Clone, Debug)]
1470struct AddSelectionsState {
1471 groups: Vec<AddSelectionsGroup>,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsGroup {
1476 above: bool,
1477 stack: Vec<usize>,
1478}
1479
1480#[derive(Clone)]
1481struct SelectNextState {
1482 query: AhoCorasick,
1483 wordwise: bool,
1484 done: bool,
1485}
1486
1487impl std::fmt::Debug for SelectNextState {
1488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1489 f.debug_struct(std::any::type_name::<Self>())
1490 .field("wordwise", &self.wordwise)
1491 .field("done", &self.done)
1492 .finish()
1493 }
1494}
1495
1496#[derive(Debug)]
1497struct AutocloseRegion {
1498 selection_id: usize,
1499 range: Range<Anchor>,
1500 pair: BracketPair,
1501}
1502
1503#[derive(Debug)]
1504struct SnippetState {
1505 ranges: Vec<Vec<Range<Anchor>>>,
1506 active_index: usize,
1507 choices: Vec<Option<Vec<String>>>,
1508}
1509
1510#[doc(hidden)]
1511pub struct RenameState {
1512 pub range: Range<Anchor>,
1513 pub old_name: Arc<str>,
1514 pub editor: Entity<Editor>,
1515 block_id: CustomBlockId,
1516}
1517
1518struct InvalidationStack<T>(Vec<T>);
1519
1520struct RegisteredInlineCompletionProvider {
1521 provider: Arc<dyn InlineCompletionProviderHandle>,
1522 _subscription: Subscription,
1523}
1524
1525#[derive(Debug, PartialEq, Eq)]
1526pub struct ActiveDiagnosticGroup {
1527 pub active_range: Range<Anchor>,
1528 pub active_message: String,
1529 pub group_id: usize,
1530 pub blocks: HashSet<CustomBlockId>,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1534
1535pub(crate) enum ActiveDiagnostic {
1536 None,
1537 All,
1538 Group(ActiveDiagnosticGroup),
1539}
1540
1541#[derive(Serialize, Deserialize, Clone, Debug)]
1542pub struct ClipboardSelection {
1543 /// The number of bytes in this selection.
1544 pub len: usize,
1545 /// Whether this was a full-line selection.
1546 pub is_entire_line: bool,
1547 /// The indentation of the first line when this content was originally copied.
1548 pub first_line_indent: u32,
1549}
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<usize>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603#[derive(Debug, Clone)]
1604enum InlayHintRefreshReason {
1605 ModifiersChanged(bool),
1606 Toggle(bool),
1607 SettingsChange(InlayHintSettings),
1608 NewLinesShown,
1609 BufferEdited(HashSet<Arc<Language>>),
1610 RefreshRequested,
1611 ExcerptsRemoved(Vec<ExcerptId>),
1612}
1613
1614impl InlayHintRefreshReason {
1615 fn description(&self) -> &'static str {
1616 match self {
1617 Self::ModifiersChanged(_) => "modifiers changed",
1618 Self::Toggle(_) => "toggle",
1619 Self::SettingsChange(_) => "settings change",
1620 Self::NewLinesShown => "new lines shown",
1621 Self::BufferEdited(_) => "buffer edited",
1622 Self::RefreshRequested => "refresh requested",
1623 Self::ExcerptsRemoved(_) => "excerpts removed",
1624 }
1625 }
1626}
1627
1628pub enum FormatTarget {
1629 Buffers(HashSet<Entity<Buffer>>),
1630 Ranges(Vec<Range<MultiBufferPoint>>),
1631}
1632
1633pub(crate) struct FocusedBlock {
1634 id: BlockId,
1635 focus_handle: WeakFocusHandle,
1636}
1637
1638#[derive(Clone)]
1639enum JumpData {
1640 MultiBufferRow {
1641 row: MultiBufferRow,
1642 line_offset_from_top: u32,
1643 },
1644 MultiBufferPoint {
1645 excerpt_id: ExcerptId,
1646 position: Point,
1647 anchor: text::Anchor,
1648 line_offset_from_top: u32,
1649 },
1650}
1651
1652pub enum MultibufferSelectionMode {
1653 First,
1654 All,
1655}
1656
1657#[derive(Clone, Copy, Debug, Default)]
1658pub struct RewrapOptions {
1659 pub override_language_settings: bool,
1660 pub preserve_existing_whitespace: bool,
1661}
1662
1663impl Editor {
1664 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1665 let buffer = cx.new(|cx| Buffer::local("", cx));
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1668 }
1669
1670 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::full(), buffer, None, window, cx)
1674 }
1675
1676 pub fn auto_height(
1677 min_lines: usize,
1678 max_lines: usize,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(
1685 EditorMode::AutoHeight {
1686 min_lines,
1687 max_lines: Some(max_lines),
1688 },
1689 buffer,
1690 None,
1691 window,
1692 cx,
1693 )
1694 }
1695
1696 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1697 /// The editor grows as tall as needed to fit its content.
1698 pub fn auto_height_unbounded(
1699 min_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: None,
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 pub fn for_buffer(
1718 buffer: Entity<Buffer>,
1719 project: Option<Entity<Project>>,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(EditorMode::full(), buffer, project, window, cx)
1725 }
1726
1727 pub fn for_multibuffer(
1728 buffer: Entity<MultiBuffer>,
1729 project: Option<Entity<Project>>,
1730 window: &mut Window,
1731 cx: &mut Context<Self>,
1732 ) -> Self {
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let mut clone = Self::new(
1738 self.mode.clone(),
1739 self.buffer.clone(),
1740 self.project.clone(),
1741 window,
1742 cx,
1743 );
1744 self.display_map.update(cx, |display_map, cx| {
1745 let snapshot = display_map.snapshot(cx);
1746 clone.display_map.update(cx, |display_map, cx| {
1747 display_map.set_state(&snapshot, cx);
1748 });
1749 });
1750 clone.folds_did_change(cx);
1751 clone.selections.clone_state(&self.selections);
1752 clone.scroll_manager.clone_state(&self.scroll_manager);
1753 clone.searchable = self.searchable;
1754 clone.read_only = self.read_only;
1755 clone
1756 }
1757
1758 pub fn new(
1759 mode: EditorMode,
1760 buffer: Entity<MultiBuffer>,
1761 project: Option<Entity<Project>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 Editor::new_internal(mode, buffer, project, None, window, cx)
1766 }
1767
1768 fn new_internal(
1769 mode: EditorMode,
1770 buffer: Entity<MultiBuffer>,
1771 project: Option<Entity<Project>>,
1772 display_map: Option<Entity<DisplayMap>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 debug_assert!(
1777 display_map.is_none() || mode.is_minimap(),
1778 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1779 );
1780
1781 let full_mode = mode.is_full();
1782 let is_minimap = mode.is_minimap();
1783 let diagnostics_max_severity = if full_mode {
1784 EditorSettings::get_global(cx)
1785 .diagnostics_max_severity
1786 .unwrap_or(DiagnosticSeverity::Hint)
1787 } else {
1788 DiagnosticSeverity::Off
1789 };
1790 let style = window.text_style();
1791 let font_size = style.font_size.to_pixels(window.rem_size());
1792 let editor = cx.entity().downgrade();
1793 let fold_placeholder = FoldPlaceholder {
1794 constrain_width: true,
1795 render: Arc::new(move |fold_id, fold_range, cx| {
1796 let editor = editor.clone();
1797 div()
1798 .id(fold_id)
1799 .bg(cx.theme().colors().ghost_element_background)
1800 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1801 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1802 .rounded_xs()
1803 .size_full()
1804 .cursor_pointer()
1805 .child("⋯")
1806 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1807 .on_click(move |_, _window, cx| {
1808 editor
1809 .update(cx, |editor, cx| {
1810 editor.unfold_ranges(
1811 &[fold_range.start..fold_range.end],
1812 true,
1813 false,
1814 cx,
1815 );
1816 cx.stop_propagation();
1817 })
1818 .ok();
1819 })
1820 .into_any()
1821 }),
1822 merge_adjacent: true,
1823 ..FoldPlaceholder::default()
1824 };
1825 let display_map = display_map.unwrap_or_else(|| {
1826 cx.new(|cx| {
1827 DisplayMap::new(
1828 buffer.clone(),
1829 style.font(),
1830 font_size,
1831 None,
1832 FILE_HEADER_HEIGHT,
1833 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1834 fold_placeholder,
1835 diagnostics_max_severity,
1836 cx,
1837 )
1838 })
1839 });
1840
1841 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1842
1843 let blink_manager = cx.new(|cx| {
1844 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1845 if is_minimap {
1846 blink_manager.disable(cx);
1847 }
1848 blink_manager
1849 });
1850
1851 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1852 .then(|| language_settings::SoftWrap::None);
1853
1854 let mut project_subscriptions = Vec::new();
1855 if full_mode {
1856 if let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor
1866 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1867 }
1868 project::Event::LanguageServerAdded(..)
1869 | project::Event::LanguageServerRemoved(..) => {
1870 if editor.tasks_update_task.is_none() {
1871 editor.tasks_update_task =
1872 Some(editor.refresh_runnables(window, cx));
1873 }
1874 editor.update_lsp_data(true, None, window, cx);
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 _ => {}
1897 },
1898 ));
1899 if let Some(task_inventory) = project
1900 .read(cx)
1901 .task_store()
1902 .read(cx)
1903 .task_inventory()
1904 .cloned()
1905 {
1906 project_subscriptions.push(cx.observe_in(
1907 &task_inventory,
1908 window,
1909 |editor, _, window, cx| {
1910 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1911 },
1912 ));
1913 };
1914
1915 project_subscriptions.push(cx.subscribe_in(
1916 &project.read(cx).breakpoint_store(),
1917 window,
1918 |editor, _, event, window, cx| match event {
1919 BreakpointStoreEvent::ClearDebugLines => {
1920 editor.clear_row_highlights::<ActiveDebugLine>();
1921 editor.refresh_inline_values(cx);
1922 }
1923 BreakpointStoreEvent::SetDebugLine => {
1924 if editor.go_to_active_debug_line(window, cx) {
1925 cx.stop_propagation();
1926 }
1927
1928 editor.refresh_inline_values(cx);
1929 }
1930 _ => {}
1931 },
1932 ));
1933 let git_store = project.read(cx).git_store().clone();
1934 let project = project.clone();
1935 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1936 match event {
1937 GitStoreEvent::RepositoryUpdated(
1938 _,
1939 RepositoryEvent::Updated {
1940 new_instance: true, ..
1941 },
1942 _,
1943 ) => {
1944 this.load_diff_task = Some(
1945 update_uncommitted_diff_for_buffer(
1946 cx.entity(),
1947 &project,
1948 this.buffer.read(cx).all_buffers(),
1949 this.buffer.clone(),
1950 cx,
1951 )
1952 .shared(),
1953 );
1954 }
1955 _ => {}
1956 }
1957 }));
1958 }
1959 }
1960
1961 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1962
1963 let inlay_hint_settings =
1964 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1965 let focus_handle = cx.focus_handle();
1966 if !is_minimap {
1967 cx.on_focus(&focus_handle, window, Self::handle_focus)
1968 .detach();
1969 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1970 .detach();
1971 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1972 .detach();
1973 cx.on_blur(&focus_handle, window, Self::handle_blur)
1974 .detach();
1975 cx.observe_pending_input(window, Self::observe_pending_input)
1976 .detach();
1977 }
1978
1979 let show_indent_guides = if matches!(
1980 mode,
1981 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1982 ) {
1983 Some(false)
1984 } else {
1985 None
1986 };
1987
1988 let breakpoint_store = match (&mode, project.as_ref()) {
1989 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1990 _ => None,
1991 };
1992
1993 let mut code_action_providers = Vec::new();
1994 let mut load_uncommitted_diff = None;
1995 if let Some(project) = project.clone() {
1996 load_uncommitted_diff = Some(
1997 update_uncommitted_diff_for_buffer(
1998 cx.entity(),
1999 &project,
2000 buffer.read(cx).all_buffers(),
2001 buffer.clone(),
2002 cx,
2003 )
2004 .shared(),
2005 );
2006 code_action_providers.push(Rc::new(project) as Rc<_>);
2007 }
2008
2009 let mut editor = Self {
2010 focus_handle,
2011 show_cursor_when_unfocused: false,
2012 last_focused_descendant: None,
2013 buffer: buffer.clone(),
2014 display_map: display_map.clone(),
2015 selections,
2016 scroll_manager: ScrollManager::new(cx),
2017 columnar_selection_state: None,
2018 add_selections_state: None,
2019 select_next_state: None,
2020 select_prev_state: None,
2021 selection_history: SelectionHistory::default(),
2022 defer_selection_effects: false,
2023 deferred_selection_effects_state: None,
2024 autoclose_regions: Vec::new(),
2025 snippet_stack: InvalidationStack::default(),
2026 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2027 ime_transaction: None,
2028 active_diagnostics: ActiveDiagnostic::None,
2029 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2030 inline_diagnostics_update: Task::ready(()),
2031 inline_diagnostics: Vec::new(),
2032 soft_wrap_mode_override,
2033 diagnostics_max_severity,
2034 hard_wrap: None,
2035 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2036 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2037 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2038 project,
2039 blink_manager: blink_manager.clone(),
2040 show_local_selections: true,
2041 show_scrollbars: ScrollbarAxes {
2042 horizontal: full_mode,
2043 vertical: full_mode,
2044 },
2045 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2046 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2047 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2048 show_gutter: full_mode,
2049 show_line_numbers: (!full_mode).then_some(false),
2050 use_relative_line_numbers: None,
2051 disable_expand_excerpt_buttons: !full_mode,
2052 show_git_diff_gutter: None,
2053 show_code_actions: None,
2054 show_runnables: None,
2055 show_breakpoints: None,
2056 show_wrap_guides: None,
2057 show_indent_guides,
2058 placeholder_text: None,
2059 highlight_order: 0,
2060 highlighted_rows: HashMap::default(),
2061 background_highlights: TreeMap::default(),
2062 gutter_highlights: TreeMap::default(),
2063 scrollbar_marker_state: ScrollbarMarkerState::default(),
2064 active_indent_guides_state: ActiveIndentGuidesState::default(),
2065 nav_history: None,
2066 context_menu: RefCell::new(None),
2067 context_menu_options: None,
2068 mouse_context_menu: None,
2069 completion_tasks: Vec::new(),
2070 inline_blame_popover: None,
2071 inline_blame_popover_show_task: None,
2072 signature_help_state: SignatureHelpState::default(),
2073 auto_signature_help: None,
2074 find_all_references_task_sources: Vec::new(),
2075 next_completion_id: 0,
2076 next_inlay_id: 0,
2077 code_action_providers,
2078 available_code_actions: None,
2079 code_actions_task: None,
2080 quick_selection_highlight_task: None,
2081 debounced_selection_highlight_task: None,
2082 document_highlights_task: None,
2083 linked_editing_range_task: None,
2084 pending_rename: None,
2085 searchable: !is_minimap,
2086 cursor_shape: EditorSettings::get_global(cx)
2087 .cursor_shape
2088 .unwrap_or_default(),
2089 current_line_highlight: None,
2090 autoindent_mode: Some(AutoindentMode::EachLine),
2091 collapse_matches: false,
2092 workspace: None,
2093 input_enabled: !is_minimap,
2094 use_modal_editing: full_mode,
2095 read_only: is_minimap,
2096 use_autoclose: true,
2097 use_auto_surround: true,
2098 auto_replace_emoji_shortcode: false,
2099 jsx_tag_auto_close_enabled_in_any_buffer: false,
2100 leader_id: None,
2101 remote_id: None,
2102 hover_state: HoverState::default(),
2103 pending_mouse_down: None,
2104 hovered_link_state: None,
2105 edit_prediction_provider: None,
2106 active_inline_completion: None,
2107 stale_inline_completion_in_menu: None,
2108 edit_prediction_preview: EditPredictionPreview::Inactive {
2109 released_too_fast: false,
2110 },
2111 inline_diagnostics_enabled: full_mode,
2112 diagnostics_enabled: full_mode,
2113 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2114 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2115 gutter_hovered: false,
2116 pixel_position_of_newest_cursor: None,
2117 last_bounds: None,
2118 last_position_map: None,
2119 expect_bounds_change: None,
2120 gutter_dimensions: GutterDimensions::default(),
2121 style: None,
2122 show_cursor_names: false,
2123 hovered_cursors: HashMap::default(),
2124 next_editor_action_id: EditorActionId::default(),
2125 editor_actions: Rc::default(),
2126 inline_completions_hidden_for_vim_mode: false,
2127 show_inline_completions_override: None,
2128 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2129 edit_prediction_settings: EditPredictionSettings::Disabled,
2130 edit_prediction_indent_conflict: false,
2131 edit_prediction_requires_modifier_in_indent_conflict: true,
2132 custom_context_menu: None,
2133 show_git_blame_gutter: false,
2134 show_git_blame_inline: false,
2135 show_selection_menu: None,
2136 show_git_blame_inline_delay_task: None,
2137 git_blame_inline_enabled: full_mode
2138 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2139 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2140 serialize_dirty_buffers: !is_minimap
2141 && ProjectSettings::get_global(cx)
2142 .session
2143 .restore_unsaved_buffers,
2144 blame: None,
2145 blame_subscription: None,
2146 tasks: BTreeMap::default(),
2147
2148 breakpoint_store,
2149 gutter_breakpoint_indicator: (None, None),
2150 hovered_diff_hunk_row: None,
2151 _subscriptions: (!is_minimap)
2152 .then(|| {
2153 vec![
2154 cx.observe(&buffer, Self::on_buffer_changed),
2155 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2156 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2157 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2158 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2159 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2160 cx.observe_window_activation(window, |editor, window, cx| {
2161 let active = window.is_window_active();
2162 editor.blink_manager.update(cx, |blink_manager, cx| {
2163 if active {
2164 blink_manager.enable(cx);
2165 } else {
2166 blink_manager.disable(cx);
2167 }
2168 });
2169 if active {
2170 editor.show_mouse_cursor(cx);
2171 }
2172 }),
2173 ]
2174 })
2175 .unwrap_or_default(),
2176 tasks_update_task: None,
2177 pull_diagnostics_task: Task::ready(()),
2178 colors: None,
2179 next_color_inlay_id: 0,
2180 linked_edit_ranges: Default::default(),
2181 in_project_search: false,
2182 previous_search_ranges: None,
2183 breadcrumb_header: None,
2184 focused_block: None,
2185 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2186 addons: HashMap::default(),
2187 registered_buffers: HashMap::default(),
2188 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2189 selection_mark_mode: false,
2190 toggle_fold_multiple_buffers: Task::ready(()),
2191 serialize_selections: Task::ready(()),
2192 serialize_folds: Task::ready(()),
2193 text_style_refinement: None,
2194 load_diff_task: load_uncommitted_diff,
2195 temporary_diff_override: false,
2196 mouse_cursor_hidden: false,
2197 minimap: None,
2198 hide_mouse_mode: EditorSettings::get_global(cx)
2199 .hide_mouse
2200 .unwrap_or_default(),
2201 change_list: ChangeList::new(),
2202 mode,
2203 selection_drag_state: SelectionDragState::None,
2204 folding_newlines: Task::ready(()),
2205 };
2206
2207 if is_minimap {
2208 return editor;
2209 }
2210
2211 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2212 editor
2213 ._subscriptions
2214 .push(cx.observe(breakpoints, |_, _, cx| {
2215 cx.notify();
2216 }));
2217 }
2218 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2219 editor._subscriptions.extend(project_subscriptions);
2220
2221 editor._subscriptions.push(cx.subscribe_in(
2222 &cx.entity(),
2223 window,
2224 |editor, _, e: &EditorEvent, window, cx| match e {
2225 EditorEvent::ScrollPositionChanged { local, .. } => {
2226 if *local {
2227 let new_anchor = editor.scroll_manager.anchor();
2228 let snapshot = editor.snapshot(window, cx);
2229 editor.update_restoration_data(cx, move |data| {
2230 data.scroll_position = (
2231 new_anchor.top_row(&snapshot.buffer_snapshot),
2232 new_anchor.offset,
2233 );
2234 });
2235 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2236 editor.inline_blame_popover.take();
2237 }
2238 }
2239 EditorEvent::Edited { .. } => {
2240 if !vim_enabled(cx) {
2241 let (map, selections) = editor.selections.all_adjusted_display(cx);
2242 let pop_state = editor
2243 .change_list
2244 .last()
2245 .map(|previous| {
2246 previous.len() == selections.len()
2247 && previous.iter().enumerate().all(|(ix, p)| {
2248 p.to_display_point(&map).row()
2249 == selections[ix].head().row()
2250 })
2251 })
2252 .unwrap_or(false);
2253 let new_positions = selections
2254 .into_iter()
2255 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2256 .collect();
2257 editor
2258 .change_list
2259 .push_to_change_list(pop_state, new_positions);
2260 }
2261 }
2262 _ => (),
2263 },
2264 ));
2265
2266 if let Some(dap_store) = editor
2267 .project
2268 .as_ref()
2269 .map(|project| project.read(cx).dap_store())
2270 {
2271 let weak_editor = cx.weak_entity();
2272
2273 editor
2274 ._subscriptions
2275 .push(
2276 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2277 let session_entity = cx.entity();
2278 weak_editor
2279 .update(cx, |editor, cx| {
2280 editor._subscriptions.push(
2281 cx.subscribe(&session_entity, Self::on_debug_session_event),
2282 );
2283 })
2284 .ok();
2285 }),
2286 );
2287
2288 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2289 editor
2290 ._subscriptions
2291 .push(cx.subscribe(&session, Self::on_debug_session_event));
2292 }
2293 }
2294
2295 // skip adding the initial selection to selection history
2296 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2297 editor.end_selection(window, cx);
2298 editor.selection_history.mode = SelectionHistoryMode::Normal;
2299
2300 editor.scroll_manager.show_scrollbars(window, cx);
2301 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2302
2303 if full_mode {
2304 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2305 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2306
2307 if editor.git_blame_inline_enabled {
2308 editor.start_git_blame_inline(false, window, cx);
2309 }
2310
2311 editor.go_to_active_debug_line(window, cx);
2312
2313 if let Some(buffer) = buffer.read(cx).as_singleton() {
2314 if let Some(project) = editor.project.as_ref() {
2315 let handle = project.update(cx, |project, cx| {
2316 project.register_buffer_with_language_servers(&buffer, cx)
2317 });
2318 editor
2319 .registered_buffers
2320 .insert(buffer.read(cx).remote_id(), handle);
2321 }
2322 }
2323
2324 editor.minimap =
2325 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2326 editor.colors = Some(LspColorData::new(cx));
2327 editor.update_lsp_data(false, None, window, cx);
2328 }
2329
2330 if editor.mode.is_full() {
2331 editor.report_editor_event("Editor Opened", None, cx);
2332 }
2333
2334 editor
2335 }
2336
2337 pub fn deploy_mouse_context_menu(
2338 &mut self,
2339 position: gpui::Point<Pixels>,
2340 context_menu: Entity<ContextMenu>,
2341 window: &mut Window,
2342 cx: &mut Context<Self>,
2343 ) {
2344 self.mouse_context_menu = Some(MouseContextMenu::new(
2345 self,
2346 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2347 context_menu,
2348 window,
2349 cx,
2350 ));
2351 }
2352
2353 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2354 self.mouse_context_menu
2355 .as_ref()
2356 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2357 }
2358
2359 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2360 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2361 }
2362
2363 fn key_context_internal(
2364 &self,
2365 has_active_edit_prediction: bool,
2366 window: &Window,
2367 cx: &App,
2368 ) -> KeyContext {
2369 let mut key_context = KeyContext::new_with_defaults();
2370 key_context.add("Editor");
2371 let mode = match self.mode {
2372 EditorMode::SingleLine { .. } => "single_line",
2373 EditorMode::AutoHeight { .. } => "auto_height",
2374 EditorMode::Minimap { .. } => "minimap",
2375 EditorMode::Full { .. } => "full",
2376 };
2377
2378 if EditorSettings::jupyter_enabled(cx) {
2379 key_context.add("jupyter");
2380 }
2381
2382 key_context.set("mode", mode);
2383 if self.pending_rename.is_some() {
2384 key_context.add("renaming");
2385 }
2386
2387 match self.context_menu.borrow().as_ref() {
2388 Some(CodeContextMenu::Completions(menu)) => {
2389 if menu.visible() {
2390 key_context.add("menu");
2391 key_context.add("showing_completions");
2392 }
2393 }
2394 Some(CodeContextMenu::CodeActions(menu)) => {
2395 if menu.visible() {
2396 key_context.add("menu");
2397 key_context.add("showing_code_actions")
2398 }
2399 }
2400 None => {}
2401 }
2402
2403 if self.signature_help_state.has_multiple_signatures() {
2404 key_context.add("showing_signature_help");
2405 }
2406
2407 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2408 if !self.focus_handle(cx).contains_focused(window, cx)
2409 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2410 {
2411 for addon in self.addons.values() {
2412 addon.extend_key_context(&mut key_context, cx)
2413 }
2414 }
2415
2416 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2417 if let Some(extension) = singleton_buffer
2418 .read(cx)
2419 .file()
2420 .and_then(|file| file.path().extension()?.to_str())
2421 {
2422 key_context.set("extension", extension.to_string());
2423 }
2424 } else {
2425 key_context.add("multibuffer");
2426 }
2427
2428 if has_active_edit_prediction {
2429 if self.edit_prediction_in_conflict() {
2430 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2431 } else {
2432 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2433 key_context.add("copilot_suggestion");
2434 }
2435 }
2436
2437 if self.selection_mark_mode {
2438 key_context.add("selection_mode");
2439 }
2440
2441 key_context
2442 }
2443
2444 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2445 if self.mouse_cursor_hidden {
2446 self.mouse_cursor_hidden = false;
2447 cx.notify();
2448 }
2449 }
2450
2451 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2452 let hide_mouse_cursor = match origin {
2453 HideMouseCursorOrigin::TypingAction => {
2454 matches!(
2455 self.hide_mouse_mode,
2456 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2457 )
2458 }
2459 HideMouseCursorOrigin::MovementAction => {
2460 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2461 }
2462 };
2463 if self.mouse_cursor_hidden != hide_mouse_cursor {
2464 self.mouse_cursor_hidden = hide_mouse_cursor;
2465 cx.notify();
2466 }
2467 }
2468
2469 pub fn edit_prediction_in_conflict(&self) -> bool {
2470 if !self.show_edit_predictions_in_menu() {
2471 return false;
2472 }
2473
2474 let showing_completions = self
2475 .context_menu
2476 .borrow()
2477 .as_ref()
2478 .map_or(false, |context| {
2479 matches!(context, CodeContextMenu::Completions(_))
2480 });
2481
2482 showing_completions
2483 || self.edit_prediction_requires_modifier()
2484 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2485 // bindings to insert tab characters.
2486 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2487 }
2488
2489 pub fn accept_edit_prediction_keybind(
2490 &self,
2491 accept_partial: bool,
2492 window: &Window,
2493 cx: &App,
2494 ) -> AcceptEditPredictionBinding {
2495 let key_context = self.key_context_internal(true, window, cx);
2496 let in_conflict = self.edit_prediction_in_conflict();
2497
2498 let bindings = if accept_partial {
2499 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2500 } else {
2501 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2502 };
2503
2504 // TODO: if the binding contains multiple keystrokes, display all of them, not
2505 // just the first one.
2506 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2507 !in_conflict
2508 || binding
2509 .keystrokes()
2510 .first()
2511 .map_or(false, |keystroke| keystroke.modifiers.modified())
2512 }))
2513 }
2514
2515 pub fn new_file(
2516 workspace: &mut Workspace,
2517 _: &workspace::NewFile,
2518 window: &mut Window,
2519 cx: &mut Context<Workspace>,
2520 ) {
2521 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2522 "Failed to create buffer",
2523 window,
2524 cx,
2525 |e, _, _| match e.error_code() {
2526 ErrorCode::RemoteUpgradeRequired => Some(format!(
2527 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2528 e.error_tag("required").unwrap_or("the latest version")
2529 )),
2530 _ => None,
2531 },
2532 );
2533 }
2534
2535 pub fn new_in_workspace(
2536 workspace: &mut Workspace,
2537 window: &mut Window,
2538 cx: &mut Context<Workspace>,
2539 ) -> Task<Result<Entity<Editor>>> {
2540 let project = workspace.project().clone();
2541 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2542
2543 cx.spawn_in(window, async move |workspace, cx| {
2544 let buffer = create.await?;
2545 workspace.update_in(cx, |workspace, window, cx| {
2546 let editor =
2547 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2548 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2549 editor
2550 })
2551 })
2552 }
2553
2554 fn new_file_vertical(
2555 workspace: &mut Workspace,
2556 _: &workspace::NewFileSplitVertical,
2557 window: &mut Window,
2558 cx: &mut Context<Workspace>,
2559 ) {
2560 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2561 }
2562
2563 fn new_file_horizontal(
2564 workspace: &mut Workspace,
2565 _: &workspace::NewFileSplitHorizontal,
2566 window: &mut Window,
2567 cx: &mut Context<Workspace>,
2568 ) {
2569 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2570 }
2571
2572 fn new_file_in_direction(
2573 workspace: &mut Workspace,
2574 direction: SplitDirection,
2575 window: &mut Window,
2576 cx: &mut Context<Workspace>,
2577 ) {
2578 let project = workspace.project().clone();
2579 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2580
2581 cx.spawn_in(window, async move |workspace, cx| {
2582 let buffer = create.await?;
2583 workspace.update_in(cx, move |workspace, window, cx| {
2584 workspace.split_item(
2585 direction,
2586 Box::new(
2587 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2588 ),
2589 window,
2590 cx,
2591 )
2592 })?;
2593 anyhow::Ok(())
2594 })
2595 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2596 match e.error_code() {
2597 ErrorCode::RemoteUpgradeRequired => Some(format!(
2598 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2599 e.error_tag("required").unwrap_or("the latest version")
2600 )),
2601 _ => None,
2602 }
2603 });
2604 }
2605
2606 pub fn leader_id(&self) -> Option<CollaboratorId> {
2607 self.leader_id
2608 }
2609
2610 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2611 &self.buffer
2612 }
2613
2614 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2615 self.workspace.as_ref()?.0.upgrade()
2616 }
2617
2618 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2619 self.buffer().read(cx).title(cx)
2620 }
2621
2622 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2623 let git_blame_gutter_max_author_length = self
2624 .render_git_blame_gutter(cx)
2625 .then(|| {
2626 if let Some(blame) = self.blame.as_ref() {
2627 let max_author_length =
2628 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2629 Some(max_author_length)
2630 } else {
2631 None
2632 }
2633 })
2634 .flatten();
2635
2636 EditorSnapshot {
2637 mode: self.mode.clone(),
2638 show_gutter: self.show_gutter,
2639 show_line_numbers: self.show_line_numbers,
2640 show_git_diff_gutter: self.show_git_diff_gutter,
2641 show_code_actions: self.show_code_actions,
2642 show_runnables: self.show_runnables,
2643 show_breakpoints: self.show_breakpoints,
2644 git_blame_gutter_max_author_length,
2645 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2646 scroll_anchor: self.scroll_manager.anchor(),
2647 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2648 placeholder_text: self.placeholder_text.clone(),
2649 is_focused: self.focus_handle.is_focused(window),
2650 current_line_highlight: self
2651 .current_line_highlight
2652 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2653 gutter_hovered: self.gutter_hovered,
2654 }
2655 }
2656
2657 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2658 self.buffer.read(cx).language_at(point, cx)
2659 }
2660
2661 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2662 self.buffer.read(cx).read(cx).file_at(point).cloned()
2663 }
2664
2665 pub fn active_excerpt(
2666 &self,
2667 cx: &App,
2668 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2669 self.buffer
2670 .read(cx)
2671 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2672 }
2673
2674 pub fn mode(&self) -> &EditorMode {
2675 &self.mode
2676 }
2677
2678 pub fn set_mode(&mut self, mode: EditorMode) {
2679 self.mode = mode;
2680 }
2681
2682 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2683 self.collaboration_hub.as_deref()
2684 }
2685
2686 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2687 self.collaboration_hub = Some(hub);
2688 }
2689
2690 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2691 self.in_project_search = in_project_search;
2692 }
2693
2694 pub fn set_custom_context_menu(
2695 &mut self,
2696 f: impl 'static
2697 + Fn(
2698 &mut Self,
2699 DisplayPoint,
2700 &mut Window,
2701 &mut Context<Self>,
2702 ) -> Option<Entity<ui::ContextMenu>>,
2703 ) {
2704 self.custom_context_menu = Some(Box::new(f))
2705 }
2706
2707 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2708 self.completion_provider = provider;
2709 }
2710
2711 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2712 self.semantics_provider.clone()
2713 }
2714
2715 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2716 self.semantics_provider = provider;
2717 }
2718
2719 pub fn set_edit_prediction_provider<T>(
2720 &mut self,
2721 provider: Option<Entity<T>>,
2722 window: &mut Window,
2723 cx: &mut Context<Self>,
2724 ) where
2725 T: EditPredictionProvider,
2726 {
2727 self.edit_prediction_provider =
2728 provider.map(|provider| RegisteredInlineCompletionProvider {
2729 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2730 if this.focus_handle.is_focused(window) {
2731 this.update_visible_inline_completion(window, cx);
2732 }
2733 }),
2734 provider: Arc::new(provider),
2735 });
2736 self.update_edit_prediction_settings(cx);
2737 self.refresh_inline_completion(false, false, window, cx);
2738 }
2739
2740 pub fn placeholder_text(&self) -> Option<&str> {
2741 self.placeholder_text.as_deref()
2742 }
2743
2744 pub fn set_placeholder_text(
2745 &mut self,
2746 placeholder_text: impl Into<Arc<str>>,
2747 cx: &mut Context<Self>,
2748 ) {
2749 let placeholder_text = Some(placeholder_text.into());
2750 if self.placeholder_text != placeholder_text {
2751 self.placeholder_text = placeholder_text;
2752 cx.notify();
2753 }
2754 }
2755
2756 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2757 self.cursor_shape = cursor_shape;
2758
2759 // Disrupt blink for immediate user feedback that the cursor shape has changed
2760 self.blink_manager.update(cx, BlinkManager::show_cursor);
2761
2762 cx.notify();
2763 }
2764
2765 pub fn set_current_line_highlight(
2766 &mut self,
2767 current_line_highlight: Option<CurrentLineHighlight>,
2768 ) {
2769 self.current_line_highlight = current_line_highlight;
2770 }
2771
2772 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2773 self.collapse_matches = collapse_matches;
2774 }
2775
2776 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2777 let buffers = self.buffer.read(cx).all_buffers();
2778 let Some(project) = self.project.as_ref() else {
2779 return;
2780 };
2781 project.update(cx, |project, cx| {
2782 for buffer in buffers {
2783 self.registered_buffers
2784 .entry(buffer.read(cx).remote_id())
2785 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2786 }
2787 })
2788 }
2789
2790 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2791 if self.collapse_matches {
2792 return range.start..range.start;
2793 }
2794 range.clone()
2795 }
2796
2797 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2798 if self.display_map.read(cx).clip_at_line_ends != clip {
2799 self.display_map
2800 .update(cx, |map, _| map.clip_at_line_ends = clip);
2801 }
2802 }
2803
2804 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2805 self.input_enabled = input_enabled;
2806 }
2807
2808 pub fn set_inline_completions_hidden_for_vim_mode(
2809 &mut self,
2810 hidden: bool,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) {
2814 if hidden != self.inline_completions_hidden_for_vim_mode {
2815 self.inline_completions_hidden_for_vim_mode = hidden;
2816 if hidden {
2817 self.update_visible_inline_completion(window, cx);
2818 } else {
2819 self.refresh_inline_completion(true, false, window, cx);
2820 }
2821 }
2822 }
2823
2824 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2825 self.menu_inline_completions_policy = value;
2826 }
2827
2828 pub fn set_autoindent(&mut self, autoindent: bool) {
2829 if autoindent {
2830 self.autoindent_mode = Some(AutoindentMode::EachLine);
2831 } else {
2832 self.autoindent_mode = None;
2833 }
2834 }
2835
2836 pub fn read_only(&self, cx: &App) -> bool {
2837 self.read_only || self.buffer.read(cx).read_only()
2838 }
2839
2840 pub fn set_read_only(&mut self, read_only: bool) {
2841 self.read_only = read_only;
2842 }
2843
2844 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2845 self.use_autoclose = autoclose;
2846 }
2847
2848 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2849 self.use_auto_surround = auto_surround;
2850 }
2851
2852 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2853 self.auto_replace_emoji_shortcode = auto_replace;
2854 }
2855
2856 pub fn toggle_edit_predictions(
2857 &mut self,
2858 _: &ToggleEditPrediction,
2859 window: &mut Window,
2860 cx: &mut Context<Self>,
2861 ) {
2862 if self.show_inline_completions_override.is_some() {
2863 self.set_show_edit_predictions(None, window, cx);
2864 } else {
2865 let show_edit_predictions = !self.edit_predictions_enabled();
2866 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2867 }
2868 }
2869
2870 pub fn set_show_edit_predictions(
2871 &mut self,
2872 show_edit_predictions: Option<bool>,
2873 window: &mut Window,
2874 cx: &mut Context<Self>,
2875 ) {
2876 self.show_inline_completions_override = show_edit_predictions;
2877 self.update_edit_prediction_settings(cx);
2878
2879 if let Some(false) = show_edit_predictions {
2880 self.discard_inline_completion(false, cx);
2881 } else {
2882 self.refresh_inline_completion(false, true, window, cx);
2883 }
2884 }
2885
2886 fn inline_completions_disabled_in_scope(
2887 &self,
2888 buffer: &Entity<Buffer>,
2889 buffer_position: language::Anchor,
2890 cx: &App,
2891 ) -> bool {
2892 let snapshot = buffer.read(cx).snapshot();
2893 let settings = snapshot.settings_at(buffer_position, cx);
2894
2895 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2896 return false;
2897 };
2898
2899 scope.override_name().map_or(false, |scope_name| {
2900 settings
2901 .edit_predictions_disabled_in
2902 .iter()
2903 .any(|s| s == scope_name)
2904 })
2905 }
2906
2907 pub fn set_use_modal_editing(&mut self, to: bool) {
2908 self.use_modal_editing = to;
2909 }
2910
2911 pub fn use_modal_editing(&self) -> bool {
2912 self.use_modal_editing
2913 }
2914
2915 fn selections_did_change(
2916 &mut self,
2917 local: bool,
2918 old_cursor_position: &Anchor,
2919 effects: SelectionEffects,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 window.invalidate_character_coordinates();
2924
2925 // Copy selections to primary selection buffer
2926 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2927 if local {
2928 let selections = self.selections.all::<usize>(cx);
2929 let buffer_handle = self.buffer.read(cx).read(cx);
2930
2931 let mut text = String::new();
2932 for (index, selection) in selections.iter().enumerate() {
2933 let text_for_selection = buffer_handle
2934 .text_for_range(selection.start..selection.end)
2935 .collect::<String>();
2936
2937 text.push_str(&text_for_selection);
2938 if index != selections.len() - 1 {
2939 text.push('\n');
2940 }
2941 }
2942
2943 if !text.is_empty() {
2944 cx.write_to_primary(ClipboardItem::new_string(text));
2945 }
2946 }
2947
2948 let selection_anchors = self.selections.disjoint_anchors();
2949
2950 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2951 self.buffer.update(cx, |buffer, cx| {
2952 buffer.set_active_selections(
2953 &selection_anchors,
2954 self.selections.line_mode,
2955 self.cursor_shape,
2956 cx,
2957 )
2958 });
2959 }
2960 let display_map = self
2961 .display_map
2962 .update(cx, |display_map, cx| display_map.snapshot(cx));
2963 let buffer = &display_map.buffer_snapshot;
2964 if self.selections.count() == 1 {
2965 self.add_selections_state = None;
2966 }
2967 self.select_next_state = None;
2968 self.select_prev_state = None;
2969 self.select_syntax_node_history.try_clear();
2970 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2971 self.snippet_stack.invalidate(&selection_anchors, buffer);
2972 self.take_rename(false, window, cx);
2973
2974 let newest_selection = self.selections.newest_anchor();
2975 let new_cursor_position = newest_selection.head();
2976 let selection_start = newest_selection.start;
2977
2978 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2979 self.push_to_nav_history(
2980 *old_cursor_position,
2981 Some(new_cursor_position.to_point(buffer)),
2982 false,
2983 effects.nav_history == Some(true),
2984 cx,
2985 );
2986 }
2987
2988 if local {
2989 if let Some(buffer_id) = new_cursor_position.buffer_id {
2990 if !self.registered_buffers.contains_key(&buffer_id) {
2991 if let Some(project) = self.project.as_ref() {
2992 project.update(cx, |project, cx| {
2993 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2994 return;
2995 };
2996 self.registered_buffers.insert(
2997 buffer_id,
2998 project.register_buffer_with_language_servers(&buffer, cx),
2999 );
3000 })
3001 }
3002 }
3003 }
3004
3005 let mut context_menu = self.context_menu.borrow_mut();
3006 let completion_menu = match context_menu.as_ref() {
3007 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3008 Some(CodeContextMenu::CodeActions(_)) => {
3009 *context_menu = None;
3010 None
3011 }
3012 None => None,
3013 };
3014 let completion_position = completion_menu.map(|menu| menu.initial_position);
3015 drop(context_menu);
3016
3017 if effects.completions {
3018 if let Some(completion_position) = completion_position {
3019 let start_offset = selection_start.to_offset(buffer);
3020 let position_matches = start_offset == completion_position.to_offset(buffer);
3021 let continue_showing = if position_matches {
3022 if self.snippet_stack.is_empty() {
3023 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3024 } else {
3025 // Snippet choices can be shown even when the cursor is in whitespace.
3026 // Dismissing the menu with actions like backspace is handled by
3027 // invalidation regions.
3028 true
3029 }
3030 } else {
3031 false
3032 };
3033
3034 if continue_showing {
3035 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3036 } else {
3037 self.hide_context_menu(window, cx);
3038 }
3039 }
3040 }
3041
3042 hide_hover(self, cx);
3043
3044 if old_cursor_position.to_display_point(&display_map).row()
3045 != new_cursor_position.to_display_point(&display_map).row()
3046 {
3047 self.available_code_actions.take();
3048 }
3049 self.refresh_code_actions(window, cx);
3050 self.refresh_document_highlights(cx);
3051 self.refresh_selected_text_highlights(false, window, cx);
3052 refresh_matching_bracket_highlights(self, window, cx);
3053 self.update_visible_inline_completion(window, cx);
3054 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3055 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3056 self.inline_blame_popover.take();
3057 if self.git_blame_inline_enabled {
3058 self.start_inline_blame_timer(window, cx);
3059 }
3060 }
3061
3062 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3063 cx.emit(EditorEvent::SelectionsChanged { local });
3064
3065 let selections = &self.selections.disjoint;
3066 if selections.len() == 1 {
3067 cx.emit(SearchEvent::ActiveMatchChanged)
3068 }
3069 if local {
3070 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3071 let inmemory_selections = selections
3072 .iter()
3073 .map(|s| {
3074 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3075 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3076 })
3077 .collect();
3078 self.update_restoration_data(cx, |data| {
3079 data.selections = inmemory_selections;
3080 });
3081
3082 if WorkspaceSettings::get(None, cx).restore_on_startup
3083 != RestoreOnStartupBehavior::None
3084 {
3085 if let Some(workspace_id) =
3086 self.workspace.as_ref().and_then(|workspace| workspace.1)
3087 {
3088 let snapshot = self.buffer().read(cx).snapshot(cx);
3089 let selections = selections.clone();
3090 let background_executor = cx.background_executor().clone();
3091 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3092 self.serialize_selections = cx.background_spawn(async move {
3093 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3094 let db_selections = selections
3095 .iter()
3096 .map(|selection| {
3097 (
3098 selection.start.to_offset(&snapshot),
3099 selection.end.to_offset(&snapshot),
3100 )
3101 })
3102 .collect();
3103
3104 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3105 .await
3106 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3107 .log_err();
3108 });
3109 }
3110 }
3111 }
3112 }
3113
3114 cx.notify();
3115 }
3116
3117 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3118 use text::ToOffset as _;
3119 use text::ToPoint as _;
3120
3121 if self.mode.is_minimap()
3122 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3123 {
3124 return;
3125 }
3126
3127 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3128 return;
3129 };
3130
3131 let snapshot = singleton.read(cx).snapshot();
3132 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3133 let display_snapshot = display_map.snapshot(cx);
3134
3135 display_snapshot
3136 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3137 .map(|fold| {
3138 fold.range.start.text_anchor.to_point(&snapshot)
3139 ..fold.range.end.text_anchor.to_point(&snapshot)
3140 })
3141 .collect()
3142 });
3143 self.update_restoration_data(cx, |data| {
3144 data.folds = inmemory_folds;
3145 });
3146
3147 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3148 return;
3149 };
3150 let background_executor = cx.background_executor().clone();
3151 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3152 let db_folds = self.display_map.update(cx, |display_map, cx| {
3153 display_map
3154 .snapshot(cx)
3155 .folds_in_range(0..snapshot.len())
3156 .map(|fold| {
3157 (
3158 fold.range.start.text_anchor.to_offset(&snapshot),
3159 fold.range.end.text_anchor.to_offset(&snapshot),
3160 )
3161 })
3162 .collect()
3163 });
3164 self.serialize_folds = cx.background_spawn(async move {
3165 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3166 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3167 .await
3168 .with_context(|| {
3169 format!(
3170 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3171 )
3172 })
3173 .log_err();
3174 });
3175 }
3176
3177 pub fn sync_selections(
3178 &mut self,
3179 other: Entity<Editor>,
3180 cx: &mut Context<Self>,
3181 ) -> gpui::Subscription {
3182 let other_selections = other.read(cx).selections.disjoint.to_vec();
3183 self.selections.change_with(cx, |selections| {
3184 selections.select_anchors(other_selections);
3185 });
3186
3187 let other_subscription =
3188 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3189 EditorEvent::SelectionsChanged { local: true } => {
3190 let other_selections = other.read(cx).selections.disjoint.to_vec();
3191 if other_selections.is_empty() {
3192 return;
3193 }
3194 this.selections.change_with(cx, |selections| {
3195 selections.select_anchors(other_selections);
3196 });
3197 }
3198 _ => {}
3199 });
3200
3201 let this_subscription =
3202 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3203 EditorEvent::SelectionsChanged { local: true } => {
3204 let these_selections = this.selections.disjoint.to_vec();
3205 if these_selections.is_empty() {
3206 return;
3207 }
3208 other.update(cx, |other_editor, cx| {
3209 other_editor.selections.change_with(cx, |selections| {
3210 selections.select_anchors(these_selections);
3211 })
3212 });
3213 }
3214 _ => {}
3215 });
3216
3217 Subscription::join(other_subscription, this_subscription)
3218 }
3219
3220 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3221 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3222 /// effects of selection change occur at the end of the transaction.
3223 pub fn change_selections<R>(
3224 &mut self,
3225 effects: SelectionEffects,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3229 ) -> R {
3230 if let Some(state) = &mut self.deferred_selection_effects_state {
3231 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3232 state.effects.completions = effects.completions;
3233 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3234 let (changed, result) = self.selections.change_with(cx, change);
3235 state.changed |= changed;
3236 return result;
3237 }
3238 let mut state = DeferredSelectionEffectsState {
3239 changed: false,
3240 effects,
3241 old_cursor_position: self.selections.newest_anchor().head(),
3242 history_entry: SelectionHistoryEntry {
3243 selections: self.selections.disjoint_anchors(),
3244 select_next_state: self.select_next_state.clone(),
3245 select_prev_state: self.select_prev_state.clone(),
3246 add_selections_state: self.add_selections_state.clone(),
3247 },
3248 };
3249 let (changed, result) = self.selections.change_with(cx, change);
3250 state.changed = state.changed || changed;
3251 if self.defer_selection_effects {
3252 self.deferred_selection_effects_state = Some(state);
3253 } else {
3254 self.apply_selection_effects(state, window, cx);
3255 }
3256 result
3257 }
3258
3259 /// Defers the effects of selection change, so that the effects of multiple calls to
3260 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3261 /// to selection history and the state of popovers based on selection position aren't
3262 /// erroneously updated.
3263 pub fn with_selection_effects_deferred<R>(
3264 &mut self,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3268 ) -> R {
3269 let already_deferred = self.defer_selection_effects;
3270 self.defer_selection_effects = true;
3271 let result = update(self, window, cx);
3272 if !already_deferred {
3273 self.defer_selection_effects = false;
3274 if let Some(state) = self.deferred_selection_effects_state.take() {
3275 self.apply_selection_effects(state, window, cx);
3276 }
3277 }
3278 result
3279 }
3280
3281 fn apply_selection_effects(
3282 &mut self,
3283 state: DeferredSelectionEffectsState,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 ) {
3287 if state.changed {
3288 self.selection_history.push(state.history_entry);
3289
3290 if let Some(autoscroll) = state.effects.scroll {
3291 self.request_autoscroll(autoscroll, cx);
3292 }
3293
3294 let old_cursor_position = &state.old_cursor_position;
3295
3296 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3297
3298 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3299 self.show_signature_help(&ShowSignatureHelp, window, cx);
3300 }
3301 }
3302 }
3303
3304 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3305 where
3306 I: IntoIterator<Item = (Range<S>, T)>,
3307 S: ToOffset,
3308 T: Into<Arc<str>>,
3309 {
3310 if self.read_only(cx) {
3311 return;
3312 }
3313
3314 self.buffer
3315 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3316 }
3317
3318 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3319 where
3320 I: IntoIterator<Item = (Range<S>, T)>,
3321 S: ToOffset,
3322 T: Into<Arc<str>>,
3323 {
3324 if self.read_only(cx) {
3325 return;
3326 }
3327
3328 self.buffer.update(cx, |buffer, cx| {
3329 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3330 });
3331 }
3332
3333 pub fn edit_with_block_indent<I, S, T>(
3334 &mut self,
3335 edits: I,
3336 original_indent_columns: Vec<Option<u32>>,
3337 cx: &mut Context<Self>,
3338 ) where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(
3349 edits,
3350 Some(AutoindentMode::Block {
3351 original_indent_columns,
3352 }),
3353 cx,
3354 )
3355 });
3356 }
3357
3358 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3359 self.hide_context_menu(window, cx);
3360
3361 match phase {
3362 SelectPhase::Begin {
3363 position,
3364 add,
3365 click_count,
3366 } => self.begin_selection(position, add, click_count, window, cx),
3367 SelectPhase::BeginColumnar {
3368 position,
3369 goal_column,
3370 reset,
3371 mode,
3372 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3373 SelectPhase::Extend {
3374 position,
3375 click_count,
3376 } => self.extend_selection(position, click_count, window, cx),
3377 SelectPhase::Update {
3378 position,
3379 goal_column,
3380 scroll_delta,
3381 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3382 SelectPhase::End => self.end_selection(window, cx),
3383 }
3384 }
3385
3386 fn extend_selection(
3387 &mut self,
3388 position: DisplayPoint,
3389 click_count: usize,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3394 let tail = self.selections.newest::<usize>(cx).tail();
3395 self.begin_selection(position, false, click_count, window, cx);
3396
3397 let position = position.to_offset(&display_map, Bias::Left);
3398 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3399
3400 let mut pending_selection = self
3401 .selections
3402 .pending_anchor()
3403 .expect("extend_selection not called with pending selection");
3404 if position >= tail {
3405 pending_selection.start = tail_anchor;
3406 } else {
3407 pending_selection.end = tail_anchor;
3408 pending_selection.reversed = true;
3409 }
3410
3411 let mut pending_mode = self.selections.pending_mode().unwrap();
3412 match &mut pending_mode {
3413 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3414 _ => {}
3415 }
3416
3417 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3418 SelectionEffects::scroll(Autoscroll::fit())
3419 } else {
3420 SelectionEffects::no_scroll()
3421 };
3422
3423 self.change_selections(effects, window, cx, |s| {
3424 s.set_pending(pending_selection, pending_mode)
3425 });
3426 }
3427
3428 fn begin_selection(
3429 &mut self,
3430 position: DisplayPoint,
3431 add: bool,
3432 click_count: usize,
3433 window: &mut Window,
3434 cx: &mut Context<Self>,
3435 ) {
3436 if !self.focus_handle.is_focused(window) {
3437 self.last_focused_descendant = None;
3438 window.focus(&self.focus_handle);
3439 }
3440
3441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3442 let buffer = &display_map.buffer_snapshot;
3443 let position = display_map.clip_point(position, Bias::Left);
3444
3445 let start;
3446 let end;
3447 let mode;
3448 let mut auto_scroll;
3449 match click_count {
3450 1 => {
3451 start = buffer.anchor_before(position.to_point(&display_map));
3452 end = start;
3453 mode = SelectMode::Character;
3454 auto_scroll = true;
3455 }
3456 2 => {
3457 let position = display_map
3458 .clip_point(position, Bias::Left)
3459 .to_offset(&display_map, Bias::Left);
3460 let (range, _) = buffer.surrounding_word(position, false);
3461 start = buffer.anchor_before(range.start);
3462 end = buffer.anchor_before(range.end);
3463 mode = SelectMode::Word(start..end);
3464 auto_scroll = true;
3465 }
3466 3 => {
3467 let position = display_map
3468 .clip_point(position, Bias::Left)
3469 .to_point(&display_map);
3470 let line_start = display_map.prev_line_boundary(position).0;
3471 let next_line_start = buffer.clip_point(
3472 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3473 Bias::Left,
3474 );
3475 start = buffer.anchor_before(line_start);
3476 end = buffer.anchor_before(next_line_start);
3477 mode = SelectMode::Line(start..end);
3478 auto_scroll = true;
3479 }
3480 _ => {
3481 start = buffer.anchor_before(0);
3482 end = buffer.anchor_before(buffer.len());
3483 mode = SelectMode::All;
3484 auto_scroll = false;
3485 }
3486 }
3487 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3488
3489 let point_to_delete: Option<usize> = {
3490 let selected_points: Vec<Selection<Point>> =
3491 self.selections.disjoint_in_range(start..end, cx);
3492
3493 if !add || click_count > 1 {
3494 None
3495 } else if !selected_points.is_empty() {
3496 Some(selected_points[0].id)
3497 } else {
3498 let clicked_point_already_selected =
3499 self.selections.disjoint.iter().find(|selection| {
3500 selection.start.to_point(buffer) == start.to_point(buffer)
3501 || selection.end.to_point(buffer) == end.to_point(buffer)
3502 });
3503
3504 clicked_point_already_selected.map(|selection| selection.id)
3505 }
3506 };
3507
3508 let selections_count = self.selections.count();
3509 let effects = if auto_scroll {
3510 SelectionEffects::default()
3511 } else {
3512 SelectionEffects::no_scroll()
3513 };
3514
3515 self.change_selections(effects, window, cx, |s| {
3516 if let Some(point_to_delete) = point_to_delete {
3517 s.delete(point_to_delete);
3518
3519 if selections_count == 1 {
3520 s.set_pending_anchor_range(start..end, mode);
3521 }
3522 } else {
3523 if !add {
3524 s.clear_disjoint();
3525 }
3526
3527 s.set_pending_anchor_range(start..end, mode);
3528 }
3529 });
3530 }
3531
3532 fn begin_columnar_selection(
3533 &mut self,
3534 position: DisplayPoint,
3535 goal_column: u32,
3536 reset: bool,
3537 mode: ColumnarMode,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) {
3541 if !self.focus_handle.is_focused(window) {
3542 self.last_focused_descendant = None;
3543 window.focus(&self.focus_handle);
3544 }
3545
3546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3547
3548 if reset {
3549 let pointer_position = display_map
3550 .buffer_snapshot
3551 .anchor_before(position.to_point(&display_map));
3552
3553 self.change_selections(
3554 SelectionEffects::scroll(Autoscroll::newest()),
3555 window,
3556 cx,
3557 |s| {
3558 s.clear_disjoint();
3559 s.set_pending_anchor_range(
3560 pointer_position..pointer_position,
3561 SelectMode::Character,
3562 );
3563 },
3564 );
3565 };
3566
3567 let tail = self.selections.newest::<Point>(cx).tail();
3568 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3569 self.columnar_selection_state = match mode {
3570 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3571 selection_tail: selection_anchor,
3572 display_point: if reset {
3573 if position.column() != goal_column {
3574 Some(DisplayPoint::new(position.row(), goal_column))
3575 } else {
3576 None
3577 }
3578 } else {
3579 None
3580 },
3581 }),
3582 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3583 selection_tail: selection_anchor,
3584 }),
3585 };
3586
3587 if !reset {
3588 self.select_columns(position, goal_column, &display_map, window, cx);
3589 }
3590 }
3591
3592 fn update_selection(
3593 &mut self,
3594 position: DisplayPoint,
3595 goal_column: u32,
3596 scroll_delta: gpui::Point<f32>,
3597 window: &mut Window,
3598 cx: &mut Context<Self>,
3599 ) {
3600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3601
3602 if self.columnar_selection_state.is_some() {
3603 self.select_columns(position, goal_column, &display_map, window, cx);
3604 } else if let Some(mut pending) = self.selections.pending_anchor() {
3605 let buffer = &display_map.buffer_snapshot;
3606 let head;
3607 let tail;
3608 let mode = self.selections.pending_mode().unwrap();
3609 match &mode {
3610 SelectMode::Character => {
3611 head = position.to_point(&display_map);
3612 tail = pending.tail().to_point(buffer);
3613 }
3614 SelectMode::Word(original_range) => {
3615 let offset = display_map
3616 .clip_point(position, Bias::Left)
3617 .to_offset(&display_map, Bias::Left);
3618 let original_range = original_range.to_offset(buffer);
3619
3620 let head_offset = if buffer.is_inside_word(offset, false)
3621 || original_range.contains(&offset)
3622 {
3623 let (word_range, _) = buffer.surrounding_word(offset, false);
3624 if word_range.start < original_range.start {
3625 word_range.start
3626 } else {
3627 word_range.end
3628 }
3629 } else {
3630 offset
3631 };
3632
3633 head = head_offset.to_point(buffer);
3634 if head_offset <= original_range.start {
3635 tail = original_range.end.to_point(buffer);
3636 } else {
3637 tail = original_range.start.to_point(buffer);
3638 }
3639 }
3640 SelectMode::Line(original_range) => {
3641 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3642
3643 let position = display_map
3644 .clip_point(position, Bias::Left)
3645 .to_point(&display_map);
3646 let line_start = display_map.prev_line_boundary(position).0;
3647 let next_line_start = buffer.clip_point(
3648 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3649 Bias::Left,
3650 );
3651
3652 if line_start < original_range.start {
3653 head = line_start
3654 } else {
3655 head = next_line_start
3656 }
3657
3658 if head <= original_range.start {
3659 tail = original_range.end;
3660 } else {
3661 tail = original_range.start;
3662 }
3663 }
3664 SelectMode::All => {
3665 return;
3666 }
3667 };
3668
3669 if head < tail {
3670 pending.start = buffer.anchor_before(head);
3671 pending.end = buffer.anchor_before(tail);
3672 pending.reversed = true;
3673 } else {
3674 pending.start = buffer.anchor_before(tail);
3675 pending.end = buffer.anchor_before(head);
3676 pending.reversed = false;
3677 }
3678
3679 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3680 s.set_pending(pending, mode);
3681 });
3682 } else {
3683 log::error!("update_selection dispatched with no pending selection");
3684 return;
3685 }
3686
3687 self.apply_scroll_delta(scroll_delta, window, cx);
3688 cx.notify();
3689 }
3690
3691 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3692 self.columnar_selection_state.take();
3693 if self.selections.pending_anchor().is_some() {
3694 let selections = self.selections.all::<usize>(cx);
3695 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3696 s.select(selections);
3697 s.clear_pending();
3698 });
3699 }
3700 }
3701
3702 fn select_columns(
3703 &mut self,
3704 head: DisplayPoint,
3705 goal_column: u32,
3706 display_map: &DisplaySnapshot,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3711 return;
3712 };
3713
3714 let tail = match columnar_state {
3715 ColumnarSelectionState::FromMouse {
3716 selection_tail,
3717 display_point,
3718 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3719 ColumnarSelectionState::FromSelection { selection_tail } => {
3720 selection_tail.to_display_point(&display_map)
3721 }
3722 };
3723
3724 let start_row = cmp::min(tail.row(), head.row());
3725 let end_row = cmp::max(tail.row(), head.row());
3726 let start_column = cmp::min(tail.column(), goal_column);
3727 let end_column = cmp::max(tail.column(), goal_column);
3728 let reversed = start_column < tail.column();
3729
3730 let selection_ranges = (start_row.0..=end_row.0)
3731 .map(DisplayRow)
3732 .filter_map(|row| {
3733 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3734 || start_column <= display_map.line_len(row))
3735 && !display_map.is_block_line(row)
3736 {
3737 let start = display_map
3738 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3739 .to_point(display_map);
3740 let end = display_map
3741 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3742 .to_point(display_map);
3743 if reversed {
3744 Some(end..start)
3745 } else {
3746 Some(start..end)
3747 }
3748 } else {
3749 None
3750 }
3751 })
3752 .collect::<Vec<_>>();
3753
3754 let ranges = match columnar_state {
3755 ColumnarSelectionState::FromMouse { .. } => {
3756 let mut non_empty_ranges = selection_ranges
3757 .iter()
3758 .filter(|selection_range| selection_range.start != selection_range.end)
3759 .peekable();
3760 if non_empty_ranges.peek().is_some() {
3761 non_empty_ranges.cloned().collect()
3762 } else {
3763 selection_ranges
3764 }
3765 }
3766 _ => selection_ranges,
3767 };
3768
3769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3770 s.select_ranges(ranges);
3771 });
3772 cx.notify();
3773 }
3774
3775 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3776 self.selections
3777 .all_adjusted(cx)
3778 .iter()
3779 .any(|selection| !selection.is_empty())
3780 }
3781
3782 pub fn has_pending_nonempty_selection(&self) -> bool {
3783 let pending_nonempty_selection = match self.selections.pending_anchor() {
3784 Some(Selection { start, end, .. }) => start != end,
3785 None => false,
3786 };
3787
3788 pending_nonempty_selection
3789 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3790 }
3791
3792 pub fn has_pending_selection(&self) -> bool {
3793 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3794 }
3795
3796 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3797 self.selection_mark_mode = false;
3798 self.selection_drag_state = SelectionDragState::None;
3799
3800 if self.clear_expanded_diff_hunks(cx) {
3801 cx.notify();
3802 return;
3803 }
3804 if self.dismiss_menus_and_popups(true, window, cx) {
3805 return;
3806 }
3807
3808 if self.mode.is_full()
3809 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3810 {
3811 return;
3812 }
3813
3814 cx.propagate();
3815 }
3816
3817 pub fn dismiss_menus_and_popups(
3818 &mut self,
3819 is_user_requested: bool,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) -> bool {
3823 if self.take_rename(false, window, cx).is_some() {
3824 return true;
3825 }
3826
3827 if hide_hover(self, cx) {
3828 return true;
3829 }
3830
3831 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3832 return true;
3833 }
3834
3835 if self.hide_context_menu(window, cx).is_some() {
3836 return true;
3837 }
3838
3839 if self.mouse_context_menu.take().is_some() {
3840 return true;
3841 }
3842
3843 if is_user_requested && self.discard_inline_completion(true, cx) {
3844 return true;
3845 }
3846
3847 if self.snippet_stack.pop().is_some() {
3848 return true;
3849 }
3850
3851 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3852 self.dismiss_diagnostics(cx);
3853 return true;
3854 }
3855
3856 false
3857 }
3858
3859 fn linked_editing_ranges_for(
3860 &self,
3861 selection: Range<text::Anchor>,
3862 cx: &App,
3863 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3864 if self.linked_edit_ranges.is_empty() {
3865 return None;
3866 }
3867 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3868 selection.end.buffer_id.and_then(|end_buffer_id| {
3869 if selection.start.buffer_id != Some(end_buffer_id) {
3870 return None;
3871 }
3872 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3873 let snapshot = buffer.read(cx).snapshot();
3874 self.linked_edit_ranges
3875 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3876 .map(|ranges| (ranges, snapshot, buffer))
3877 })?;
3878 use text::ToOffset as TO;
3879 // find offset from the start of current range to current cursor position
3880 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3881
3882 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3883 let start_difference = start_offset - start_byte_offset;
3884 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3885 let end_difference = end_offset - start_byte_offset;
3886 // Current range has associated linked ranges.
3887 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3888 for range in linked_ranges.iter() {
3889 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3890 let end_offset = start_offset + end_difference;
3891 let start_offset = start_offset + start_difference;
3892 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3893 continue;
3894 }
3895 if self.selections.disjoint_anchor_ranges().any(|s| {
3896 if s.start.buffer_id != selection.start.buffer_id
3897 || s.end.buffer_id != selection.end.buffer_id
3898 {
3899 return false;
3900 }
3901 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3902 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3903 }) {
3904 continue;
3905 }
3906 let start = buffer_snapshot.anchor_after(start_offset);
3907 let end = buffer_snapshot.anchor_after(end_offset);
3908 linked_edits
3909 .entry(buffer.clone())
3910 .or_default()
3911 .push(start..end);
3912 }
3913 Some(linked_edits)
3914 }
3915
3916 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3917 let text: Arc<str> = text.into();
3918
3919 if self.read_only(cx) {
3920 return;
3921 }
3922
3923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3924
3925 let selections = self.selections.all_adjusted(cx);
3926 let mut bracket_inserted = false;
3927 let mut edits = Vec::new();
3928 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3929 let mut new_selections = Vec::with_capacity(selections.len());
3930 let mut new_autoclose_regions = Vec::new();
3931 let snapshot = self.buffer.read(cx).read(cx);
3932 let mut clear_linked_edit_ranges = false;
3933
3934 for (selection, autoclose_region) in
3935 self.selections_with_autoclose_regions(selections, &snapshot)
3936 {
3937 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3938 // Determine if the inserted text matches the opening or closing
3939 // bracket of any of this language's bracket pairs.
3940 let mut bracket_pair = None;
3941 let mut is_bracket_pair_start = false;
3942 let mut is_bracket_pair_end = false;
3943 if !text.is_empty() {
3944 let mut bracket_pair_matching_end = None;
3945 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3946 // and they are removing the character that triggered IME popup.
3947 for (pair, enabled) in scope.brackets() {
3948 if !pair.close && !pair.surround {
3949 continue;
3950 }
3951
3952 if enabled && pair.start.ends_with(text.as_ref()) {
3953 let prefix_len = pair.start.len() - text.len();
3954 let preceding_text_matches_prefix = prefix_len == 0
3955 || (selection.start.column >= (prefix_len as u32)
3956 && snapshot.contains_str_at(
3957 Point::new(
3958 selection.start.row,
3959 selection.start.column - (prefix_len as u32),
3960 ),
3961 &pair.start[..prefix_len],
3962 ));
3963 if preceding_text_matches_prefix {
3964 bracket_pair = Some(pair.clone());
3965 is_bracket_pair_start = true;
3966 break;
3967 }
3968 }
3969 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3970 {
3971 // take first bracket pair matching end, but don't break in case a later bracket
3972 // pair matches start
3973 bracket_pair_matching_end = Some(pair.clone());
3974 }
3975 }
3976 if let Some(end) = bracket_pair_matching_end
3977 && bracket_pair.is_none()
3978 {
3979 bracket_pair = Some(end);
3980 is_bracket_pair_end = true;
3981 }
3982 }
3983
3984 if let Some(bracket_pair) = bracket_pair {
3985 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3986 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3987 let auto_surround =
3988 self.use_auto_surround && snapshot_settings.use_auto_surround;
3989 if selection.is_empty() {
3990 if is_bracket_pair_start {
3991 // If the inserted text is a suffix of an opening bracket and the
3992 // selection is preceded by the rest of the opening bracket, then
3993 // insert the closing bracket.
3994 let following_text_allows_autoclose = snapshot
3995 .chars_at(selection.start)
3996 .next()
3997 .map_or(true, |c| scope.should_autoclose_before(c));
3998
3999 let preceding_text_allows_autoclose = selection.start.column == 0
4000 || snapshot.reversed_chars_at(selection.start).next().map_or(
4001 true,
4002 |c| {
4003 bracket_pair.start != bracket_pair.end
4004 || !snapshot
4005 .char_classifier_at(selection.start)
4006 .is_word(c)
4007 },
4008 );
4009
4010 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4011 && bracket_pair.start.len() == 1
4012 {
4013 let target = bracket_pair.start.chars().next().unwrap();
4014 let current_line_count = snapshot
4015 .reversed_chars_at(selection.start)
4016 .take_while(|&c| c != '\n')
4017 .filter(|&c| c == target)
4018 .count();
4019 current_line_count % 2 == 1
4020 } else {
4021 false
4022 };
4023
4024 if autoclose
4025 && bracket_pair.close
4026 && following_text_allows_autoclose
4027 && preceding_text_allows_autoclose
4028 && !is_closing_quote
4029 {
4030 let anchor = snapshot.anchor_before(selection.end);
4031 new_selections.push((selection.map(|_| anchor), text.len()));
4032 new_autoclose_regions.push((
4033 anchor,
4034 text.len(),
4035 selection.id,
4036 bracket_pair.clone(),
4037 ));
4038 edits.push((
4039 selection.range(),
4040 format!("{}{}", text, bracket_pair.end).into(),
4041 ));
4042 bracket_inserted = true;
4043 continue;
4044 }
4045 }
4046
4047 if let Some(region) = autoclose_region {
4048 // If the selection is followed by an auto-inserted closing bracket,
4049 // then don't insert that closing bracket again; just move the selection
4050 // past the closing bracket.
4051 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4052 && text.as_ref() == region.pair.end.as_str()
4053 && snapshot.contains_str_at(region.range.end, text.as_ref());
4054 if should_skip {
4055 let anchor = snapshot.anchor_after(selection.end);
4056 new_selections
4057 .push((selection.map(|_| anchor), region.pair.end.len()));
4058 continue;
4059 }
4060 }
4061
4062 let always_treat_brackets_as_autoclosed = snapshot
4063 .language_settings_at(selection.start, cx)
4064 .always_treat_brackets_as_autoclosed;
4065 if always_treat_brackets_as_autoclosed
4066 && is_bracket_pair_end
4067 && snapshot.contains_str_at(selection.end, text.as_ref())
4068 {
4069 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4070 // and the inserted text is a closing bracket and the selection is followed
4071 // by the closing bracket then move the selection past the closing bracket.
4072 let anchor = snapshot.anchor_after(selection.end);
4073 new_selections.push((selection.map(|_| anchor), text.len()));
4074 continue;
4075 }
4076 }
4077 // If an opening bracket is 1 character long and is typed while
4078 // text is selected, then surround that text with the bracket pair.
4079 else if auto_surround
4080 && bracket_pair.surround
4081 && is_bracket_pair_start
4082 && bracket_pair.start.chars().count() == 1
4083 {
4084 edits.push((selection.start..selection.start, text.clone()));
4085 edits.push((
4086 selection.end..selection.end,
4087 bracket_pair.end.as_str().into(),
4088 ));
4089 bracket_inserted = true;
4090 new_selections.push((
4091 Selection {
4092 id: selection.id,
4093 start: snapshot.anchor_after(selection.start),
4094 end: snapshot.anchor_before(selection.end),
4095 reversed: selection.reversed,
4096 goal: selection.goal,
4097 },
4098 0,
4099 ));
4100 continue;
4101 }
4102 }
4103 }
4104
4105 if self.auto_replace_emoji_shortcode
4106 && selection.is_empty()
4107 && text.as_ref().ends_with(':')
4108 {
4109 if let Some(possible_emoji_short_code) =
4110 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4111 {
4112 if !possible_emoji_short_code.is_empty() {
4113 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4114 let emoji_shortcode_start = Point::new(
4115 selection.start.row,
4116 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4117 );
4118
4119 // Remove shortcode from buffer
4120 edits.push((
4121 emoji_shortcode_start..selection.start,
4122 "".to_string().into(),
4123 ));
4124 new_selections.push((
4125 Selection {
4126 id: selection.id,
4127 start: snapshot.anchor_after(emoji_shortcode_start),
4128 end: snapshot.anchor_before(selection.start),
4129 reversed: selection.reversed,
4130 goal: selection.goal,
4131 },
4132 0,
4133 ));
4134
4135 // Insert emoji
4136 let selection_start_anchor = snapshot.anchor_after(selection.start);
4137 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4138 edits.push((selection.start..selection.end, emoji.to_string().into()));
4139
4140 continue;
4141 }
4142 }
4143 }
4144 }
4145
4146 // If not handling any auto-close operation, then just replace the selected
4147 // text with the given input and move the selection to the end of the
4148 // newly inserted text.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 if !self.linked_edit_ranges.is_empty() {
4151 let start_anchor = snapshot.anchor_before(selection.start);
4152
4153 let is_word_char = text.chars().next().map_or(true, |char| {
4154 let classifier = snapshot
4155 .char_classifier_at(start_anchor.to_offset(&snapshot))
4156 .ignore_punctuation(true);
4157 classifier.is_word(char)
4158 });
4159
4160 if is_word_char {
4161 if let Some(ranges) = self
4162 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4163 {
4164 for (buffer, edits) in ranges {
4165 linked_edits
4166 .entry(buffer.clone())
4167 .or_default()
4168 .extend(edits.into_iter().map(|range| (range, text.clone())));
4169 }
4170 }
4171 } else {
4172 clear_linked_edit_ranges = true;
4173 }
4174 }
4175
4176 new_selections.push((selection.map(|_| anchor), 0));
4177 edits.push((selection.start..selection.end, text.clone()));
4178 }
4179
4180 drop(snapshot);
4181
4182 self.transact(window, cx, |this, window, cx| {
4183 if clear_linked_edit_ranges {
4184 this.linked_edit_ranges.clear();
4185 }
4186 let initial_buffer_versions =
4187 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4188
4189 this.buffer.update(cx, |buffer, cx| {
4190 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4191 });
4192 for (buffer, edits) in linked_edits {
4193 buffer.update(cx, |buffer, cx| {
4194 let snapshot = buffer.snapshot();
4195 let edits = edits
4196 .into_iter()
4197 .map(|(range, text)| {
4198 use text::ToPoint as TP;
4199 let end_point = TP::to_point(&range.end, &snapshot);
4200 let start_point = TP::to_point(&range.start, &snapshot);
4201 (start_point..end_point, text)
4202 })
4203 .sorted_by_key(|(range, _)| range.start);
4204 buffer.edit(edits, None, cx);
4205 })
4206 }
4207 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4208 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4209 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4210 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4211 .zip(new_selection_deltas)
4212 .map(|(selection, delta)| Selection {
4213 id: selection.id,
4214 start: selection.start + delta,
4215 end: selection.end + delta,
4216 reversed: selection.reversed,
4217 goal: SelectionGoal::None,
4218 })
4219 .collect::<Vec<_>>();
4220
4221 let mut i = 0;
4222 for (position, delta, selection_id, pair) in new_autoclose_regions {
4223 let position = position.to_offset(&map.buffer_snapshot) + delta;
4224 let start = map.buffer_snapshot.anchor_before(position);
4225 let end = map.buffer_snapshot.anchor_after(position);
4226 while let Some(existing_state) = this.autoclose_regions.get(i) {
4227 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Greater => break,
4230 Ordering::Equal => {
4231 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4232 Ordering::Less => i += 1,
4233 Ordering::Equal => break,
4234 Ordering::Greater => break,
4235 }
4236 }
4237 }
4238 }
4239 this.autoclose_regions.insert(
4240 i,
4241 AutocloseRegion {
4242 selection_id,
4243 range: start..end,
4244 pair,
4245 },
4246 );
4247 }
4248
4249 let had_active_inline_completion = this.has_active_inline_completion();
4250 this.change_selections(
4251 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4252 window,
4253 cx,
4254 |s| s.select(new_selections),
4255 );
4256
4257 if !bracket_inserted {
4258 if let Some(on_type_format_task) =
4259 this.trigger_on_type_formatting(text.to_string(), window, cx)
4260 {
4261 on_type_format_task.detach_and_log_err(cx);
4262 }
4263 }
4264
4265 let editor_settings = EditorSettings::get_global(cx);
4266 if bracket_inserted
4267 && (editor_settings.auto_signature_help
4268 || editor_settings.show_signature_help_after_edits)
4269 {
4270 this.show_signature_help(&ShowSignatureHelp, window, cx);
4271 }
4272
4273 let trigger_in_words =
4274 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4275 if this.hard_wrap.is_some() {
4276 let latest: Range<Point> = this.selections.newest(cx).range();
4277 if latest.is_empty()
4278 && this
4279 .buffer()
4280 .read(cx)
4281 .snapshot(cx)
4282 .line_len(MultiBufferRow(latest.start.row))
4283 == latest.start.column
4284 {
4285 this.rewrap_impl(
4286 RewrapOptions {
4287 override_language_settings: true,
4288 preserve_existing_whitespace: true,
4289 },
4290 cx,
4291 )
4292 }
4293 }
4294 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4295 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4296 this.refresh_inline_completion(true, false, window, cx);
4297 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4298 });
4299 }
4300
4301 fn find_possible_emoji_shortcode_at_position(
4302 snapshot: &MultiBufferSnapshot,
4303 position: Point,
4304 ) -> Option<String> {
4305 let mut chars = Vec::new();
4306 let mut found_colon = false;
4307 for char in snapshot.reversed_chars_at(position).take(100) {
4308 // Found a possible emoji shortcode in the middle of the buffer
4309 if found_colon {
4310 if char.is_whitespace() {
4311 chars.reverse();
4312 return Some(chars.iter().collect());
4313 }
4314 // If the previous character is not a whitespace, we are in the middle of a word
4315 // and we only want to complete the shortcode if the word is made up of other emojis
4316 let mut containing_word = String::new();
4317 for ch in snapshot
4318 .reversed_chars_at(position)
4319 .skip(chars.len() + 1)
4320 .take(100)
4321 {
4322 if ch.is_whitespace() {
4323 break;
4324 }
4325 containing_word.push(ch);
4326 }
4327 let containing_word = containing_word.chars().rev().collect::<String>();
4328 if util::word_consists_of_emojis(containing_word.as_str()) {
4329 chars.reverse();
4330 return Some(chars.iter().collect());
4331 }
4332 }
4333
4334 if char.is_whitespace() || !char.is_ascii() {
4335 return None;
4336 }
4337 if char == ':' {
4338 found_colon = true;
4339 } else {
4340 chars.push(char);
4341 }
4342 }
4343 // Found a possible emoji shortcode at the beginning of the buffer
4344 chars.reverse();
4345 Some(chars.iter().collect())
4346 }
4347
4348 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4350 self.transact(window, cx, |this, window, cx| {
4351 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4352 let selections = this.selections.all::<usize>(cx);
4353 let multi_buffer = this.buffer.read(cx);
4354 let buffer = multi_buffer.snapshot(cx);
4355 selections
4356 .iter()
4357 .map(|selection| {
4358 let start_point = selection.start.to_point(&buffer);
4359 let mut existing_indent =
4360 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4361 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4362 let start = selection.start;
4363 let end = selection.end;
4364 let selection_is_empty = start == end;
4365 let language_scope = buffer.language_scope_at(start);
4366 let (
4367 comment_delimiter,
4368 doc_delimiter,
4369 insert_extra_newline,
4370 indent_on_newline,
4371 indent_on_extra_newline,
4372 ) = if let Some(language) = &language_scope {
4373 let mut insert_extra_newline =
4374 insert_extra_newline_brackets(&buffer, start..end, language)
4375 || insert_extra_newline_tree_sitter(&buffer, start..end);
4376
4377 // Comment extension on newline is allowed only for cursor selections
4378 let comment_delimiter = maybe!({
4379 if !selection_is_empty {
4380 return None;
4381 }
4382
4383 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4384 return None;
4385 }
4386
4387 let delimiters = language.line_comment_prefixes();
4388 let max_len_of_delimiter =
4389 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4390 let (snapshot, range) =
4391 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4392
4393 let num_of_whitespaces = snapshot
4394 .chars_for_range(range.clone())
4395 .take_while(|c| c.is_whitespace())
4396 .count();
4397 let comment_candidate = snapshot
4398 .chars_for_range(range.clone())
4399 .skip(num_of_whitespaces)
4400 .take(max_len_of_delimiter)
4401 .collect::<String>();
4402 let (delimiter, trimmed_len) = delimiters
4403 .iter()
4404 .filter_map(|delimiter| {
4405 let prefix = delimiter.trim_end();
4406 if comment_candidate.starts_with(prefix) {
4407 Some((delimiter, prefix.len()))
4408 } else {
4409 None
4410 }
4411 })
4412 .max_by_key(|(_, len)| *len)?;
4413
4414 if let Some(BlockCommentConfig {
4415 start: block_start, ..
4416 }) = language.block_comment()
4417 {
4418 let block_start_trimmed = block_start.trim_end();
4419 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4420 let line_content = snapshot
4421 .chars_for_range(range)
4422 .skip(num_of_whitespaces)
4423 .take(block_start_trimmed.len())
4424 .collect::<String>();
4425
4426 if line_content.starts_with(block_start_trimmed) {
4427 return None;
4428 }
4429 }
4430 }
4431
4432 let cursor_is_placed_after_comment_marker =
4433 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4434 if cursor_is_placed_after_comment_marker {
4435 Some(delimiter.clone())
4436 } else {
4437 None
4438 }
4439 });
4440
4441 let mut indent_on_newline = IndentSize::spaces(0);
4442 let mut indent_on_extra_newline = IndentSize::spaces(0);
4443
4444 let doc_delimiter = maybe!({
4445 if !selection_is_empty {
4446 return None;
4447 }
4448
4449 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4450 return None;
4451 }
4452
4453 let BlockCommentConfig {
4454 start: start_tag,
4455 end: end_tag,
4456 prefix: delimiter,
4457 tab_size: len,
4458 } = language.documentation_comment()?;
4459 let is_within_block_comment = buffer
4460 .language_scope_at(start_point)
4461 .is_some_and(|scope| scope.override_name() == Some("comment"));
4462 if !is_within_block_comment {
4463 return None;
4464 }
4465
4466 let (snapshot, range) =
4467 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4468
4469 let num_of_whitespaces = snapshot
4470 .chars_for_range(range.clone())
4471 .take_while(|c| c.is_whitespace())
4472 .count();
4473
4474 // 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.
4475 let column = start_point.column;
4476 let cursor_is_after_start_tag = {
4477 let start_tag_len = start_tag.len();
4478 let start_tag_line = snapshot
4479 .chars_for_range(range.clone())
4480 .skip(num_of_whitespaces)
4481 .take(start_tag_len)
4482 .collect::<String>();
4483 if start_tag_line.starts_with(start_tag.as_ref()) {
4484 num_of_whitespaces + start_tag_len <= column as usize
4485 } else {
4486 false
4487 }
4488 };
4489
4490 let cursor_is_after_delimiter = {
4491 let delimiter_trim = delimiter.trim_end();
4492 let delimiter_line = snapshot
4493 .chars_for_range(range.clone())
4494 .skip(num_of_whitespaces)
4495 .take(delimiter_trim.len())
4496 .collect::<String>();
4497 if delimiter_line.starts_with(delimiter_trim) {
4498 num_of_whitespaces + delimiter_trim.len() <= column as usize
4499 } else {
4500 false
4501 }
4502 };
4503
4504 let cursor_is_before_end_tag_if_exists = {
4505 let mut char_position = 0u32;
4506 let mut end_tag_offset = None;
4507
4508 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4509 if let Some(byte_pos) = chunk.find(&**end_tag) {
4510 let chars_before_match =
4511 chunk[..byte_pos].chars().count() as u32;
4512 end_tag_offset =
4513 Some(char_position + chars_before_match);
4514 break 'outer;
4515 }
4516 char_position += chunk.chars().count() as u32;
4517 }
4518
4519 if let Some(end_tag_offset) = end_tag_offset {
4520 let cursor_is_before_end_tag = column <= end_tag_offset;
4521 if cursor_is_after_start_tag {
4522 if cursor_is_before_end_tag {
4523 insert_extra_newline = true;
4524 }
4525 let cursor_is_at_start_of_end_tag =
4526 column == end_tag_offset;
4527 if cursor_is_at_start_of_end_tag {
4528 indent_on_extra_newline.len = *len;
4529 }
4530 }
4531 cursor_is_before_end_tag
4532 } else {
4533 true
4534 }
4535 };
4536
4537 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4538 && cursor_is_before_end_tag_if_exists
4539 {
4540 if cursor_is_after_start_tag {
4541 indent_on_newline.len = *len;
4542 }
4543 Some(delimiter.clone())
4544 } else {
4545 None
4546 }
4547 });
4548
4549 (
4550 comment_delimiter,
4551 doc_delimiter,
4552 insert_extra_newline,
4553 indent_on_newline,
4554 indent_on_extra_newline,
4555 )
4556 } else {
4557 (
4558 None,
4559 None,
4560 false,
4561 IndentSize::default(),
4562 IndentSize::default(),
4563 )
4564 };
4565
4566 let prevent_auto_indent = doc_delimiter.is_some();
4567 let delimiter = comment_delimiter.or(doc_delimiter);
4568
4569 let capacity_for_delimiter =
4570 delimiter.as_deref().map(str::len).unwrap_or_default();
4571 let mut new_text = String::with_capacity(
4572 1 + capacity_for_delimiter
4573 + existing_indent.len as usize
4574 + indent_on_newline.len as usize
4575 + indent_on_extra_newline.len as usize,
4576 );
4577 new_text.push('\n');
4578 new_text.extend(existing_indent.chars());
4579 new_text.extend(indent_on_newline.chars());
4580
4581 if let Some(delimiter) = &delimiter {
4582 new_text.push_str(delimiter);
4583 }
4584
4585 if insert_extra_newline {
4586 new_text.push('\n');
4587 new_text.extend(existing_indent.chars());
4588 new_text.extend(indent_on_extra_newline.chars());
4589 }
4590
4591 let anchor = buffer.anchor_after(end);
4592 let new_selection = selection.map(|_| anchor);
4593 (
4594 ((start..end, new_text), prevent_auto_indent),
4595 (insert_extra_newline, new_selection),
4596 )
4597 })
4598 .unzip()
4599 };
4600
4601 let mut auto_indent_edits = Vec::new();
4602 let mut edits = Vec::new();
4603 for (edit, prevent_auto_indent) in edits_with_flags {
4604 if prevent_auto_indent {
4605 edits.push(edit);
4606 } else {
4607 auto_indent_edits.push(edit);
4608 }
4609 }
4610 if !edits.is_empty() {
4611 this.edit(edits, cx);
4612 }
4613 if !auto_indent_edits.is_empty() {
4614 this.edit_with_autoindent(auto_indent_edits, cx);
4615 }
4616
4617 let buffer = this.buffer.read(cx).snapshot(cx);
4618 let new_selections = selection_info
4619 .into_iter()
4620 .map(|(extra_newline_inserted, new_selection)| {
4621 let mut cursor = new_selection.end.to_point(&buffer);
4622 if extra_newline_inserted {
4623 cursor.row -= 1;
4624 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4625 }
4626 new_selection.map(|_| cursor)
4627 })
4628 .collect();
4629
4630 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4631 this.refresh_inline_completion(true, false, window, cx);
4632 });
4633 }
4634
4635 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4637
4638 let buffer = self.buffer.read(cx);
4639 let snapshot = buffer.snapshot(cx);
4640
4641 let mut edits = Vec::new();
4642 let mut rows = Vec::new();
4643
4644 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4645 let cursor = selection.head();
4646 let row = cursor.row;
4647
4648 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4649
4650 let newline = "\n".to_string();
4651 edits.push((start_of_line..start_of_line, newline));
4652
4653 rows.push(row + rows_inserted as u32);
4654 }
4655
4656 self.transact(window, cx, |editor, window, cx| {
4657 editor.edit(edits, cx);
4658
4659 editor.change_selections(Default::default(), window, cx, |s| {
4660 let mut index = 0;
4661 s.move_cursors_with(|map, _, _| {
4662 let row = rows[index];
4663 index += 1;
4664
4665 let point = Point::new(row, 0);
4666 let boundary = map.next_line_boundary(point).1;
4667 let clipped = map.clip_point(boundary, Bias::Left);
4668
4669 (clipped, SelectionGoal::None)
4670 });
4671 });
4672
4673 let mut indent_edits = Vec::new();
4674 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4675 for row in rows {
4676 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4677 for (row, indent) in indents {
4678 if indent.len == 0 {
4679 continue;
4680 }
4681
4682 let text = match indent.kind {
4683 IndentKind::Space => " ".repeat(indent.len as usize),
4684 IndentKind::Tab => "\t".repeat(indent.len as usize),
4685 };
4686 let point = Point::new(row.0, 0);
4687 indent_edits.push((point..point, text));
4688 }
4689 }
4690 editor.edit(indent_edits, cx);
4691 });
4692 }
4693
4694 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4696
4697 let buffer = self.buffer.read(cx);
4698 let snapshot = buffer.snapshot(cx);
4699
4700 let mut edits = Vec::new();
4701 let mut rows = Vec::new();
4702 let mut rows_inserted = 0;
4703
4704 for selection in self.selections.all_adjusted(cx) {
4705 let cursor = selection.head();
4706 let row = cursor.row;
4707
4708 let point = Point::new(row + 1, 0);
4709 let start_of_line = snapshot.clip_point(point, Bias::Left);
4710
4711 let newline = "\n".to_string();
4712 edits.push((start_of_line..start_of_line, newline));
4713
4714 rows_inserted += 1;
4715 rows.push(row + rows_inserted);
4716 }
4717
4718 self.transact(window, cx, |editor, window, cx| {
4719 editor.edit(edits, cx);
4720
4721 editor.change_selections(Default::default(), window, cx, |s| {
4722 let mut index = 0;
4723 s.move_cursors_with(|map, _, _| {
4724 let row = rows[index];
4725 index += 1;
4726
4727 let point = Point::new(row, 0);
4728 let boundary = map.next_line_boundary(point).1;
4729 let clipped = map.clip_point(boundary, Bias::Left);
4730
4731 (clipped, SelectionGoal::None)
4732 });
4733 });
4734
4735 let mut indent_edits = Vec::new();
4736 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4737 for row in rows {
4738 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4739 for (row, indent) in indents {
4740 if indent.len == 0 {
4741 continue;
4742 }
4743
4744 let text = match indent.kind {
4745 IndentKind::Space => " ".repeat(indent.len as usize),
4746 IndentKind::Tab => "\t".repeat(indent.len as usize),
4747 };
4748 let point = Point::new(row.0, 0);
4749 indent_edits.push((point..point, text));
4750 }
4751 }
4752 editor.edit(indent_edits, cx);
4753 });
4754 }
4755
4756 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4757 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4758 original_indent_columns: Vec::new(),
4759 });
4760 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4761 }
4762
4763 fn insert_with_autoindent_mode(
4764 &mut self,
4765 text: &str,
4766 autoindent_mode: Option<AutoindentMode>,
4767 window: &mut Window,
4768 cx: &mut Context<Self>,
4769 ) {
4770 if self.read_only(cx) {
4771 return;
4772 }
4773
4774 let text: Arc<str> = text.into();
4775 self.transact(window, cx, |this, window, cx| {
4776 let old_selections = this.selections.all_adjusted(cx);
4777 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4778 let anchors = {
4779 let snapshot = buffer.read(cx);
4780 old_selections
4781 .iter()
4782 .map(|s| {
4783 let anchor = snapshot.anchor_after(s.head());
4784 s.map(|_| anchor)
4785 })
4786 .collect::<Vec<_>>()
4787 };
4788 buffer.edit(
4789 old_selections
4790 .iter()
4791 .map(|s| (s.start..s.end, text.clone())),
4792 autoindent_mode,
4793 cx,
4794 );
4795 anchors
4796 });
4797
4798 this.change_selections(Default::default(), window, cx, |s| {
4799 s.select_anchors(selection_anchors);
4800 });
4801
4802 cx.notify();
4803 });
4804 }
4805
4806 fn trigger_completion_on_input(
4807 &mut self,
4808 text: &str,
4809 trigger_in_words: bool,
4810 window: &mut Window,
4811 cx: &mut Context<Self>,
4812 ) {
4813 let completions_source = self
4814 .context_menu
4815 .borrow()
4816 .as_ref()
4817 .and_then(|menu| match menu {
4818 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4819 CodeContextMenu::CodeActions(_) => None,
4820 });
4821
4822 match completions_source {
4823 Some(CompletionsMenuSource::Words) => {
4824 self.show_word_completions(&ShowWordCompletions, window, cx)
4825 }
4826 Some(CompletionsMenuSource::Normal)
4827 | Some(CompletionsMenuSource::SnippetChoices)
4828 | None
4829 if self.is_completion_trigger(
4830 text,
4831 trigger_in_words,
4832 completions_source.is_some(),
4833 cx,
4834 ) =>
4835 {
4836 self.show_completions(
4837 &ShowCompletions {
4838 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4839 },
4840 window,
4841 cx,
4842 )
4843 }
4844 _ => {
4845 self.hide_context_menu(window, cx);
4846 }
4847 }
4848 }
4849
4850 fn is_completion_trigger(
4851 &self,
4852 text: &str,
4853 trigger_in_words: bool,
4854 menu_is_open: bool,
4855 cx: &mut Context<Self>,
4856 ) -> bool {
4857 let position = self.selections.newest_anchor().head();
4858 let multibuffer = self.buffer.read(cx);
4859 let Some(buffer) = position
4860 .buffer_id
4861 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4862 else {
4863 return false;
4864 };
4865
4866 if let Some(completion_provider) = &self.completion_provider {
4867 completion_provider.is_completion_trigger(
4868 &buffer,
4869 position.text_anchor,
4870 text,
4871 trigger_in_words,
4872 menu_is_open,
4873 cx,
4874 )
4875 } else {
4876 false
4877 }
4878 }
4879
4880 /// If any empty selections is touching the start of its innermost containing autoclose
4881 /// region, expand it to select the brackets.
4882 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4883 let selections = self.selections.all::<usize>(cx);
4884 let buffer = self.buffer.read(cx).read(cx);
4885 let new_selections = self
4886 .selections_with_autoclose_regions(selections, &buffer)
4887 .map(|(mut selection, region)| {
4888 if !selection.is_empty() {
4889 return selection;
4890 }
4891
4892 if let Some(region) = region {
4893 let mut range = region.range.to_offset(&buffer);
4894 if selection.start == range.start && range.start >= region.pair.start.len() {
4895 range.start -= region.pair.start.len();
4896 if buffer.contains_str_at(range.start, ®ion.pair.start)
4897 && buffer.contains_str_at(range.end, ®ion.pair.end)
4898 {
4899 range.end += region.pair.end.len();
4900 selection.start = range.start;
4901 selection.end = range.end;
4902
4903 return selection;
4904 }
4905 }
4906 }
4907
4908 let always_treat_brackets_as_autoclosed = buffer
4909 .language_settings_at(selection.start, cx)
4910 .always_treat_brackets_as_autoclosed;
4911
4912 if !always_treat_brackets_as_autoclosed {
4913 return selection;
4914 }
4915
4916 if let Some(scope) = buffer.language_scope_at(selection.start) {
4917 for (pair, enabled) in scope.brackets() {
4918 if !enabled || !pair.close {
4919 continue;
4920 }
4921
4922 if buffer.contains_str_at(selection.start, &pair.end) {
4923 let pair_start_len = pair.start.len();
4924 if buffer.contains_str_at(
4925 selection.start.saturating_sub(pair_start_len),
4926 &pair.start,
4927 ) {
4928 selection.start -= pair_start_len;
4929 selection.end += pair.end.len();
4930
4931 return selection;
4932 }
4933 }
4934 }
4935 }
4936
4937 selection
4938 })
4939 .collect();
4940
4941 drop(buffer);
4942 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4943 selections.select(new_selections)
4944 });
4945 }
4946
4947 /// Iterate the given selections, and for each one, find the smallest surrounding
4948 /// autoclose region. This uses the ordering of the selections and the autoclose
4949 /// regions to avoid repeated comparisons.
4950 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4951 &'a self,
4952 selections: impl IntoIterator<Item = Selection<D>>,
4953 buffer: &'a MultiBufferSnapshot,
4954 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4955 let mut i = 0;
4956 let mut regions = self.autoclose_regions.as_slice();
4957 selections.into_iter().map(move |selection| {
4958 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4959
4960 let mut enclosing = None;
4961 while let Some(pair_state) = regions.get(i) {
4962 if pair_state.range.end.to_offset(buffer) < range.start {
4963 regions = ®ions[i + 1..];
4964 i = 0;
4965 } else if pair_state.range.start.to_offset(buffer) > range.end {
4966 break;
4967 } else {
4968 if pair_state.selection_id == selection.id {
4969 enclosing = Some(pair_state);
4970 }
4971 i += 1;
4972 }
4973 }
4974
4975 (selection, enclosing)
4976 })
4977 }
4978
4979 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4980 fn invalidate_autoclose_regions(
4981 &mut self,
4982 mut selections: &[Selection<Anchor>],
4983 buffer: &MultiBufferSnapshot,
4984 ) {
4985 self.autoclose_regions.retain(|state| {
4986 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4987 return false;
4988 }
4989
4990 let mut i = 0;
4991 while let Some(selection) = selections.get(i) {
4992 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4993 selections = &selections[1..];
4994 continue;
4995 }
4996 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4997 break;
4998 }
4999 if selection.id == state.selection_id {
5000 return true;
5001 } else {
5002 i += 1;
5003 }
5004 }
5005 false
5006 });
5007 }
5008
5009 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5010 let offset = position.to_offset(buffer);
5011 let (word_range, kind) = buffer.surrounding_word(offset, true);
5012 if offset > word_range.start && kind == Some(CharKind::Word) {
5013 Some(
5014 buffer
5015 .text_for_range(word_range.start..offset)
5016 .collect::<String>(),
5017 )
5018 } else {
5019 None
5020 }
5021 }
5022
5023 pub fn toggle_inline_values(
5024 &mut self,
5025 _: &ToggleInlineValues,
5026 _: &mut Window,
5027 cx: &mut Context<Self>,
5028 ) {
5029 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5030
5031 self.refresh_inline_values(cx);
5032 }
5033
5034 pub fn toggle_inlay_hints(
5035 &mut self,
5036 _: &ToggleInlayHints,
5037 _: &mut Window,
5038 cx: &mut Context<Self>,
5039 ) {
5040 self.refresh_inlay_hints(
5041 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5042 cx,
5043 );
5044 }
5045
5046 pub fn inlay_hints_enabled(&self) -> bool {
5047 self.inlay_hint_cache.enabled
5048 }
5049
5050 pub fn inline_values_enabled(&self) -> bool {
5051 self.inline_value_cache.enabled
5052 }
5053
5054 #[cfg(any(test, feature = "test-support"))]
5055 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5056 self.display_map
5057 .read(cx)
5058 .current_inlays()
5059 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5060 .cloned()
5061 .collect()
5062 }
5063
5064 #[cfg(any(test, feature = "test-support"))]
5065 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5066 self.display_map
5067 .read(cx)
5068 .current_inlays()
5069 .cloned()
5070 .collect()
5071 }
5072
5073 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5074 if self.semantics_provider.is_none() || !self.mode.is_full() {
5075 return;
5076 }
5077
5078 let reason_description = reason.description();
5079 let ignore_debounce = matches!(
5080 reason,
5081 InlayHintRefreshReason::SettingsChange(_)
5082 | InlayHintRefreshReason::Toggle(_)
5083 | InlayHintRefreshReason::ExcerptsRemoved(_)
5084 | InlayHintRefreshReason::ModifiersChanged(_)
5085 );
5086 let (invalidate_cache, required_languages) = match reason {
5087 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5088 match self.inlay_hint_cache.modifiers_override(enabled) {
5089 Some(enabled) => {
5090 if enabled {
5091 (InvalidationStrategy::RefreshRequested, None)
5092 } else {
5093 self.splice_inlays(
5094 &self
5095 .visible_inlay_hints(cx)
5096 .iter()
5097 .map(|inlay| inlay.id)
5098 .collect::<Vec<InlayId>>(),
5099 Vec::new(),
5100 cx,
5101 );
5102 return;
5103 }
5104 }
5105 None => return,
5106 }
5107 }
5108 InlayHintRefreshReason::Toggle(enabled) => {
5109 if self.inlay_hint_cache.toggle(enabled) {
5110 if enabled {
5111 (InvalidationStrategy::RefreshRequested, None)
5112 } else {
5113 self.splice_inlays(
5114 &self
5115 .visible_inlay_hints(cx)
5116 .iter()
5117 .map(|inlay| inlay.id)
5118 .collect::<Vec<InlayId>>(),
5119 Vec::new(),
5120 cx,
5121 );
5122 return;
5123 }
5124 } else {
5125 return;
5126 }
5127 }
5128 InlayHintRefreshReason::SettingsChange(new_settings) => {
5129 match self.inlay_hint_cache.update_settings(
5130 &self.buffer,
5131 new_settings,
5132 self.visible_inlay_hints(cx),
5133 cx,
5134 ) {
5135 ControlFlow::Break(Some(InlaySplice {
5136 to_remove,
5137 to_insert,
5138 })) => {
5139 self.splice_inlays(&to_remove, to_insert, cx);
5140 return;
5141 }
5142 ControlFlow::Break(None) => return,
5143 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5144 }
5145 }
5146 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5147 if let Some(InlaySplice {
5148 to_remove,
5149 to_insert,
5150 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5151 {
5152 self.splice_inlays(&to_remove, to_insert, cx);
5153 }
5154 self.display_map.update(cx, |display_map, _| {
5155 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5156 });
5157 return;
5158 }
5159 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5160 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5161 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5162 }
5163 InlayHintRefreshReason::RefreshRequested => {
5164 (InvalidationStrategy::RefreshRequested, None)
5165 }
5166 };
5167
5168 if let Some(InlaySplice {
5169 to_remove,
5170 to_insert,
5171 }) = self.inlay_hint_cache.spawn_hint_refresh(
5172 reason_description,
5173 self.visible_excerpts(required_languages.as_ref(), cx),
5174 invalidate_cache,
5175 ignore_debounce,
5176 cx,
5177 ) {
5178 self.splice_inlays(&to_remove, to_insert, cx);
5179 }
5180 }
5181
5182 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5183 self.display_map
5184 .read(cx)
5185 .current_inlays()
5186 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5187 .cloned()
5188 .collect()
5189 }
5190
5191 pub fn visible_excerpts(
5192 &self,
5193 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5194 cx: &mut Context<Editor>,
5195 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5196 let Some(project) = self.project.as_ref() else {
5197 return HashMap::default();
5198 };
5199 let project = project.read(cx);
5200 let multi_buffer = self.buffer().read(cx);
5201 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5202 let multi_buffer_visible_start = self
5203 .scroll_manager
5204 .anchor()
5205 .anchor
5206 .to_point(&multi_buffer_snapshot);
5207 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5208 multi_buffer_visible_start
5209 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5210 Bias::Left,
5211 );
5212 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5213 multi_buffer_snapshot
5214 .range_to_buffer_ranges(multi_buffer_visible_range)
5215 .into_iter()
5216 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5217 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5218 let buffer_file = project::File::from_dyn(buffer.file())?;
5219 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5220 let worktree_entry = buffer_worktree
5221 .read(cx)
5222 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5223 if worktree_entry.is_ignored {
5224 return None;
5225 }
5226
5227 let language = buffer.language()?;
5228 if let Some(restrict_to_languages) = restrict_to_languages {
5229 if !restrict_to_languages.contains(language) {
5230 return None;
5231 }
5232 }
5233 Some((
5234 excerpt_id,
5235 (
5236 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5237 buffer.version().clone(),
5238 excerpt_visible_range,
5239 ),
5240 ))
5241 })
5242 .collect()
5243 }
5244
5245 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5246 TextLayoutDetails {
5247 text_system: window.text_system().clone(),
5248 editor_style: self.style.clone().unwrap(),
5249 rem_size: window.rem_size(),
5250 scroll_anchor: self.scroll_manager.anchor(),
5251 visible_rows: self.visible_line_count(),
5252 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5253 }
5254 }
5255
5256 pub fn splice_inlays(
5257 &self,
5258 to_remove: &[InlayId],
5259 to_insert: Vec<Inlay>,
5260 cx: &mut Context<Self>,
5261 ) {
5262 self.display_map.update(cx, |display_map, cx| {
5263 display_map.splice_inlays(to_remove, to_insert, cx)
5264 });
5265 cx.notify();
5266 }
5267
5268 fn trigger_on_type_formatting(
5269 &self,
5270 input: String,
5271 window: &mut Window,
5272 cx: &mut Context<Self>,
5273 ) -> Option<Task<Result<()>>> {
5274 if input.len() != 1 {
5275 return None;
5276 }
5277
5278 let project = self.project.as_ref()?;
5279 let position = self.selections.newest_anchor().head();
5280 let (buffer, buffer_position) = self
5281 .buffer
5282 .read(cx)
5283 .text_anchor_for_position(position, cx)?;
5284
5285 let settings = language_settings::language_settings(
5286 buffer
5287 .read(cx)
5288 .language_at(buffer_position)
5289 .map(|l| l.name()),
5290 buffer.read(cx).file(),
5291 cx,
5292 );
5293 if !settings.use_on_type_format {
5294 return None;
5295 }
5296
5297 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5298 // hence we do LSP request & edit on host side only — add formats to host's history.
5299 let push_to_lsp_host_history = true;
5300 // If this is not the host, append its history with new edits.
5301 let push_to_client_history = project.read(cx).is_via_collab();
5302
5303 let on_type_formatting = project.update(cx, |project, cx| {
5304 project.on_type_format(
5305 buffer.clone(),
5306 buffer_position,
5307 input,
5308 push_to_lsp_host_history,
5309 cx,
5310 )
5311 });
5312 Some(cx.spawn_in(window, async move |editor, cx| {
5313 if let Some(transaction) = on_type_formatting.await? {
5314 if push_to_client_history {
5315 buffer
5316 .update(cx, |buffer, _| {
5317 buffer.push_transaction(transaction, Instant::now());
5318 buffer.finalize_last_transaction();
5319 })
5320 .ok();
5321 }
5322 editor.update(cx, |editor, cx| {
5323 editor.refresh_document_highlights(cx);
5324 })?;
5325 }
5326 Ok(())
5327 }))
5328 }
5329
5330 pub fn show_word_completions(
5331 &mut self,
5332 _: &ShowWordCompletions,
5333 window: &mut Window,
5334 cx: &mut Context<Self>,
5335 ) {
5336 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5337 }
5338
5339 pub fn show_completions(
5340 &mut self,
5341 options: &ShowCompletions,
5342 window: &mut Window,
5343 cx: &mut Context<Self>,
5344 ) {
5345 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5346 }
5347
5348 fn open_or_update_completions_menu(
5349 &mut self,
5350 requested_source: Option<CompletionsMenuSource>,
5351 trigger: Option<&str>,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 if self.pending_rename.is_some() {
5356 return;
5357 }
5358
5359 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5360
5361 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5362 // inserted and selected. To handle that case, the start of the selection is used so that
5363 // the menu starts with all choices.
5364 let position = self
5365 .selections
5366 .newest_anchor()
5367 .start
5368 .bias_right(&multibuffer_snapshot);
5369 if position.diff_base_anchor.is_some() {
5370 return;
5371 }
5372 let (buffer, buffer_position) =
5373 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5374 output
5375 } else {
5376 return;
5377 };
5378 let buffer_snapshot = buffer.read(cx).snapshot();
5379
5380 let query: Option<Arc<String>> =
5381 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5382
5383 drop(multibuffer_snapshot);
5384
5385 let provider = match requested_source {
5386 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5387 Some(CompletionsMenuSource::Words) => None,
5388 Some(CompletionsMenuSource::SnippetChoices) => {
5389 log::error!("bug: SnippetChoices requested_source is not handled");
5390 None
5391 }
5392 };
5393
5394 let sort_completions = provider
5395 .as_ref()
5396 .map_or(false, |provider| provider.sort_completions());
5397
5398 let filter_completions = provider
5399 .as_ref()
5400 .map_or(true, |provider| provider.filter_completions());
5401
5402 let trigger_kind = match trigger {
5403 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5404 CompletionTriggerKind::TRIGGER_CHARACTER
5405 }
5406 _ => CompletionTriggerKind::INVOKED,
5407 };
5408 let completion_context = CompletionContext {
5409 trigger_character: trigger.and_then(|trigger| {
5410 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5411 Some(String::from(trigger))
5412 } else {
5413 None
5414 }
5415 }),
5416 trigger_kind,
5417 };
5418
5419 // Hide the current completions menu when a trigger char is typed. Without this, cached
5420 // completions from before the trigger char may be reused (#32774). Snippet choices could
5421 // involve trigger chars, so this is skipped in that case.
5422 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5423 {
5424 let menu_is_open = matches!(
5425 self.context_menu.borrow().as_ref(),
5426 Some(CodeContextMenu::Completions(_))
5427 );
5428 if menu_is_open {
5429 self.hide_context_menu(window, cx);
5430 }
5431 }
5432
5433 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5434 if filter_completions {
5435 menu.filter(query.clone(), provider.clone(), window, cx);
5436 }
5437 // When `is_incomplete` is false, no need to re-query completions when the current query
5438 // is a suffix of the initial query.
5439 if !menu.is_incomplete {
5440 // If the new query is a suffix of the old query (typing more characters) and
5441 // the previous result was complete, the existing completions can be filtered.
5442 //
5443 // Note that this is always true for snippet completions.
5444 let query_matches = match (&menu.initial_query, &query) {
5445 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5446 (None, _) => true,
5447 _ => false,
5448 };
5449 if query_matches {
5450 let position_matches = if menu.initial_position == position {
5451 true
5452 } else {
5453 let snapshot = self.buffer.read(cx).read(cx);
5454 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5455 };
5456 if position_matches {
5457 return;
5458 }
5459 }
5460 }
5461 };
5462
5463 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5464 buffer_snapshot.surrounding_word(buffer_position, false)
5465 {
5466 let word_to_exclude = buffer_snapshot
5467 .text_for_range(word_range.clone())
5468 .collect::<String>();
5469 (
5470 buffer_snapshot.anchor_before(word_range.start)
5471 ..buffer_snapshot.anchor_after(buffer_position),
5472 Some(word_to_exclude),
5473 )
5474 } else {
5475 (buffer_position..buffer_position, None)
5476 };
5477
5478 let language = buffer_snapshot
5479 .language_at(buffer_position)
5480 .map(|language| language.name());
5481
5482 let completion_settings =
5483 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5484
5485 let show_completion_documentation = buffer_snapshot
5486 .settings_at(buffer_position, cx)
5487 .show_completion_documentation;
5488
5489 // The document can be large, so stay in reasonable bounds when searching for words,
5490 // otherwise completion pop-up might be slow to appear.
5491 const WORD_LOOKUP_ROWS: u32 = 5_000;
5492 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5493 let min_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5495 Bias::Left,
5496 );
5497 let max_word_search = buffer_snapshot.clip_point(
5498 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5499 Bias::Right,
5500 );
5501 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5502 ..buffer_snapshot.point_to_offset(max_word_search);
5503
5504 let skip_digits = query
5505 .as_ref()
5506 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5507
5508 let (mut words, provider_responses) = match &provider {
5509 Some(provider) => {
5510 let provider_responses = provider.completions(
5511 position.excerpt_id,
5512 &buffer,
5513 buffer_position,
5514 completion_context,
5515 window,
5516 cx,
5517 );
5518
5519 let words = match completion_settings.words {
5520 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5521 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5522 .background_spawn(async move {
5523 buffer_snapshot.words_in_range(WordsQuery {
5524 fuzzy_contents: None,
5525 range: word_search_range,
5526 skip_digits,
5527 })
5528 }),
5529 };
5530
5531 (words, provider_responses)
5532 }
5533 None => (
5534 cx.background_spawn(async move {
5535 buffer_snapshot.words_in_range(WordsQuery {
5536 fuzzy_contents: None,
5537 range: word_search_range,
5538 skip_digits,
5539 })
5540 }),
5541 Task::ready(Ok(Vec::new())),
5542 ),
5543 };
5544
5545 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5546
5547 let id = post_inc(&mut self.next_completion_id);
5548 let task = cx.spawn_in(window, async move |editor, cx| {
5549 let Ok(()) = editor.update(cx, |this, _| {
5550 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5551 }) else {
5552 return;
5553 };
5554
5555 // TODO: Ideally completions from different sources would be selectively re-queried, so
5556 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5557 let mut completions = Vec::new();
5558 let mut is_incomplete = false;
5559 if let Some(provider_responses) = provider_responses.await.log_err() {
5560 if !provider_responses.is_empty() {
5561 for response in provider_responses {
5562 completions.extend(response.completions);
5563 is_incomplete = is_incomplete || response.is_incomplete;
5564 }
5565 if completion_settings.words == WordsCompletionMode::Fallback {
5566 words = Task::ready(BTreeMap::default());
5567 }
5568 }
5569 }
5570
5571 let mut words = words.await;
5572 if let Some(word_to_exclude) = &word_to_exclude {
5573 words.remove(word_to_exclude);
5574 }
5575 for lsp_completion in &completions {
5576 words.remove(&lsp_completion.new_text);
5577 }
5578 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5579 replace_range: word_replace_range.clone(),
5580 new_text: word.clone(),
5581 label: CodeLabel::plain(word, None),
5582 icon_path: None,
5583 documentation: None,
5584 source: CompletionSource::BufferWord {
5585 word_range,
5586 resolved: false,
5587 },
5588 insert_text_mode: Some(InsertTextMode::AS_IS),
5589 confirm: None,
5590 }));
5591
5592 let menu = if completions.is_empty() {
5593 None
5594 } else {
5595 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5596 let languages = editor
5597 .workspace
5598 .as_ref()
5599 .and_then(|(workspace, _)| workspace.upgrade())
5600 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5601 let menu = CompletionsMenu::new(
5602 id,
5603 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5604 sort_completions,
5605 show_completion_documentation,
5606 position,
5607 query.clone(),
5608 is_incomplete,
5609 buffer.clone(),
5610 completions.into(),
5611 snippet_sort_order,
5612 languages,
5613 language,
5614 cx,
5615 );
5616
5617 let query = if filter_completions { query } else { None };
5618 let matches_task = if let Some(query) = query {
5619 menu.do_async_filtering(query, cx)
5620 } else {
5621 Task::ready(menu.unfiltered_matches())
5622 };
5623 (menu, matches_task)
5624 }) else {
5625 return;
5626 };
5627
5628 let matches = matches_task.await;
5629
5630 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5631 // Newer menu already set, so exit.
5632 match editor.context_menu.borrow().as_ref() {
5633 Some(CodeContextMenu::Completions(prev_menu)) => {
5634 if prev_menu.id > id {
5635 return;
5636 }
5637 }
5638 _ => {}
5639 };
5640
5641 // Only valid to take prev_menu because it the new menu is immediately set
5642 // below, or the menu is hidden.
5643 match editor.context_menu.borrow_mut().take() {
5644 Some(CodeContextMenu::Completions(prev_menu)) => {
5645 let position_matches =
5646 if prev_menu.initial_position == menu.initial_position {
5647 true
5648 } else {
5649 let snapshot = editor.buffer.read(cx).read(cx);
5650 prev_menu.initial_position.to_offset(&snapshot)
5651 == menu.initial_position.to_offset(&snapshot)
5652 };
5653 if position_matches {
5654 // Preserve markdown cache before `set_filter_results` because it will
5655 // try to populate the documentation cache.
5656 menu.preserve_markdown_cache(prev_menu);
5657 }
5658 }
5659 _ => {}
5660 };
5661
5662 menu.set_filter_results(matches, provider, window, cx);
5663 }) else {
5664 return;
5665 };
5666
5667 menu.visible().then_some(menu)
5668 };
5669
5670 editor
5671 .update_in(cx, |editor, window, cx| {
5672 if editor.focus_handle.is_focused(window) {
5673 if let Some(menu) = menu {
5674 *editor.context_menu.borrow_mut() =
5675 Some(CodeContextMenu::Completions(menu));
5676
5677 crate::hover_popover::hide_hover(editor, cx);
5678 if editor.show_edit_predictions_in_menu() {
5679 editor.update_visible_inline_completion(window, cx);
5680 } else {
5681 editor.discard_inline_completion(false, cx);
5682 }
5683
5684 cx.notify();
5685 return;
5686 }
5687 }
5688
5689 if editor.completion_tasks.len() <= 1 {
5690 // If there are no more completion tasks and the last menu was empty, we should hide it.
5691 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5692 // If it was already hidden and we don't show inline completions in the menu, we should
5693 // also show the inline-completion when available.
5694 if was_hidden && editor.show_edit_predictions_in_menu() {
5695 editor.update_visible_inline_completion(window, cx);
5696 }
5697 }
5698 })
5699 .ok();
5700 });
5701
5702 self.completion_tasks.push((id, task));
5703 }
5704
5705 #[cfg(feature = "test-support")]
5706 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5707 let menu = self.context_menu.borrow();
5708 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5709 let completions = menu.completions.borrow();
5710 Some(completions.to_vec())
5711 } else {
5712 None
5713 }
5714 }
5715
5716 pub fn with_completions_menu_matching_id<R>(
5717 &self,
5718 id: CompletionId,
5719 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5720 ) -> R {
5721 let mut context_menu = self.context_menu.borrow_mut();
5722 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5723 return f(None);
5724 };
5725 if completions_menu.id != id {
5726 return f(None);
5727 }
5728 f(Some(completions_menu))
5729 }
5730
5731 pub fn confirm_completion(
5732 &mut self,
5733 action: &ConfirmCompletion,
5734 window: &mut Window,
5735 cx: &mut Context<Self>,
5736 ) -> Option<Task<Result<()>>> {
5737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5738 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5739 }
5740
5741 pub fn confirm_completion_insert(
5742 &mut self,
5743 _: &ConfirmCompletionInsert,
5744 window: &mut Window,
5745 cx: &mut Context<Self>,
5746 ) -> Option<Task<Result<()>>> {
5747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5748 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5749 }
5750
5751 pub fn confirm_completion_replace(
5752 &mut self,
5753 _: &ConfirmCompletionReplace,
5754 window: &mut Window,
5755 cx: &mut Context<Self>,
5756 ) -> Option<Task<Result<()>>> {
5757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5758 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5759 }
5760
5761 pub fn compose_completion(
5762 &mut self,
5763 action: &ComposeCompletion,
5764 window: &mut Window,
5765 cx: &mut Context<Self>,
5766 ) -> Option<Task<Result<()>>> {
5767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5768 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5769 }
5770
5771 fn do_completion(
5772 &mut self,
5773 item_ix: Option<usize>,
5774 intent: CompletionIntent,
5775 window: &mut Window,
5776 cx: &mut Context<Editor>,
5777 ) -> Option<Task<Result<()>>> {
5778 use language::ToOffset as _;
5779
5780 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5781 else {
5782 return None;
5783 };
5784
5785 let candidate_id = {
5786 let entries = completions_menu.entries.borrow();
5787 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5788 if self.show_edit_predictions_in_menu() {
5789 self.discard_inline_completion(true, cx);
5790 }
5791 mat.candidate_id
5792 };
5793
5794 let completion = completions_menu
5795 .completions
5796 .borrow()
5797 .get(candidate_id)?
5798 .clone();
5799 cx.stop_propagation();
5800
5801 let buffer_handle = completions_menu.buffer.clone();
5802
5803 let CompletionEdit {
5804 new_text,
5805 snippet,
5806 replace_range,
5807 } = process_completion_for_edit(
5808 &completion,
5809 intent,
5810 &buffer_handle,
5811 &completions_menu.initial_position.text_anchor,
5812 cx,
5813 );
5814
5815 let buffer = buffer_handle.read(cx);
5816 let snapshot = self.buffer.read(cx).snapshot(cx);
5817 let newest_anchor = self.selections.newest_anchor();
5818 let replace_range_multibuffer = {
5819 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5820 let multibuffer_anchor = snapshot
5821 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5822 .unwrap()
5823 ..snapshot
5824 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5825 .unwrap();
5826 multibuffer_anchor.start.to_offset(&snapshot)
5827 ..multibuffer_anchor.end.to_offset(&snapshot)
5828 };
5829 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5830 return None;
5831 }
5832
5833 let old_text = buffer
5834 .text_for_range(replace_range.clone())
5835 .collect::<String>();
5836 let lookbehind = newest_anchor
5837 .start
5838 .text_anchor
5839 .to_offset(buffer)
5840 .saturating_sub(replace_range.start);
5841 let lookahead = replace_range
5842 .end
5843 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5844 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5845 let suffix = &old_text[lookbehind.min(old_text.len())..];
5846
5847 let selections = self.selections.all::<usize>(cx);
5848 let mut ranges = Vec::new();
5849 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5850
5851 for selection in &selections {
5852 let range = if selection.id == newest_anchor.id {
5853 replace_range_multibuffer.clone()
5854 } else {
5855 let mut range = selection.range();
5856
5857 // if prefix is present, don't duplicate it
5858 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5859 range.start = range.start.saturating_sub(lookbehind);
5860
5861 // if suffix is also present, mimic the newest cursor and replace it
5862 if selection.id != newest_anchor.id
5863 && snapshot.contains_str_at(range.end, suffix)
5864 {
5865 range.end += lookahead;
5866 }
5867 }
5868 range
5869 };
5870
5871 ranges.push(range.clone());
5872
5873 if !self.linked_edit_ranges.is_empty() {
5874 let start_anchor = snapshot.anchor_before(range.start);
5875 let end_anchor = snapshot.anchor_after(range.end);
5876 if let Some(ranges) = self
5877 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5878 {
5879 for (buffer, edits) in ranges {
5880 linked_edits
5881 .entry(buffer.clone())
5882 .or_default()
5883 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5884 }
5885 }
5886 }
5887 }
5888
5889 let common_prefix_len = old_text
5890 .chars()
5891 .zip(new_text.chars())
5892 .take_while(|(a, b)| a == b)
5893 .map(|(a, _)| a.len_utf8())
5894 .sum::<usize>();
5895
5896 cx.emit(EditorEvent::InputHandled {
5897 utf16_range_to_replace: None,
5898 text: new_text[common_prefix_len..].into(),
5899 });
5900
5901 self.transact(window, cx, |editor, window, cx| {
5902 if let Some(mut snippet) = snippet {
5903 snippet.text = new_text.to_string();
5904 editor
5905 .insert_snippet(&ranges, snippet, window, cx)
5906 .log_err();
5907 } else {
5908 editor.buffer.update(cx, |multi_buffer, cx| {
5909 let auto_indent = match completion.insert_text_mode {
5910 Some(InsertTextMode::AS_IS) => None,
5911 _ => editor.autoindent_mode.clone(),
5912 };
5913 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5914 multi_buffer.edit(edits, auto_indent, cx);
5915 });
5916 }
5917 for (buffer, edits) in linked_edits {
5918 buffer.update(cx, |buffer, cx| {
5919 let snapshot = buffer.snapshot();
5920 let edits = edits
5921 .into_iter()
5922 .map(|(range, text)| {
5923 use text::ToPoint as TP;
5924 let end_point = TP::to_point(&range.end, &snapshot);
5925 let start_point = TP::to_point(&range.start, &snapshot);
5926 (start_point..end_point, text)
5927 })
5928 .sorted_by_key(|(range, _)| range.start);
5929 buffer.edit(edits, None, cx);
5930 })
5931 }
5932
5933 editor.refresh_inline_completion(true, false, window, cx);
5934 });
5935 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5936
5937 let show_new_completions_on_confirm = completion
5938 .confirm
5939 .as_ref()
5940 .map_or(false, |confirm| confirm(intent, window, cx));
5941 if show_new_completions_on_confirm {
5942 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5943 }
5944
5945 let provider = self.completion_provider.as_ref()?;
5946 drop(completion);
5947 let apply_edits = provider.apply_additional_edits_for_completion(
5948 buffer_handle,
5949 completions_menu.completions.clone(),
5950 candidate_id,
5951 true,
5952 cx,
5953 );
5954
5955 let editor_settings = EditorSettings::get_global(cx);
5956 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5957 // After the code completion is finished, users often want to know what signatures are needed.
5958 // so we should automatically call signature_help
5959 self.show_signature_help(&ShowSignatureHelp, window, cx);
5960 }
5961
5962 Some(cx.foreground_executor().spawn(async move {
5963 apply_edits.await?;
5964 Ok(())
5965 }))
5966 }
5967
5968 pub fn toggle_code_actions(
5969 &mut self,
5970 action: &ToggleCodeActions,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) {
5974 let quick_launch = action.quick_launch;
5975 let mut context_menu = self.context_menu.borrow_mut();
5976 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5977 if code_actions.deployed_from == action.deployed_from {
5978 // Toggle if we're selecting the same one
5979 *context_menu = None;
5980 cx.notify();
5981 return;
5982 } else {
5983 // Otherwise, clear it and start a new one
5984 *context_menu = None;
5985 cx.notify();
5986 }
5987 }
5988 drop(context_menu);
5989 let snapshot = self.snapshot(window, cx);
5990 let deployed_from = action.deployed_from.clone();
5991 let action = action.clone();
5992 self.completion_tasks.clear();
5993 self.discard_inline_completion(false, cx);
5994
5995 let multibuffer_point = match &action.deployed_from {
5996 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5997 DisplayPoint::new(*row, 0).to_point(&snapshot)
5998 }
5999 _ => self.selections.newest::<Point>(cx).head(),
6000 };
6001 let Some((buffer, buffer_row)) = snapshot
6002 .buffer_snapshot
6003 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6004 .and_then(|(buffer_snapshot, range)| {
6005 self.buffer()
6006 .read(cx)
6007 .buffer(buffer_snapshot.remote_id())
6008 .map(|buffer| (buffer, range.start.row))
6009 })
6010 else {
6011 return;
6012 };
6013 let buffer_id = buffer.read(cx).remote_id();
6014 let tasks = self
6015 .tasks
6016 .get(&(buffer_id, buffer_row))
6017 .map(|t| Arc::new(t.to_owned()));
6018
6019 if !self.focus_handle.is_focused(window) {
6020 return;
6021 }
6022 let project = self.project.clone();
6023
6024 let code_actions_task = match deployed_from {
6025 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6026 _ => self.code_actions(buffer_row, window, cx),
6027 };
6028
6029 let runnable_task = match deployed_from {
6030 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6031 _ => {
6032 let mut task_context_task = Task::ready(None);
6033 if let Some(tasks) = &tasks {
6034 if let Some(project) = project {
6035 task_context_task =
6036 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6037 }
6038 }
6039
6040 cx.spawn_in(window, {
6041 let buffer = buffer.clone();
6042 async move |editor, cx| {
6043 let task_context = task_context_task.await;
6044
6045 let resolved_tasks =
6046 tasks
6047 .zip(task_context.clone())
6048 .map(|(tasks, task_context)| ResolvedTasks {
6049 templates: tasks.resolve(&task_context).collect(),
6050 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6051 multibuffer_point.row,
6052 tasks.column,
6053 )),
6054 });
6055 let debug_scenarios = editor
6056 .update(cx, |editor, cx| {
6057 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6058 })?
6059 .await;
6060 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6061 }
6062 })
6063 }
6064 };
6065
6066 cx.spawn_in(window, async move |editor, cx| {
6067 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6068 let code_actions = code_actions_task.await;
6069 let spawn_straight_away = quick_launch
6070 && resolved_tasks
6071 .as_ref()
6072 .map_or(false, |tasks| tasks.templates.len() == 1)
6073 && code_actions
6074 .as_ref()
6075 .map_or(true, |actions| actions.is_empty())
6076 && debug_scenarios.is_empty();
6077
6078 editor.update_in(cx, |editor, window, cx| {
6079 crate::hover_popover::hide_hover(editor, cx);
6080 let actions = CodeActionContents::new(
6081 resolved_tasks,
6082 code_actions,
6083 debug_scenarios,
6084 task_context.unwrap_or_default(),
6085 );
6086
6087 // Don't show the menu if there are no actions available
6088 if actions.is_empty() {
6089 cx.notify();
6090 return Task::ready(Ok(()));
6091 }
6092
6093 *editor.context_menu.borrow_mut() =
6094 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6095 buffer,
6096 actions,
6097 selected_item: Default::default(),
6098 scroll_handle: UniformListScrollHandle::default(),
6099 deployed_from,
6100 }));
6101 cx.notify();
6102 if spawn_straight_away {
6103 if let Some(task) = editor.confirm_code_action(
6104 &ConfirmCodeAction { item_ix: Some(0) },
6105 window,
6106 cx,
6107 ) {
6108 return task;
6109 }
6110 }
6111
6112 Task::ready(Ok(()))
6113 })
6114 })
6115 .detach_and_log_err(cx);
6116 }
6117
6118 fn debug_scenarios(
6119 &mut self,
6120 resolved_tasks: &Option<ResolvedTasks>,
6121 buffer: &Entity<Buffer>,
6122 cx: &mut App,
6123 ) -> Task<Vec<task::DebugScenario>> {
6124 maybe!({
6125 let project = self.project.as_ref()?;
6126 let dap_store = project.read(cx).dap_store();
6127 let mut scenarios = vec![];
6128 let resolved_tasks = resolved_tasks.as_ref()?;
6129 let buffer = buffer.read(cx);
6130 let language = buffer.language()?;
6131 let file = buffer.file();
6132 let debug_adapter = language_settings(language.name().into(), file, cx)
6133 .debuggers
6134 .first()
6135 .map(SharedString::from)
6136 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6137
6138 dap_store.update(cx, |dap_store, cx| {
6139 for (_, task) in &resolved_tasks.templates {
6140 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6141 task.original_task().clone(),
6142 debug_adapter.clone().into(),
6143 task.display_label().to_owned().into(),
6144 cx,
6145 );
6146 scenarios.push(maybe_scenario);
6147 }
6148 });
6149 Some(cx.background_spawn(async move {
6150 let scenarios = futures::future::join_all(scenarios)
6151 .await
6152 .into_iter()
6153 .flatten()
6154 .collect::<Vec<_>>();
6155 scenarios
6156 }))
6157 })
6158 .unwrap_or_else(|| Task::ready(vec![]))
6159 }
6160
6161 fn code_actions(
6162 &mut self,
6163 buffer_row: u32,
6164 window: &mut Window,
6165 cx: &mut Context<Self>,
6166 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6167 let mut task = self.code_actions_task.take();
6168 cx.spawn_in(window, async move |editor, cx| {
6169 while let Some(prev_task) = task {
6170 prev_task.await.log_err();
6171 task = editor
6172 .update(cx, |this, _| this.code_actions_task.take())
6173 .ok()?;
6174 }
6175
6176 editor
6177 .update(cx, |editor, cx| {
6178 editor
6179 .available_code_actions
6180 .clone()
6181 .and_then(|(location, code_actions)| {
6182 let snapshot = location.buffer.read(cx).snapshot();
6183 let point_range = location.range.to_point(&snapshot);
6184 let point_range = point_range.start.row..=point_range.end.row;
6185 if point_range.contains(&buffer_row) {
6186 Some(code_actions)
6187 } else {
6188 None
6189 }
6190 })
6191 })
6192 .ok()
6193 .flatten()
6194 })
6195 }
6196
6197 pub fn confirm_code_action(
6198 &mut self,
6199 action: &ConfirmCodeAction,
6200 window: &mut Window,
6201 cx: &mut Context<Self>,
6202 ) -> Option<Task<Result<()>>> {
6203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6204
6205 let actions_menu =
6206 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6207 menu
6208 } else {
6209 return None;
6210 };
6211
6212 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6213 let action = actions_menu.actions.get(action_ix)?;
6214 let title = action.label();
6215 let buffer = actions_menu.buffer;
6216 let workspace = self.workspace()?;
6217
6218 match action {
6219 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6220 workspace.update(cx, |workspace, cx| {
6221 workspace.schedule_resolved_task(
6222 task_source_kind,
6223 resolved_task,
6224 false,
6225 window,
6226 cx,
6227 );
6228
6229 Some(Task::ready(Ok(())))
6230 })
6231 }
6232 CodeActionsItem::CodeAction {
6233 excerpt_id,
6234 action,
6235 provider,
6236 } => {
6237 let apply_code_action =
6238 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6239 let workspace = workspace.downgrade();
6240 Some(cx.spawn_in(window, async move |editor, cx| {
6241 let project_transaction = apply_code_action.await?;
6242 Self::open_project_transaction(
6243 &editor,
6244 workspace,
6245 project_transaction,
6246 title,
6247 cx,
6248 )
6249 .await
6250 }))
6251 }
6252 CodeActionsItem::DebugScenario(scenario) => {
6253 let context = actions_menu.actions.context.clone();
6254
6255 workspace.update(cx, |workspace, cx| {
6256 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6257 workspace.start_debug_session(
6258 scenario,
6259 context,
6260 Some(buffer),
6261 None,
6262 window,
6263 cx,
6264 );
6265 });
6266 Some(Task::ready(Ok(())))
6267 }
6268 }
6269 }
6270
6271 pub async fn open_project_transaction(
6272 this: &WeakEntity<Editor>,
6273 workspace: WeakEntity<Workspace>,
6274 transaction: ProjectTransaction,
6275 title: String,
6276 cx: &mut AsyncWindowContext,
6277 ) -> Result<()> {
6278 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6279 cx.update(|_, cx| {
6280 entries.sort_unstable_by_key(|(buffer, _)| {
6281 buffer.read(cx).file().map(|f| f.path().clone())
6282 });
6283 })?;
6284
6285 // If the project transaction's edits are all contained within this editor, then
6286 // avoid opening a new editor to display them.
6287
6288 if let Some((buffer, transaction)) = entries.first() {
6289 if entries.len() == 1 {
6290 let excerpt = this.update(cx, |editor, cx| {
6291 editor
6292 .buffer()
6293 .read(cx)
6294 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6295 })?;
6296 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6297 if excerpted_buffer == *buffer {
6298 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6299 let excerpt_range = excerpt_range.to_offset(buffer);
6300 buffer
6301 .edited_ranges_for_transaction::<usize>(transaction)
6302 .all(|range| {
6303 excerpt_range.start <= range.start
6304 && excerpt_range.end >= range.end
6305 })
6306 })?;
6307
6308 if all_edits_within_excerpt {
6309 return Ok(());
6310 }
6311 }
6312 }
6313 }
6314 } else {
6315 return Ok(());
6316 }
6317
6318 let mut ranges_to_highlight = Vec::new();
6319 let excerpt_buffer = cx.new(|cx| {
6320 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6321 for (buffer_handle, transaction) in &entries {
6322 let edited_ranges = buffer_handle
6323 .read(cx)
6324 .edited_ranges_for_transaction::<Point>(transaction)
6325 .collect::<Vec<_>>();
6326 let (ranges, _) = multibuffer.set_excerpts_for_path(
6327 PathKey::for_buffer(buffer_handle, cx),
6328 buffer_handle.clone(),
6329 edited_ranges,
6330 DEFAULT_MULTIBUFFER_CONTEXT,
6331 cx,
6332 );
6333
6334 ranges_to_highlight.extend(ranges);
6335 }
6336 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6337 multibuffer
6338 })?;
6339
6340 workspace.update_in(cx, |workspace, window, cx| {
6341 let project = workspace.project().clone();
6342 let editor =
6343 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6344 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6345 editor.update(cx, |editor, cx| {
6346 editor.highlight_background::<Self>(
6347 &ranges_to_highlight,
6348 |theme| theme.colors().editor_highlighted_line_background,
6349 cx,
6350 );
6351 });
6352 })?;
6353
6354 Ok(())
6355 }
6356
6357 pub fn clear_code_action_providers(&mut self) {
6358 self.code_action_providers.clear();
6359 self.available_code_actions.take();
6360 }
6361
6362 pub fn add_code_action_provider(
6363 &mut self,
6364 provider: Rc<dyn CodeActionProvider>,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) {
6368 if self
6369 .code_action_providers
6370 .iter()
6371 .any(|existing_provider| existing_provider.id() == provider.id())
6372 {
6373 return;
6374 }
6375
6376 self.code_action_providers.push(provider);
6377 self.refresh_code_actions(window, cx);
6378 }
6379
6380 pub fn remove_code_action_provider(
6381 &mut self,
6382 id: Arc<str>,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) {
6386 self.code_action_providers
6387 .retain(|provider| provider.id() != id);
6388 self.refresh_code_actions(window, cx);
6389 }
6390
6391 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6392 !self.code_action_providers.is_empty()
6393 && EditorSettings::get_global(cx).toolbar.code_actions
6394 }
6395
6396 pub fn has_available_code_actions(&self) -> bool {
6397 self.available_code_actions
6398 .as_ref()
6399 .is_some_and(|(_, actions)| !actions.is_empty())
6400 }
6401
6402 fn render_inline_code_actions(
6403 &self,
6404 icon_size: ui::IconSize,
6405 display_row: DisplayRow,
6406 is_active: bool,
6407 cx: &mut Context<Self>,
6408 ) -> AnyElement {
6409 let show_tooltip = !self.context_menu_visible();
6410 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6411 .icon_size(icon_size)
6412 .shape(ui::IconButtonShape::Square)
6413 .style(ButtonStyle::Transparent)
6414 .icon_color(ui::Color::Hidden)
6415 .toggle_state(is_active)
6416 .when(show_tooltip, |this| {
6417 this.tooltip({
6418 let focus_handle = self.focus_handle.clone();
6419 move |window, cx| {
6420 Tooltip::for_action_in(
6421 "Toggle Code Actions",
6422 &ToggleCodeActions {
6423 deployed_from: None,
6424 quick_launch: false,
6425 },
6426 &focus_handle,
6427 window,
6428 cx,
6429 )
6430 }
6431 })
6432 })
6433 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6434 window.focus(&editor.focus_handle(cx));
6435 editor.toggle_code_actions(
6436 &crate::actions::ToggleCodeActions {
6437 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6438 display_row,
6439 )),
6440 quick_launch: false,
6441 },
6442 window,
6443 cx,
6444 );
6445 }))
6446 .into_any_element()
6447 }
6448
6449 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6450 &self.context_menu
6451 }
6452
6453 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6454 let newest_selection = self.selections.newest_anchor().clone();
6455 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6456 let buffer = self.buffer.read(cx);
6457 if newest_selection.head().diff_base_anchor.is_some() {
6458 return None;
6459 }
6460 let (start_buffer, start) =
6461 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6462 let (end_buffer, end) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6464 if start_buffer != end_buffer {
6465 return None;
6466 }
6467
6468 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6469 cx.background_executor()
6470 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6471 .await;
6472
6473 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6474 let providers = this.code_action_providers.clone();
6475 let tasks = this
6476 .code_action_providers
6477 .iter()
6478 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6479 .collect::<Vec<_>>();
6480 (providers, tasks)
6481 })?;
6482
6483 let mut actions = Vec::new();
6484 for (provider, provider_actions) in
6485 providers.into_iter().zip(future::join_all(tasks).await)
6486 {
6487 if let Some(provider_actions) = provider_actions.log_err() {
6488 actions.extend(provider_actions.into_iter().map(|action| {
6489 AvailableCodeAction {
6490 excerpt_id: newest_selection.start.excerpt_id,
6491 action,
6492 provider: provider.clone(),
6493 }
6494 }));
6495 }
6496 }
6497
6498 this.update(cx, |this, cx| {
6499 this.available_code_actions = if actions.is_empty() {
6500 None
6501 } else {
6502 Some((
6503 Location {
6504 buffer: start_buffer,
6505 range: start..end,
6506 },
6507 actions.into(),
6508 ))
6509 };
6510 cx.notify();
6511 })
6512 }));
6513 None
6514 }
6515
6516 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6517 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6518 self.show_git_blame_inline = false;
6519
6520 self.show_git_blame_inline_delay_task =
6521 Some(cx.spawn_in(window, async move |this, cx| {
6522 cx.background_executor().timer(delay).await;
6523
6524 this.update(cx, |this, cx| {
6525 this.show_git_blame_inline = true;
6526 cx.notify();
6527 })
6528 .log_err();
6529 }));
6530 }
6531 }
6532
6533 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6534 let snapshot = self.snapshot(window, cx);
6535 let cursor = self.selections.newest::<Point>(cx).head();
6536 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6537 else {
6538 return;
6539 };
6540
6541 let Some(blame) = self.blame.as_ref() else {
6542 return;
6543 };
6544
6545 let row_info = RowInfo {
6546 buffer_id: Some(buffer.remote_id()),
6547 buffer_row: Some(point.row),
6548 ..Default::default()
6549 };
6550 let Some(blame_entry) = blame
6551 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6552 .flatten()
6553 else {
6554 return;
6555 };
6556
6557 let anchor = self.selections.newest_anchor().head();
6558 let position = self.to_pixel_point(anchor, &snapshot, window);
6559 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6560 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6561 };
6562 }
6563
6564 fn show_blame_popover(
6565 &mut self,
6566 blame_entry: &BlameEntry,
6567 position: gpui::Point<Pixels>,
6568 ignore_timeout: bool,
6569 cx: &mut Context<Self>,
6570 ) {
6571 if let Some(state) = &mut self.inline_blame_popover {
6572 state.hide_task.take();
6573 } else {
6574 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6575 let blame_entry = blame_entry.clone();
6576 let show_task = cx.spawn(async move |editor, cx| {
6577 if !ignore_timeout {
6578 cx.background_executor()
6579 .timer(std::time::Duration::from_millis(blame_popover_delay))
6580 .await;
6581 }
6582 editor
6583 .update(cx, |editor, cx| {
6584 editor.inline_blame_popover_show_task.take();
6585 let Some(blame) = editor.blame.as_ref() else {
6586 return;
6587 };
6588 let blame = blame.read(cx);
6589 let details = blame.details_for_entry(&blame_entry);
6590 let markdown = cx.new(|cx| {
6591 Markdown::new(
6592 details
6593 .as_ref()
6594 .map(|message| message.message.clone())
6595 .unwrap_or_default(),
6596 None,
6597 None,
6598 cx,
6599 )
6600 });
6601 editor.inline_blame_popover = Some(InlineBlamePopover {
6602 position,
6603 hide_task: None,
6604 popover_bounds: None,
6605 popover_state: InlineBlamePopoverState {
6606 scroll_handle: ScrollHandle::new(),
6607 commit_message: details,
6608 markdown,
6609 },
6610 keyboard_grace: ignore_timeout,
6611 });
6612 cx.notify();
6613 })
6614 .ok();
6615 });
6616 self.inline_blame_popover_show_task = Some(show_task);
6617 }
6618 }
6619
6620 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6621 self.inline_blame_popover_show_task.take();
6622 if let Some(state) = &mut self.inline_blame_popover {
6623 let hide_task = cx.spawn(async move |editor, cx| {
6624 cx.background_executor()
6625 .timer(std::time::Duration::from_millis(100))
6626 .await;
6627 editor
6628 .update(cx, |editor, cx| {
6629 editor.inline_blame_popover.take();
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 state.hide_task = Some(hide_task);
6635 }
6636 }
6637
6638 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6639 if self.pending_rename.is_some() {
6640 return None;
6641 }
6642
6643 let provider = self.semantics_provider.clone()?;
6644 let buffer = self.buffer.read(cx);
6645 let newest_selection = self.selections.newest_anchor().clone();
6646 let cursor_position = newest_selection.head();
6647 let (cursor_buffer, cursor_buffer_position) =
6648 buffer.text_anchor_for_position(cursor_position, cx)?;
6649 let (tail_buffer, tail_buffer_position) =
6650 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6651 if cursor_buffer != tail_buffer {
6652 return None;
6653 }
6654
6655 let snapshot = cursor_buffer.read(cx).snapshot();
6656 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6657 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6658 if start_word_range != end_word_range {
6659 self.document_highlights_task.take();
6660 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6661 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6662 return None;
6663 }
6664
6665 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6666 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6667 cx.background_executor()
6668 .timer(Duration::from_millis(debounce))
6669 .await;
6670
6671 let highlights = if let Some(highlights) = cx
6672 .update(|cx| {
6673 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6674 })
6675 .ok()
6676 .flatten()
6677 {
6678 highlights.await.log_err()
6679 } else {
6680 None
6681 };
6682
6683 if let Some(highlights) = highlights {
6684 this.update(cx, |this, cx| {
6685 if this.pending_rename.is_some() {
6686 return;
6687 }
6688
6689 let buffer_id = cursor_position.buffer_id;
6690 let buffer = this.buffer.read(cx);
6691 if !buffer
6692 .text_anchor_for_position(cursor_position, cx)
6693 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6694 {
6695 return;
6696 }
6697
6698 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6699 let mut write_ranges = Vec::new();
6700 let mut read_ranges = Vec::new();
6701 for highlight in highlights {
6702 for (excerpt_id, excerpt_range) in
6703 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6704 {
6705 let start = highlight
6706 .range
6707 .start
6708 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6709 let end = highlight
6710 .range
6711 .end
6712 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6713 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6714 continue;
6715 }
6716
6717 let range = Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: start,
6721 diff_base_anchor: None,
6722 }..Anchor {
6723 buffer_id,
6724 excerpt_id,
6725 text_anchor: end,
6726 diff_base_anchor: None,
6727 };
6728 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6729 write_ranges.push(range);
6730 } else {
6731 read_ranges.push(range);
6732 }
6733 }
6734 }
6735
6736 this.highlight_background::<DocumentHighlightRead>(
6737 &read_ranges,
6738 |theme| theme.colors().editor_document_highlight_read_background,
6739 cx,
6740 );
6741 this.highlight_background::<DocumentHighlightWrite>(
6742 &write_ranges,
6743 |theme| theme.colors().editor_document_highlight_write_background,
6744 cx,
6745 );
6746 cx.notify();
6747 })
6748 .log_err();
6749 }
6750 }));
6751 None
6752 }
6753
6754 fn prepare_highlight_query_from_selection(
6755 &mut self,
6756 cx: &mut Context<Editor>,
6757 ) -> Option<(String, Range<Anchor>)> {
6758 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6759 return None;
6760 }
6761 if !EditorSettings::get_global(cx).selection_highlight {
6762 return None;
6763 }
6764 if self.selections.count() != 1 || self.selections.line_mode {
6765 return None;
6766 }
6767 let selection = self.selections.newest::<Point>(cx);
6768 if selection.is_empty() || selection.start.row != selection.end.row {
6769 return None;
6770 }
6771 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6772 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6773 let query = multi_buffer_snapshot
6774 .text_for_range(selection_anchor_range.clone())
6775 .collect::<String>();
6776 if query.trim().is_empty() {
6777 return None;
6778 }
6779 Some((query, selection_anchor_range))
6780 }
6781
6782 fn update_selection_occurrence_highlights(
6783 &mut self,
6784 query_text: String,
6785 query_range: Range<Anchor>,
6786 multi_buffer_range_to_query: Range<Point>,
6787 use_debounce: bool,
6788 window: &mut Window,
6789 cx: &mut Context<Editor>,
6790 ) -> Task<()> {
6791 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6792 cx.spawn_in(window, async move |editor, cx| {
6793 if use_debounce {
6794 cx.background_executor()
6795 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6796 .await;
6797 }
6798 let match_task = cx.background_spawn(async move {
6799 let buffer_ranges = multi_buffer_snapshot
6800 .range_to_buffer_ranges(multi_buffer_range_to_query)
6801 .into_iter()
6802 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6803 let mut match_ranges = Vec::new();
6804 let Ok(regex) = project::search::SearchQuery::text(
6805 query_text.clone(),
6806 false,
6807 false,
6808 false,
6809 Default::default(),
6810 Default::default(),
6811 false,
6812 None,
6813 ) else {
6814 return Vec::default();
6815 };
6816 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6817 match_ranges.extend(
6818 regex
6819 .search(&buffer_snapshot, Some(search_range.clone()))
6820 .await
6821 .into_iter()
6822 .filter_map(|match_range| {
6823 let match_start = buffer_snapshot
6824 .anchor_after(search_range.start + match_range.start);
6825 let match_end = buffer_snapshot
6826 .anchor_before(search_range.start + match_range.end);
6827 let match_anchor_range = Anchor::range_in_buffer(
6828 excerpt_id,
6829 buffer_snapshot.remote_id(),
6830 match_start..match_end,
6831 );
6832 (match_anchor_range != query_range).then_some(match_anchor_range)
6833 }),
6834 );
6835 }
6836 match_ranges
6837 });
6838 let match_ranges = match_task.await;
6839 editor
6840 .update_in(cx, |editor, _, cx| {
6841 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6842 if !match_ranges.is_empty() {
6843 editor.highlight_background::<SelectedTextHighlight>(
6844 &match_ranges,
6845 |theme| theme.colors().editor_document_highlight_bracket_background,
6846 cx,
6847 )
6848 }
6849 })
6850 .log_err();
6851 })
6852 }
6853
6854 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6855 struct NewlineFold;
6856 let type_id = std::any::TypeId::of::<NewlineFold>();
6857 if !self.mode.is_single_line() {
6858 return;
6859 }
6860 let snapshot = self.snapshot(window, cx);
6861 if snapshot.buffer_snapshot.max_point().row == 0 {
6862 return;
6863 }
6864 let task = cx.background_spawn(async move {
6865 let new_newlines = snapshot
6866 .buffer_chars_at(0)
6867 .filter_map(|(c, i)| {
6868 if c == '\n' {
6869 Some(
6870 snapshot.buffer_snapshot.anchor_after(i)
6871 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6872 )
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878 let existing_newlines = snapshot
6879 .folds_in_range(0..snapshot.buffer_snapshot.len())
6880 .filter_map(|fold| {
6881 if fold.placeholder.type_tag == Some(type_id) {
6882 Some(fold.range.start..fold.range.end)
6883 } else {
6884 None
6885 }
6886 })
6887 .collect::<Vec<_>>();
6888
6889 (new_newlines, existing_newlines)
6890 });
6891 self.folding_newlines = cx.spawn(async move |this, cx| {
6892 let (new_newlines, existing_newlines) = task.await;
6893 if new_newlines == existing_newlines {
6894 return;
6895 }
6896 let placeholder = FoldPlaceholder {
6897 render: Arc::new(move |_, _, cx| {
6898 div()
6899 .bg(cx.theme().status().hint_background)
6900 .border_b_1()
6901 .size_full()
6902 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6903 .border_color(cx.theme().status().hint)
6904 .child("\\n")
6905 .into_any()
6906 }),
6907 constrain_width: false,
6908 merge_adjacent: false,
6909 type_tag: Some(type_id),
6910 };
6911 let creases = new_newlines
6912 .into_iter()
6913 .map(|range| Crease::simple(range, placeholder.clone()))
6914 .collect();
6915 this.update(cx, |this, cx| {
6916 this.display_map.update(cx, |display_map, cx| {
6917 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6918 display_map.fold(creases, cx);
6919 });
6920 })
6921 .ok();
6922 });
6923 }
6924
6925 fn refresh_selected_text_highlights(
6926 &mut self,
6927 on_buffer_edit: bool,
6928 window: &mut Window,
6929 cx: &mut Context<Editor>,
6930 ) {
6931 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6932 else {
6933 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6934 self.quick_selection_highlight_task.take();
6935 self.debounced_selection_highlight_task.take();
6936 return;
6937 };
6938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6939 if on_buffer_edit
6940 || self
6941 .quick_selection_highlight_task
6942 .as_ref()
6943 .map_or(true, |(prev_anchor_range, _)| {
6944 prev_anchor_range != &query_range
6945 })
6946 {
6947 let multi_buffer_visible_start = self
6948 .scroll_manager
6949 .anchor()
6950 .anchor
6951 .to_point(&multi_buffer_snapshot);
6952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6953 multi_buffer_visible_start
6954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6955 Bias::Left,
6956 );
6957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6958 self.quick_selection_highlight_task = Some((
6959 query_range.clone(),
6960 self.update_selection_occurrence_highlights(
6961 query_text.clone(),
6962 query_range.clone(),
6963 multi_buffer_visible_range,
6964 false,
6965 window,
6966 cx,
6967 ),
6968 ));
6969 }
6970 if on_buffer_edit
6971 || self
6972 .debounced_selection_highlight_task
6973 .as_ref()
6974 .map_or(true, |(prev_anchor_range, _)| {
6975 prev_anchor_range != &query_range
6976 })
6977 {
6978 let multi_buffer_start = multi_buffer_snapshot
6979 .anchor_before(0)
6980 .to_point(&multi_buffer_snapshot);
6981 let multi_buffer_end = multi_buffer_snapshot
6982 .anchor_after(multi_buffer_snapshot.len())
6983 .to_point(&multi_buffer_snapshot);
6984 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6985 self.debounced_selection_highlight_task = Some((
6986 query_range.clone(),
6987 self.update_selection_occurrence_highlights(
6988 query_text,
6989 query_range,
6990 multi_buffer_full_range,
6991 true,
6992 window,
6993 cx,
6994 ),
6995 ));
6996 }
6997 }
6998
6999 pub fn refresh_inline_completion(
7000 &mut self,
7001 debounce: bool,
7002 user_requested: bool,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) -> Option<()> {
7006 let provider = self.edit_prediction_provider()?;
7007 let cursor = self.selections.newest_anchor().head();
7008 let (buffer, cursor_buffer_position) =
7009 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7010
7011 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7012 self.discard_inline_completion(false, cx);
7013 return None;
7014 }
7015
7016 if !user_requested
7017 && (!self.should_show_edit_predictions()
7018 || !self.is_focused(window)
7019 || buffer.read(cx).is_empty())
7020 {
7021 self.discard_inline_completion(false, cx);
7022 return None;
7023 }
7024
7025 self.update_visible_inline_completion(window, cx);
7026 provider.refresh(
7027 self.project.clone(),
7028 buffer,
7029 cursor_buffer_position,
7030 debounce,
7031 cx,
7032 );
7033 Some(())
7034 }
7035
7036 fn show_edit_predictions_in_menu(&self) -> bool {
7037 match self.edit_prediction_settings {
7038 EditPredictionSettings::Disabled => false,
7039 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7040 }
7041 }
7042
7043 pub fn edit_predictions_enabled(&self) -> bool {
7044 match self.edit_prediction_settings {
7045 EditPredictionSettings::Disabled => false,
7046 EditPredictionSettings::Enabled { .. } => true,
7047 }
7048 }
7049
7050 fn edit_prediction_requires_modifier(&self) -> bool {
7051 match self.edit_prediction_settings {
7052 EditPredictionSettings::Disabled => false,
7053 EditPredictionSettings::Enabled {
7054 preview_requires_modifier,
7055 ..
7056 } => preview_requires_modifier,
7057 }
7058 }
7059
7060 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7061 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7062 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7063 } else {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066
7067 if let Some((buffer, cursor_buffer_position)) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7069 {
7070 self.edit_prediction_settings =
7071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7072 }
7073 }
7074 }
7075
7076 fn edit_prediction_settings_at_position(
7077 &self,
7078 buffer: &Entity<Buffer>,
7079 buffer_position: language::Anchor,
7080 cx: &App,
7081 ) -> EditPredictionSettings {
7082 if !self.mode.is_full()
7083 || !self.show_inline_completions_override.unwrap_or(true)
7084 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7085 {
7086 return EditPredictionSettings::Disabled;
7087 }
7088
7089 let buffer = buffer.read(cx);
7090
7091 let file = buffer.file();
7092
7093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7094 return EditPredictionSettings::Disabled;
7095 };
7096
7097 let by_provider = matches!(
7098 self.menu_inline_completions_policy,
7099 MenuInlineCompletionsPolicy::ByProvider
7100 );
7101
7102 let show_in_menu = by_provider
7103 && self
7104 .edit_prediction_provider
7105 .as_ref()
7106 .map_or(false, |provider| {
7107 provider.provider.show_completions_in_menu()
7108 });
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(&buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_inline_completion(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_inline_completion(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_inline_completion(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_inline_completion() {
7196 self.refresh_inline_completion(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_inline_completion(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_inline_completion() {
7233 self.cycle_inline_completion(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_inline_completion(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_inline_completion() {
7251 self.cycle_inline_completion(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_inline_completion(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7273 return;
7274 };
7275
7276 self.report_inline_completion_event(
7277 active_inline_completion.completion_id.clone(),
7278 true,
7279 cx,
7280 );
7281
7282 match &active_inline_completion.completion {
7283 InlineCompletion::Move { target, .. } => {
7284 let target = *target;
7285
7286 if let Some(position_map) = &self.last_position_map {
7287 if position_map
7288 .visible_row_range
7289 .contains(&target.to_display_point(&position_map.snapshot).row())
7290 || !self.edit_prediction_requires_modifier()
7291 {
7292 self.unfold_ranges(&[target..target], true, false, cx);
7293 // Note that this is also done in vim's handler of the Tab action.
7294 self.change_selections(
7295 SelectionEffects::scroll(Autoscroll::newest()),
7296 window,
7297 cx,
7298 |selections| {
7299 selections.select_anchor_ranges([target..target]);
7300 },
7301 );
7302 self.clear_row_highlights::<EditPredictionPreview>();
7303
7304 self.edit_prediction_preview
7305 .set_previous_scroll_position(None);
7306 } else {
7307 self.edit_prediction_preview
7308 .set_previous_scroll_position(Some(
7309 position_map.snapshot.scroll_anchor,
7310 ));
7311
7312 self.highlight_rows::<EditPredictionPreview>(
7313 target..target,
7314 cx.theme().colors().editor_highlighted_line_background,
7315 RowHighlightOptions {
7316 autoscroll: true,
7317 ..Default::default()
7318 },
7319 cx,
7320 );
7321 self.request_autoscroll(Autoscroll::fit(), cx);
7322 }
7323 }
7324 }
7325 InlineCompletion::Edit { edits, .. } => {
7326 if let Some(provider) = self.edit_prediction_provider() {
7327 provider.accept(cx);
7328 }
7329
7330 // Store the transaction ID and selections before applying the edit
7331 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7332
7333 let snapshot = self.buffer.read(cx).snapshot(cx);
7334 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7335
7336 self.buffer.update(cx, |buffer, cx| {
7337 buffer.edit(edits.iter().cloned(), None, cx)
7338 });
7339
7340 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7341 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7342 });
7343
7344 let selections = self.selections.disjoint_anchors();
7345 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7346 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7347 if has_new_transaction {
7348 self.selection_history
7349 .insert_transaction(transaction_id_now, selections);
7350 }
7351 }
7352
7353 self.update_visible_inline_completion(window, cx);
7354 if self.active_inline_completion.is_none() {
7355 self.refresh_inline_completion(true, true, window, cx);
7356 }
7357
7358 cx.notify();
7359 }
7360 }
7361
7362 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7363 }
7364
7365 pub fn accept_partial_inline_completion(
7366 &mut self,
7367 _: &AcceptPartialEditPrediction,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) {
7371 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7372 return;
7373 };
7374 if self.selections.count() != 1 {
7375 return;
7376 }
7377
7378 self.report_inline_completion_event(
7379 active_inline_completion.completion_id.clone(),
7380 true,
7381 cx,
7382 );
7383
7384 match &active_inline_completion.completion {
7385 InlineCompletion::Move { target, .. } => {
7386 let target = *target;
7387 self.change_selections(
7388 SelectionEffects::scroll(Autoscroll::newest()),
7389 window,
7390 cx,
7391 |selections| {
7392 selections.select_anchor_ranges([target..target]);
7393 },
7394 );
7395 }
7396 InlineCompletion::Edit { edits, .. } => {
7397 // Find an insertion that starts at the cursor position.
7398 let snapshot = self.buffer.read(cx).snapshot(cx);
7399 let cursor_offset = self.selections.newest::<usize>(cx).head();
7400 let insertion = edits.iter().find_map(|(range, text)| {
7401 let range = range.to_offset(&snapshot);
7402 if range.is_empty() && range.start == cursor_offset {
7403 Some(text)
7404 } else {
7405 None
7406 }
7407 });
7408
7409 if let Some(text) = insertion {
7410 let mut partial_completion = text
7411 .chars()
7412 .by_ref()
7413 .take_while(|c| c.is_alphabetic())
7414 .collect::<String>();
7415 if partial_completion.is_empty() {
7416 partial_completion = text
7417 .chars()
7418 .by_ref()
7419 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7420 .collect::<String>();
7421 }
7422
7423 cx.emit(EditorEvent::InputHandled {
7424 utf16_range_to_replace: None,
7425 text: partial_completion.clone().into(),
7426 });
7427
7428 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7429
7430 self.refresh_inline_completion(true, true, window, cx);
7431 cx.notify();
7432 } else {
7433 self.accept_edit_prediction(&Default::default(), window, cx);
7434 }
7435 }
7436 }
7437 }
7438
7439 fn discard_inline_completion(
7440 &mut self,
7441 should_report_inline_completion_event: bool,
7442 cx: &mut Context<Self>,
7443 ) -> bool {
7444 if should_report_inline_completion_event {
7445 let completion_id = self
7446 .active_inline_completion
7447 .as_ref()
7448 .and_then(|active_completion| active_completion.completion_id.clone());
7449
7450 self.report_inline_completion_event(completion_id, false, cx);
7451 }
7452
7453 if let Some(provider) = self.edit_prediction_provider() {
7454 provider.discard(cx);
7455 }
7456
7457 self.take_active_inline_completion(cx)
7458 }
7459
7460 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7461 let Some(provider) = self.edit_prediction_provider() else {
7462 return;
7463 };
7464
7465 let Some((_, buffer, _)) = self
7466 .buffer
7467 .read(cx)
7468 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7469 else {
7470 return;
7471 };
7472
7473 let extension = buffer
7474 .read(cx)
7475 .file()
7476 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7477
7478 let event_type = match accepted {
7479 true => "Edit Prediction Accepted",
7480 false => "Edit Prediction Discarded",
7481 };
7482 telemetry::event!(
7483 event_type,
7484 provider = provider.name(),
7485 prediction_id = id,
7486 suggestion_accepted = accepted,
7487 file_extension = extension,
7488 );
7489 }
7490
7491 pub fn has_active_inline_completion(&self) -> bool {
7492 self.active_inline_completion.is_some()
7493 }
7494
7495 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7496 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7497 return false;
7498 };
7499
7500 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7501 self.clear_highlights::<InlineCompletionHighlight>(cx);
7502 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7503 true
7504 }
7505
7506 /// Returns true when we're displaying the edit prediction popover below the cursor
7507 /// like we are not previewing and the LSP autocomplete menu is visible
7508 /// or we are in `when_holding_modifier` mode.
7509 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7510 if self.edit_prediction_preview_is_active()
7511 || !self.show_edit_predictions_in_menu()
7512 || !self.edit_predictions_enabled()
7513 {
7514 return false;
7515 }
7516
7517 if self.has_visible_completions_menu() {
7518 return true;
7519 }
7520
7521 has_completion && self.edit_prediction_requires_modifier()
7522 }
7523
7524 fn handle_modifiers_changed(
7525 &mut self,
7526 modifiers: Modifiers,
7527 position_map: &PositionMap,
7528 window: &mut Window,
7529 cx: &mut Context<Self>,
7530 ) {
7531 if self.show_edit_predictions_in_menu() {
7532 self.update_edit_prediction_preview(&modifiers, window, cx);
7533 }
7534
7535 self.update_selection_mode(&modifiers, position_map, window, cx);
7536
7537 let mouse_position = window.mouse_position();
7538 if !position_map.text_hitbox.is_hovered(window) {
7539 return;
7540 }
7541
7542 self.update_hovered_link(
7543 position_map.point_for_position(mouse_position),
7544 &position_map.snapshot,
7545 modifiers,
7546 window,
7547 cx,
7548 )
7549 }
7550
7551 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7552 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7553 if invert {
7554 match multi_cursor_setting {
7555 MultiCursorModifier::Alt => modifiers.alt,
7556 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7557 }
7558 } else {
7559 match multi_cursor_setting {
7560 MultiCursorModifier::Alt => modifiers.secondary(),
7561 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7562 }
7563 }
7564 }
7565
7566 fn columnar_selection_mode(
7567 modifiers: &Modifiers,
7568 cx: &mut Context<Self>,
7569 ) -> Option<ColumnarMode> {
7570 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7571 if Self::multi_cursor_modifier(false, modifiers, cx) {
7572 Some(ColumnarMode::FromMouse)
7573 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7574 Some(ColumnarMode::FromSelection)
7575 } else {
7576 None
7577 }
7578 } else {
7579 None
7580 }
7581 }
7582
7583 fn update_selection_mode(
7584 &mut self,
7585 modifiers: &Modifiers,
7586 position_map: &PositionMap,
7587 window: &mut Window,
7588 cx: &mut Context<Self>,
7589 ) {
7590 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7591 return;
7592 };
7593 if self.selections.pending.is_none() {
7594 return;
7595 }
7596
7597 let mouse_position = window.mouse_position();
7598 let point_for_position = position_map.point_for_position(mouse_position);
7599 let position = point_for_position.previous_valid;
7600
7601 self.select(
7602 SelectPhase::BeginColumnar {
7603 position,
7604 reset: false,
7605 mode,
7606 goal_column: point_for_position.exact_unclipped.column(),
7607 },
7608 window,
7609 cx,
7610 );
7611 }
7612
7613 fn update_edit_prediction_preview(
7614 &mut self,
7615 modifiers: &Modifiers,
7616 window: &mut Window,
7617 cx: &mut Context<Self>,
7618 ) {
7619 let mut modifiers_held = false;
7620 if let Some(accept_keystroke) = self
7621 .accept_edit_prediction_keybind(false, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_keystroke.modifiers == modifiers
7626 && accept_keystroke.modifiers.modified());
7627 };
7628 if let Some(accept_partial_keystroke) = self
7629 .accept_edit_prediction_keybind(true, window, cx)
7630 .keystroke()
7631 {
7632 modifiers_held = modifiers_held
7633 || (&accept_partial_keystroke.modifiers == modifiers
7634 && accept_partial_keystroke.modifiers.modified());
7635 }
7636
7637 if modifiers_held {
7638 if matches!(
7639 self.edit_prediction_preview,
7640 EditPredictionPreview::Inactive { .. }
7641 ) {
7642 self.edit_prediction_preview = EditPredictionPreview::Active {
7643 previous_scroll_position: None,
7644 since: Instant::now(),
7645 };
7646
7647 self.update_visible_inline_completion(window, cx);
7648 cx.notify();
7649 }
7650 } else if let EditPredictionPreview::Active {
7651 previous_scroll_position,
7652 since,
7653 } = self.edit_prediction_preview
7654 {
7655 if let (Some(previous_scroll_position), Some(position_map)) =
7656 (previous_scroll_position, self.last_position_map.as_ref())
7657 {
7658 self.set_scroll_position(
7659 previous_scroll_position
7660 .scroll_position(&position_map.snapshot.display_snapshot),
7661 window,
7662 cx,
7663 );
7664 }
7665
7666 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7667 released_too_fast: since.elapsed() < Duration::from_millis(200),
7668 };
7669 self.clear_row_highlights::<EditPredictionPreview>();
7670 self.update_visible_inline_completion(window, cx);
7671 cx.notify();
7672 }
7673 }
7674
7675 fn update_visible_inline_completion(
7676 &mut self,
7677 _window: &mut Window,
7678 cx: &mut Context<Self>,
7679 ) -> Option<()> {
7680 let selection = self.selections.newest_anchor();
7681 let cursor = selection.head();
7682 let multibuffer = self.buffer.read(cx).snapshot(cx);
7683 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7684 let excerpt_id = cursor.excerpt_id;
7685
7686 let show_in_menu = self.show_edit_predictions_in_menu();
7687 let completions_menu_has_precedence = !show_in_menu
7688 && (self.context_menu.borrow().is_some()
7689 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7690
7691 if completions_menu_has_precedence
7692 || !offset_selection.is_empty()
7693 || self
7694 .active_inline_completion
7695 .as_ref()
7696 .map_or(false, |completion| {
7697 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7698 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7699 !invalidation_range.contains(&offset_selection.head())
7700 })
7701 {
7702 self.discard_inline_completion(false, cx);
7703 return None;
7704 }
7705
7706 self.take_active_inline_completion(cx);
7707 let Some(provider) = self.edit_prediction_provider() else {
7708 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7709 return None;
7710 };
7711
7712 let (buffer, cursor_buffer_position) =
7713 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7714
7715 self.edit_prediction_settings =
7716 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7717
7718 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7719
7720 if self.edit_prediction_indent_conflict {
7721 let cursor_point = cursor.to_point(&multibuffer);
7722
7723 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7724
7725 if let Some((_, indent)) = indents.iter().next() {
7726 if indent.len == cursor_point.column {
7727 self.edit_prediction_indent_conflict = false;
7728 }
7729 }
7730 }
7731
7732 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7733 let edits = inline_completion
7734 .edits
7735 .into_iter()
7736 .flat_map(|(range, new_text)| {
7737 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7738 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7739 Some((start..end, new_text))
7740 })
7741 .collect::<Vec<_>>();
7742 if edits.is_empty() {
7743 return None;
7744 }
7745
7746 let first_edit_start = edits.first().unwrap().0.start;
7747 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7748 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7749
7750 let last_edit_end = edits.last().unwrap().0.end;
7751 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7752 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7753
7754 let cursor_row = cursor.to_point(&multibuffer).row;
7755
7756 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7757
7758 let mut inlay_ids = Vec::new();
7759 let invalidation_row_range;
7760 let move_invalidation_row_range = if cursor_row < edit_start_row {
7761 Some(cursor_row..edit_end_row)
7762 } else if cursor_row > edit_end_row {
7763 Some(edit_start_row..cursor_row)
7764 } else {
7765 None
7766 };
7767 let is_move =
7768 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7769 let completion = if is_move {
7770 invalidation_row_range =
7771 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7772 let target = first_edit_start;
7773 InlineCompletion::Move { target, snapshot }
7774 } else {
7775 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7776 && !self.inline_completions_hidden_for_vim_mode;
7777
7778 if show_completions_in_buffer {
7779 if edits
7780 .iter()
7781 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7782 {
7783 let mut inlays = Vec::new();
7784 for (range, new_text) in &edits {
7785 let inlay = Inlay::inline_completion(
7786 post_inc(&mut self.next_inlay_id),
7787 range.start,
7788 new_text.as_str(),
7789 );
7790 inlay_ids.push(inlay.id);
7791 inlays.push(inlay);
7792 }
7793
7794 self.splice_inlays(&[], inlays, cx);
7795 } else {
7796 let background_color = cx.theme().status().deleted_background;
7797 self.highlight_text::<InlineCompletionHighlight>(
7798 edits.iter().map(|(range, _)| range.clone()).collect(),
7799 HighlightStyle {
7800 background_color: Some(background_color),
7801 ..Default::default()
7802 },
7803 cx,
7804 );
7805 }
7806 }
7807
7808 invalidation_row_range = edit_start_row..edit_end_row;
7809
7810 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7811 if provider.show_tab_accept_marker() {
7812 EditDisplayMode::TabAccept
7813 } else {
7814 EditDisplayMode::Inline
7815 }
7816 } else {
7817 EditDisplayMode::DiffPopover
7818 };
7819
7820 InlineCompletion::Edit {
7821 edits,
7822 edit_preview: inline_completion.edit_preview,
7823 display_mode,
7824 snapshot,
7825 }
7826 };
7827
7828 let invalidation_range = multibuffer
7829 .anchor_before(Point::new(invalidation_row_range.start, 0))
7830 ..multibuffer.anchor_after(Point::new(
7831 invalidation_row_range.end,
7832 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7833 ));
7834
7835 self.stale_inline_completion_in_menu = None;
7836 self.active_inline_completion = Some(InlineCompletionState {
7837 inlay_ids,
7838 completion,
7839 completion_id: inline_completion.id,
7840 invalidation_range,
7841 });
7842
7843 cx.notify();
7844
7845 Some(())
7846 }
7847
7848 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7849 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7850 }
7851
7852 fn clear_tasks(&mut self) {
7853 self.tasks.clear()
7854 }
7855
7856 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7857 if self.tasks.insert(key, value).is_some() {
7858 // This case should hopefully be rare, but just in case...
7859 log::error!(
7860 "multiple different run targets found on a single line, only the last target will be rendered"
7861 )
7862 }
7863 }
7864
7865 /// Get all display points of breakpoints that will be rendered within editor
7866 ///
7867 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7868 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7869 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7870 fn active_breakpoints(
7871 &self,
7872 range: Range<DisplayRow>,
7873 window: &mut Window,
7874 cx: &mut Context<Self>,
7875 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7876 let mut breakpoint_display_points = HashMap::default();
7877
7878 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7879 return breakpoint_display_points;
7880 };
7881
7882 let snapshot = self.snapshot(window, cx);
7883
7884 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7885 let Some(project) = self.project.as_ref() else {
7886 return breakpoint_display_points;
7887 };
7888
7889 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7890 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7891
7892 for (buffer_snapshot, range, excerpt_id) in
7893 multi_buffer_snapshot.range_to_buffer_ranges(range)
7894 {
7895 let Some(buffer) = project
7896 .read(cx)
7897 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7898 else {
7899 continue;
7900 };
7901 let breakpoints = breakpoint_store.read(cx).breakpoints(
7902 &buffer,
7903 Some(
7904 buffer_snapshot.anchor_before(range.start)
7905 ..buffer_snapshot.anchor_after(range.end),
7906 ),
7907 buffer_snapshot,
7908 cx,
7909 );
7910 for (breakpoint, state) in breakpoints {
7911 let multi_buffer_anchor =
7912 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7913 let position = multi_buffer_anchor
7914 .to_point(&multi_buffer_snapshot)
7915 .to_display_point(&snapshot);
7916
7917 breakpoint_display_points.insert(
7918 position.row(),
7919 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7920 );
7921 }
7922 }
7923
7924 breakpoint_display_points
7925 }
7926
7927 fn breakpoint_context_menu(
7928 &self,
7929 anchor: Anchor,
7930 window: &mut Window,
7931 cx: &mut Context<Self>,
7932 ) -> Entity<ui::ContextMenu> {
7933 let weak_editor = cx.weak_entity();
7934 let focus_handle = self.focus_handle(cx);
7935
7936 let row = self
7937 .buffer
7938 .read(cx)
7939 .snapshot(cx)
7940 .summary_for_anchor::<Point>(&anchor)
7941 .row;
7942
7943 let breakpoint = self
7944 .breakpoint_at_row(row, window, cx)
7945 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7946
7947 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7948 "Edit Log Breakpoint"
7949 } else {
7950 "Set Log Breakpoint"
7951 };
7952
7953 let condition_breakpoint_msg = if breakpoint
7954 .as_ref()
7955 .is_some_and(|bp| bp.1.condition.is_some())
7956 {
7957 "Edit Condition Breakpoint"
7958 } else {
7959 "Set Condition Breakpoint"
7960 };
7961
7962 let hit_condition_breakpoint_msg = if breakpoint
7963 .as_ref()
7964 .is_some_and(|bp| bp.1.hit_condition.is_some())
7965 {
7966 "Edit Hit Condition Breakpoint"
7967 } else {
7968 "Set Hit Condition Breakpoint"
7969 };
7970
7971 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7972 "Unset Breakpoint"
7973 } else {
7974 "Set Breakpoint"
7975 };
7976
7977 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7978
7979 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7980 BreakpointState::Enabled => Some("Disable"),
7981 BreakpointState::Disabled => Some("Enable"),
7982 });
7983
7984 let (anchor, breakpoint) =
7985 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7986
7987 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7988 menu.on_blur_subscription(Subscription::new(|| {}))
7989 .context(focus_handle)
7990 .when(run_to_cursor, |this| {
7991 let weak_editor = weak_editor.clone();
7992 this.entry("Run to cursor", None, move |window, cx| {
7993 weak_editor
7994 .update(cx, |editor, cx| {
7995 editor.change_selections(
7996 SelectionEffects::no_scroll(),
7997 window,
7998 cx,
7999 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8000 );
8001 })
8002 .ok();
8003
8004 window.dispatch_action(Box::new(RunToCursor), cx);
8005 })
8006 .separator()
8007 })
8008 .when_some(toggle_state_msg, |this, msg| {
8009 this.entry(msg, None, {
8010 let weak_editor = weak_editor.clone();
8011 let breakpoint = breakpoint.clone();
8012 move |_window, cx| {
8013 weak_editor
8014 .update(cx, |this, cx| {
8015 this.edit_breakpoint_at_anchor(
8016 anchor,
8017 breakpoint.as_ref().clone(),
8018 BreakpointEditAction::InvertState,
8019 cx,
8020 );
8021 })
8022 .log_err();
8023 }
8024 })
8025 })
8026 .entry(set_breakpoint_msg, None, {
8027 let weak_editor = weak_editor.clone();
8028 let breakpoint = breakpoint.clone();
8029 move |_window, cx| {
8030 weak_editor
8031 .update(cx, |this, cx| {
8032 this.edit_breakpoint_at_anchor(
8033 anchor,
8034 breakpoint.as_ref().clone(),
8035 BreakpointEditAction::Toggle,
8036 cx,
8037 );
8038 })
8039 .log_err();
8040 }
8041 })
8042 .entry(log_breakpoint_msg, None, {
8043 let breakpoint = breakpoint.clone();
8044 let weak_editor = weak_editor.clone();
8045 move |window, cx| {
8046 weak_editor
8047 .update(cx, |this, cx| {
8048 this.add_edit_breakpoint_block(
8049 anchor,
8050 breakpoint.as_ref(),
8051 BreakpointPromptEditAction::Log,
8052 window,
8053 cx,
8054 );
8055 })
8056 .log_err();
8057 }
8058 })
8059 .entry(condition_breakpoint_msg, None, {
8060 let breakpoint = breakpoint.clone();
8061 let weak_editor = weak_editor.clone();
8062 move |window, cx| {
8063 weak_editor
8064 .update(cx, |this, cx| {
8065 this.add_edit_breakpoint_block(
8066 anchor,
8067 breakpoint.as_ref(),
8068 BreakpointPromptEditAction::Condition,
8069 window,
8070 cx,
8071 );
8072 })
8073 .log_err();
8074 }
8075 })
8076 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.add_edit_breakpoint_block(
8080 anchor,
8081 breakpoint.as_ref(),
8082 BreakpointPromptEditAction::HitCondition,
8083 window,
8084 cx,
8085 );
8086 })
8087 .log_err();
8088 })
8089 })
8090 }
8091
8092 fn render_breakpoint(
8093 &self,
8094 position: Anchor,
8095 row: DisplayRow,
8096 breakpoint: &Breakpoint,
8097 state: Option<BreakpointSessionState>,
8098 cx: &mut Context<Self>,
8099 ) -> IconButton {
8100 let is_rejected = state.is_some_and(|s| !s.verified);
8101 // Is it a breakpoint that shows up when hovering over gutter?
8102 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8103 (false, false),
8104 |PhantomBreakpointIndicator {
8105 is_active,
8106 display_row,
8107 collides_with_existing_breakpoint,
8108 }| {
8109 (
8110 is_active && display_row == row,
8111 collides_with_existing_breakpoint,
8112 )
8113 },
8114 );
8115
8116 let (color, icon) = {
8117 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8118 (false, false) => ui::IconName::DebugBreakpoint,
8119 (true, false) => ui::IconName::DebugLogBreakpoint,
8120 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8121 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8122 };
8123
8124 let color = if is_phantom {
8125 Color::Hint
8126 } else if is_rejected {
8127 Color::Disabled
8128 } else {
8129 Color::Debugger
8130 };
8131
8132 (color, icon)
8133 };
8134
8135 let breakpoint = Arc::from(breakpoint.clone());
8136
8137 let alt_as_text = gpui::Keystroke {
8138 modifiers: Modifiers::secondary_key(),
8139 ..Default::default()
8140 };
8141 let primary_action_text = if breakpoint.is_disabled() {
8142 "Enable breakpoint"
8143 } else if is_phantom && !collides_with_existing {
8144 "Set breakpoint"
8145 } else {
8146 "Unset breakpoint"
8147 };
8148 let focus_handle = self.focus_handle.clone();
8149
8150 let meta = if is_rejected {
8151 SharedString::from("No executable code is associated with this line.")
8152 } else if collides_with_existing && !breakpoint.is_disabled() {
8153 SharedString::from(format!(
8154 "{alt_as_text}-click to disable,\nright-click for more options."
8155 ))
8156 } else {
8157 SharedString::from("Right-click for more options.")
8158 };
8159 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8160 .icon_size(IconSize::XSmall)
8161 .size(ui::ButtonSize::None)
8162 .when(is_rejected, |this| {
8163 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8164 })
8165 .icon_color(color)
8166 .style(ButtonStyle::Transparent)
8167 .on_click(cx.listener({
8168 let breakpoint = breakpoint.clone();
8169
8170 move |editor, event: &ClickEvent, window, cx| {
8171 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8172 BreakpointEditAction::InvertState
8173 } else {
8174 BreakpointEditAction::Toggle
8175 };
8176
8177 window.focus(&editor.focus_handle(cx));
8178 editor.edit_breakpoint_at_anchor(
8179 position,
8180 breakpoint.as_ref().clone(),
8181 edit_action,
8182 cx,
8183 );
8184 }
8185 }))
8186 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8187 editor.set_breakpoint_context_menu(
8188 row,
8189 Some(position),
8190 event.down.position,
8191 window,
8192 cx,
8193 );
8194 }))
8195 .tooltip(move |window, cx| {
8196 Tooltip::with_meta_in(
8197 primary_action_text,
8198 Some(&ToggleBreakpoint),
8199 meta.clone(),
8200 &focus_handle,
8201 window,
8202 cx,
8203 )
8204 })
8205 }
8206
8207 fn build_tasks_context(
8208 project: &Entity<Project>,
8209 buffer: &Entity<Buffer>,
8210 buffer_row: u32,
8211 tasks: &Arc<RunnableTasks>,
8212 cx: &mut Context<Self>,
8213 ) -> Task<Option<task::TaskContext>> {
8214 let position = Point::new(buffer_row, tasks.column);
8215 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8216 let location = Location {
8217 buffer: buffer.clone(),
8218 range: range_start..range_start,
8219 };
8220 // Fill in the environmental variables from the tree-sitter captures
8221 let mut captured_task_variables = TaskVariables::default();
8222 for (capture_name, value) in tasks.extra_variables.clone() {
8223 captured_task_variables.insert(
8224 task::VariableName::Custom(capture_name.into()),
8225 value.clone(),
8226 );
8227 }
8228 project.update(cx, |project, cx| {
8229 project.task_store().update(cx, |task_store, cx| {
8230 task_store.task_context_for_location(captured_task_variables, location, cx)
8231 })
8232 })
8233 }
8234
8235 pub fn spawn_nearest_task(
8236 &mut self,
8237 action: &SpawnNearestTask,
8238 window: &mut Window,
8239 cx: &mut Context<Self>,
8240 ) {
8241 let Some((workspace, _)) = self.workspace.clone() else {
8242 return;
8243 };
8244 let Some(project) = self.project.clone() else {
8245 return;
8246 };
8247
8248 // Try to find a closest, enclosing node using tree-sitter that has a task
8249 let Some((buffer, buffer_row, tasks)) = self
8250 .find_enclosing_node_task(cx)
8251 // Or find the task that's closest in row-distance.
8252 .or_else(|| self.find_closest_task(cx))
8253 else {
8254 return;
8255 };
8256
8257 let reveal_strategy = action.reveal;
8258 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8259 cx.spawn_in(window, async move |_, cx| {
8260 let context = task_context.await?;
8261 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8262
8263 let resolved = &mut resolved_task.resolved;
8264 resolved.reveal = reveal_strategy;
8265
8266 workspace
8267 .update_in(cx, |workspace, window, cx| {
8268 workspace.schedule_resolved_task(
8269 task_source_kind,
8270 resolved_task,
8271 false,
8272 window,
8273 cx,
8274 );
8275 })
8276 .ok()
8277 })
8278 .detach();
8279 }
8280
8281 fn find_closest_task(
8282 &mut self,
8283 cx: &mut Context<Self>,
8284 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8285 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8286
8287 let ((buffer_id, row), tasks) = self
8288 .tasks
8289 .iter()
8290 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8291
8292 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8293 let tasks = Arc::new(tasks.to_owned());
8294 Some((buffer, *row, tasks))
8295 }
8296
8297 fn find_enclosing_node_task(
8298 &mut self,
8299 cx: &mut Context<Self>,
8300 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8301 let snapshot = self.buffer.read(cx).snapshot(cx);
8302 let offset = self.selections.newest::<usize>(cx).head();
8303 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8304 let buffer_id = excerpt.buffer().remote_id();
8305
8306 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8307 let mut cursor = layer.node().walk();
8308
8309 while cursor.goto_first_child_for_byte(offset).is_some() {
8310 if cursor.node().end_byte() == offset {
8311 cursor.goto_next_sibling();
8312 }
8313 }
8314
8315 // Ascend to the smallest ancestor that contains the range and has a task.
8316 loop {
8317 let node = cursor.node();
8318 let node_range = node.byte_range();
8319 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8320
8321 // Check if this node contains our offset
8322 if node_range.start <= offset && node_range.end >= offset {
8323 // If it contains offset, check for task
8324 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8325 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8326 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8327 }
8328 }
8329
8330 if !cursor.goto_parent() {
8331 break;
8332 }
8333 }
8334 None
8335 }
8336
8337 fn render_run_indicator(
8338 &self,
8339 _style: &EditorStyle,
8340 is_active: bool,
8341 row: DisplayRow,
8342 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8343 cx: &mut Context<Self>,
8344 ) -> IconButton {
8345 let color = Color::Muted;
8346 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8347
8348 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8349 .shape(ui::IconButtonShape::Square)
8350 .icon_size(IconSize::XSmall)
8351 .icon_color(color)
8352 .toggle_state(is_active)
8353 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8354 let quick_launch = e.down.button == MouseButton::Left;
8355 window.focus(&editor.focus_handle(cx));
8356 editor.toggle_code_actions(
8357 &ToggleCodeActions {
8358 deployed_from: Some(CodeActionSource::RunMenu(row)),
8359 quick_launch,
8360 },
8361 window,
8362 cx,
8363 );
8364 }))
8365 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8366 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8367 }))
8368 }
8369
8370 pub fn context_menu_visible(&self) -> bool {
8371 !self.edit_prediction_preview_is_active()
8372 && self
8373 .context_menu
8374 .borrow()
8375 .as_ref()
8376 .map_or(false, |menu| menu.visible())
8377 }
8378
8379 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8380 self.context_menu
8381 .borrow()
8382 .as_ref()
8383 .map(|menu| menu.origin())
8384 }
8385
8386 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8387 self.context_menu_options = Some(options);
8388 }
8389
8390 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8391 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8392
8393 fn render_edit_prediction_popover(
8394 &mut self,
8395 text_bounds: &Bounds<Pixels>,
8396 content_origin: gpui::Point<Pixels>,
8397 right_margin: Pixels,
8398 editor_snapshot: &EditorSnapshot,
8399 visible_row_range: Range<DisplayRow>,
8400 scroll_top: f32,
8401 scroll_bottom: f32,
8402 line_layouts: &[LineWithInvisibles],
8403 line_height: Pixels,
8404 scroll_pixel_position: gpui::Point<Pixels>,
8405 newest_selection_head: Option<DisplayPoint>,
8406 editor_width: Pixels,
8407 style: &EditorStyle,
8408 window: &mut Window,
8409 cx: &mut App,
8410 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8411 if self.mode().is_minimap() {
8412 return None;
8413 }
8414 let active_inline_completion = self.active_inline_completion.as_ref()?;
8415
8416 if self.edit_prediction_visible_in_cursor_popover(true) {
8417 return None;
8418 }
8419
8420 match &active_inline_completion.completion {
8421 InlineCompletion::Move { target, .. } => {
8422 let target_display_point = target.to_display_point(editor_snapshot);
8423
8424 if self.edit_prediction_requires_modifier() {
8425 if !self.edit_prediction_preview_is_active() {
8426 return None;
8427 }
8428
8429 self.render_edit_prediction_modifier_jump_popover(
8430 text_bounds,
8431 content_origin,
8432 visible_row_range,
8433 line_layouts,
8434 line_height,
8435 scroll_pixel_position,
8436 newest_selection_head,
8437 target_display_point,
8438 window,
8439 cx,
8440 )
8441 } else {
8442 self.render_edit_prediction_eager_jump_popover(
8443 text_bounds,
8444 content_origin,
8445 editor_snapshot,
8446 visible_row_range,
8447 scroll_top,
8448 scroll_bottom,
8449 line_height,
8450 scroll_pixel_position,
8451 target_display_point,
8452 editor_width,
8453 window,
8454 cx,
8455 )
8456 }
8457 }
8458 InlineCompletion::Edit {
8459 display_mode: EditDisplayMode::Inline,
8460 ..
8461 } => None,
8462 InlineCompletion::Edit {
8463 display_mode: EditDisplayMode::TabAccept,
8464 edits,
8465 ..
8466 } => {
8467 let range = &edits.first()?.0;
8468 let target_display_point = range.end.to_display_point(editor_snapshot);
8469
8470 self.render_edit_prediction_end_of_line_popover(
8471 "Accept",
8472 editor_snapshot,
8473 visible_row_range,
8474 target_display_point,
8475 line_height,
8476 scroll_pixel_position,
8477 content_origin,
8478 editor_width,
8479 window,
8480 cx,
8481 )
8482 }
8483 InlineCompletion::Edit {
8484 edits,
8485 edit_preview,
8486 display_mode: EditDisplayMode::DiffPopover,
8487 snapshot,
8488 } => self.render_edit_prediction_diff_popover(
8489 text_bounds,
8490 content_origin,
8491 right_margin,
8492 editor_snapshot,
8493 visible_row_range,
8494 line_layouts,
8495 line_height,
8496 scroll_pixel_position,
8497 newest_selection_head,
8498 editor_width,
8499 style,
8500 edits,
8501 edit_preview,
8502 snapshot,
8503 window,
8504 cx,
8505 ),
8506 }
8507 }
8508
8509 fn render_edit_prediction_modifier_jump_popover(
8510 &mut self,
8511 text_bounds: &Bounds<Pixels>,
8512 content_origin: gpui::Point<Pixels>,
8513 visible_row_range: Range<DisplayRow>,
8514 line_layouts: &[LineWithInvisibles],
8515 line_height: Pixels,
8516 scroll_pixel_position: gpui::Point<Pixels>,
8517 newest_selection_head: Option<DisplayPoint>,
8518 target_display_point: DisplayPoint,
8519 window: &mut Window,
8520 cx: &mut App,
8521 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8522 let scrolled_content_origin =
8523 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8524
8525 const SCROLL_PADDING_Y: Pixels = px(12.);
8526
8527 if target_display_point.row() < visible_row_range.start {
8528 return self.render_edit_prediction_scroll_popover(
8529 |_| SCROLL_PADDING_Y,
8530 IconName::ArrowUp,
8531 visible_row_range,
8532 line_layouts,
8533 newest_selection_head,
8534 scrolled_content_origin,
8535 window,
8536 cx,
8537 );
8538 } else if target_display_point.row() >= visible_row_range.end {
8539 return self.render_edit_prediction_scroll_popover(
8540 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8541 IconName::ArrowDown,
8542 visible_row_range,
8543 line_layouts,
8544 newest_selection_head,
8545 scrolled_content_origin,
8546 window,
8547 cx,
8548 );
8549 }
8550
8551 const POLE_WIDTH: Pixels = px(2.);
8552
8553 let line_layout =
8554 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8555 let target_column = target_display_point.column() as usize;
8556
8557 let target_x = line_layout.x_for_index(target_column);
8558 let target_y =
8559 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8560
8561 let flag_on_right = target_x < text_bounds.size.width / 2.;
8562
8563 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8564 border_color.l += 0.001;
8565
8566 let mut element = v_flex()
8567 .items_end()
8568 .when(flag_on_right, |el| el.items_start())
8569 .child(if flag_on_right {
8570 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8571 .rounded_bl(px(0.))
8572 .rounded_tl(px(0.))
8573 .border_l_2()
8574 .border_color(border_color)
8575 } else {
8576 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8577 .rounded_br(px(0.))
8578 .rounded_tr(px(0.))
8579 .border_r_2()
8580 .border_color(border_color)
8581 })
8582 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8583 .into_any();
8584
8585 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8586
8587 let mut origin = scrolled_content_origin + point(target_x, target_y)
8588 - point(
8589 if flag_on_right {
8590 POLE_WIDTH
8591 } else {
8592 size.width - POLE_WIDTH
8593 },
8594 size.height - line_height,
8595 );
8596
8597 origin.x = origin.x.max(content_origin.x);
8598
8599 element.prepaint_at(origin, window, cx);
8600
8601 Some((element, origin))
8602 }
8603
8604 fn render_edit_prediction_scroll_popover(
8605 &mut self,
8606 to_y: impl Fn(Size<Pixels>) -> Pixels,
8607 scroll_icon: IconName,
8608 visible_row_range: Range<DisplayRow>,
8609 line_layouts: &[LineWithInvisibles],
8610 newest_selection_head: Option<DisplayPoint>,
8611 scrolled_content_origin: gpui::Point<Pixels>,
8612 window: &mut Window,
8613 cx: &mut App,
8614 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8615 let mut element = self
8616 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8617 .into_any();
8618
8619 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8620
8621 let cursor = newest_selection_head?;
8622 let cursor_row_layout =
8623 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8624 let cursor_column = cursor.column() as usize;
8625
8626 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8627
8628 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8629
8630 element.prepaint_at(origin, window, cx);
8631 Some((element, origin))
8632 }
8633
8634 fn render_edit_prediction_eager_jump_popover(
8635 &mut self,
8636 text_bounds: &Bounds<Pixels>,
8637 content_origin: gpui::Point<Pixels>,
8638 editor_snapshot: &EditorSnapshot,
8639 visible_row_range: Range<DisplayRow>,
8640 scroll_top: f32,
8641 scroll_bottom: f32,
8642 line_height: Pixels,
8643 scroll_pixel_position: gpui::Point<Pixels>,
8644 target_display_point: DisplayPoint,
8645 editor_width: Pixels,
8646 window: &mut Window,
8647 cx: &mut App,
8648 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8649 if target_display_point.row().as_f32() < scroll_top {
8650 let mut element = self
8651 .render_edit_prediction_line_popover(
8652 "Jump to Edit",
8653 Some(IconName::ArrowUp),
8654 window,
8655 cx,
8656 )?
8657 .into_any();
8658
8659 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8660 let offset = point(
8661 (text_bounds.size.width - size.width) / 2.,
8662 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8663 );
8664
8665 let origin = text_bounds.origin + offset;
8666 element.prepaint_at(origin, window, cx);
8667 Some((element, origin))
8668 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8669 let mut element = self
8670 .render_edit_prediction_line_popover(
8671 "Jump to Edit",
8672 Some(IconName::ArrowDown),
8673 window,
8674 cx,
8675 )?
8676 .into_any();
8677
8678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8679 let offset = point(
8680 (text_bounds.size.width - size.width) / 2.,
8681 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8682 );
8683
8684 let origin = text_bounds.origin + offset;
8685 element.prepaint_at(origin, window, cx);
8686 Some((element, origin))
8687 } else {
8688 self.render_edit_prediction_end_of_line_popover(
8689 "Jump to Edit",
8690 editor_snapshot,
8691 visible_row_range,
8692 target_display_point,
8693 line_height,
8694 scroll_pixel_position,
8695 content_origin,
8696 editor_width,
8697 window,
8698 cx,
8699 )
8700 }
8701 }
8702
8703 fn render_edit_prediction_end_of_line_popover(
8704 self: &mut Editor,
8705 label: &'static str,
8706 editor_snapshot: &EditorSnapshot,
8707 visible_row_range: Range<DisplayRow>,
8708 target_display_point: DisplayPoint,
8709 line_height: Pixels,
8710 scroll_pixel_position: gpui::Point<Pixels>,
8711 content_origin: gpui::Point<Pixels>,
8712 editor_width: Pixels,
8713 window: &mut Window,
8714 cx: &mut App,
8715 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8716 let target_line_end = DisplayPoint::new(
8717 target_display_point.row(),
8718 editor_snapshot.line_len(target_display_point.row()),
8719 );
8720
8721 let mut element = self
8722 .render_edit_prediction_line_popover(label, None, window, cx)?
8723 .into_any();
8724
8725 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8726
8727 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8728
8729 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8730 let mut origin = start_point
8731 + line_origin
8732 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8733 origin.x = origin.x.max(content_origin.x);
8734
8735 let max_x = content_origin.x + editor_width - size.width;
8736
8737 if origin.x > max_x {
8738 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8739
8740 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8741 origin.y += offset;
8742 IconName::ArrowUp
8743 } else {
8744 origin.y -= offset;
8745 IconName::ArrowDown
8746 };
8747
8748 element = self
8749 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8750 .into_any();
8751
8752 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8753
8754 origin.x = content_origin.x + editor_width - size.width - px(2.);
8755 }
8756
8757 element.prepaint_at(origin, window, cx);
8758 Some((element, origin))
8759 }
8760
8761 fn render_edit_prediction_diff_popover(
8762 self: &Editor,
8763 text_bounds: &Bounds<Pixels>,
8764 content_origin: gpui::Point<Pixels>,
8765 right_margin: Pixels,
8766 editor_snapshot: &EditorSnapshot,
8767 visible_row_range: Range<DisplayRow>,
8768 line_layouts: &[LineWithInvisibles],
8769 line_height: Pixels,
8770 scroll_pixel_position: gpui::Point<Pixels>,
8771 newest_selection_head: Option<DisplayPoint>,
8772 editor_width: Pixels,
8773 style: &EditorStyle,
8774 edits: &Vec<(Range<Anchor>, String)>,
8775 edit_preview: &Option<language::EditPreview>,
8776 snapshot: &language::BufferSnapshot,
8777 window: &mut Window,
8778 cx: &mut App,
8779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8780 let edit_start = edits
8781 .first()
8782 .unwrap()
8783 .0
8784 .start
8785 .to_display_point(editor_snapshot);
8786 let edit_end = edits
8787 .last()
8788 .unwrap()
8789 .0
8790 .end
8791 .to_display_point(editor_snapshot);
8792
8793 let is_visible = visible_row_range.contains(&edit_start.row())
8794 || visible_row_range.contains(&edit_end.row());
8795 if !is_visible {
8796 return None;
8797 }
8798
8799 let highlighted_edits =
8800 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8801
8802 let styled_text = highlighted_edits.to_styled_text(&style.text);
8803 let line_count = highlighted_edits.text.lines().count();
8804
8805 const BORDER_WIDTH: Pixels = px(1.);
8806
8807 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8808 let has_keybind = keybind.is_some();
8809
8810 let mut element = h_flex()
8811 .items_start()
8812 .child(
8813 h_flex()
8814 .bg(cx.theme().colors().editor_background)
8815 .border(BORDER_WIDTH)
8816 .shadow_xs()
8817 .border_color(cx.theme().colors().border)
8818 .rounded_l_lg()
8819 .when(line_count > 1, |el| el.rounded_br_lg())
8820 .pr_1()
8821 .child(styled_text),
8822 )
8823 .child(
8824 h_flex()
8825 .h(line_height + BORDER_WIDTH * 2.)
8826 .px_1p5()
8827 .gap_1()
8828 // Workaround: For some reason, there's a gap if we don't do this
8829 .ml(-BORDER_WIDTH)
8830 .shadow(vec![gpui::BoxShadow {
8831 color: gpui::black().opacity(0.05),
8832 offset: point(px(1.), px(1.)),
8833 blur_radius: px(2.),
8834 spread_radius: px(0.),
8835 }])
8836 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8837 .border(BORDER_WIDTH)
8838 .border_color(cx.theme().colors().border)
8839 .rounded_r_lg()
8840 .id("edit_prediction_diff_popover_keybind")
8841 .when(!has_keybind, |el| {
8842 let status_colors = cx.theme().status();
8843
8844 el.bg(status_colors.error_background)
8845 .border_color(status_colors.error.opacity(0.6))
8846 .child(Icon::new(IconName::Info).color(Color::Error))
8847 .cursor_default()
8848 .hoverable_tooltip(move |_window, cx| {
8849 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8850 })
8851 })
8852 .children(keybind),
8853 )
8854 .into_any();
8855
8856 let longest_row =
8857 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8858 let longest_line_width = if visible_row_range.contains(&longest_row) {
8859 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8860 } else {
8861 layout_line(
8862 longest_row,
8863 editor_snapshot,
8864 style,
8865 editor_width,
8866 |_| false,
8867 window,
8868 cx,
8869 )
8870 .width
8871 };
8872
8873 let viewport_bounds =
8874 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8875 right: -right_margin,
8876 ..Default::default()
8877 });
8878
8879 let x_after_longest =
8880 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8881 - scroll_pixel_position.x;
8882
8883 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8884
8885 // Fully visible if it can be displayed within the window (allow overlapping other
8886 // panes). However, this is only allowed if the popover starts within text_bounds.
8887 let can_position_to_the_right = x_after_longest < text_bounds.right()
8888 && x_after_longest + element_bounds.width < viewport_bounds.right();
8889
8890 let mut origin = if can_position_to_the_right {
8891 point(
8892 x_after_longest,
8893 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8894 - scroll_pixel_position.y,
8895 )
8896 } else {
8897 let cursor_row = newest_selection_head.map(|head| head.row());
8898 let above_edit = edit_start
8899 .row()
8900 .0
8901 .checked_sub(line_count as u32)
8902 .map(DisplayRow);
8903 let below_edit = Some(edit_end.row() + 1);
8904 let above_cursor =
8905 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8906 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8907
8908 // Place the edit popover adjacent to the edit if there is a location
8909 // available that is onscreen and does not obscure the cursor. Otherwise,
8910 // place it adjacent to the cursor.
8911 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8912 .into_iter()
8913 .flatten()
8914 .find(|&start_row| {
8915 let end_row = start_row + line_count as u32;
8916 visible_row_range.contains(&start_row)
8917 && visible_row_range.contains(&end_row)
8918 && cursor_row.map_or(true, |cursor_row| {
8919 !((start_row..end_row).contains(&cursor_row))
8920 })
8921 })?;
8922
8923 content_origin
8924 + point(
8925 -scroll_pixel_position.x,
8926 row_target.as_f32() * line_height - scroll_pixel_position.y,
8927 )
8928 };
8929
8930 origin.x -= BORDER_WIDTH;
8931
8932 window.defer_draw(element, origin, 1);
8933
8934 // Do not return an element, since it will already be drawn due to defer_draw.
8935 None
8936 }
8937
8938 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8939 px(30.)
8940 }
8941
8942 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8943 if self.read_only(cx) {
8944 cx.theme().players().read_only()
8945 } else {
8946 self.style.as_ref().unwrap().local_player
8947 }
8948 }
8949
8950 fn render_edit_prediction_accept_keybind(
8951 &self,
8952 window: &mut Window,
8953 cx: &App,
8954 ) -> Option<AnyElement> {
8955 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8956 let accept_keystroke = accept_binding.keystroke()?;
8957
8958 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8959
8960 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8961 Color::Accent
8962 } else {
8963 Color::Muted
8964 };
8965
8966 h_flex()
8967 .px_0p5()
8968 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8969 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8970 .text_size(TextSize::XSmall.rems(cx))
8971 .child(h_flex().children(ui::render_modifiers(
8972 &accept_keystroke.modifiers,
8973 PlatformStyle::platform(),
8974 Some(modifiers_color),
8975 Some(IconSize::XSmall.rems().into()),
8976 true,
8977 )))
8978 .when(is_platform_style_mac, |parent| {
8979 parent.child(accept_keystroke.key.clone())
8980 })
8981 .when(!is_platform_style_mac, |parent| {
8982 parent.child(
8983 Key::new(
8984 util::capitalize(&accept_keystroke.key),
8985 Some(Color::Default),
8986 )
8987 .size(Some(IconSize::XSmall.rems().into())),
8988 )
8989 })
8990 .into_any()
8991 .into()
8992 }
8993
8994 fn render_edit_prediction_line_popover(
8995 &self,
8996 label: impl Into<SharedString>,
8997 icon: Option<IconName>,
8998 window: &mut Window,
8999 cx: &App,
9000 ) -> Option<Stateful<Div>> {
9001 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9002
9003 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9004 let has_keybind = keybind.is_some();
9005
9006 let result = h_flex()
9007 .id("ep-line-popover")
9008 .py_0p5()
9009 .pl_1()
9010 .pr(padding_right)
9011 .gap_1()
9012 .rounded_md()
9013 .border_1()
9014 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9015 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9016 .shadow_xs()
9017 .when(!has_keybind, |el| {
9018 let status_colors = cx.theme().status();
9019
9020 el.bg(status_colors.error_background)
9021 .border_color(status_colors.error.opacity(0.6))
9022 .pl_2()
9023 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9024 .cursor_default()
9025 .hoverable_tooltip(move |_window, cx| {
9026 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9027 })
9028 })
9029 .children(keybind)
9030 .child(
9031 Label::new(label)
9032 .size(LabelSize::Small)
9033 .when(!has_keybind, |el| {
9034 el.color(cx.theme().status().error.into()).strikethrough()
9035 }),
9036 )
9037 .when(!has_keybind, |el| {
9038 el.child(
9039 h_flex().ml_1().child(
9040 Icon::new(IconName::Info)
9041 .size(IconSize::Small)
9042 .color(cx.theme().status().error.into()),
9043 ),
9044 )
9045 })
9046 .when_some(icon, |element, icon| {
9047 element.child(
9048 div()
9049 .mt(px(1.5))
9050 .child(Icon::new(icon).size(IconSize::Small)),
9051 )
9052 });
9053
9054 Some(result)
9055 }
9056
9057 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9058 let accent_color = cx.theme().colors().text_accent;
9059 let editor_bg_color = cx.theme().colors().editor_background;
9060 editor_bg_color.blend(accent_color.opacity(0.1))
9061 }
9062
9063 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9064 let accent_color = cx.theme().colors().text_accent;
9065 let editor_bg_color = cx.theme().colors().editor_background;
9066 editor_bg_color.blend(accent_color.opacity(0.6))
9067 }
9068
9069 fn render_edit_prediction_cursor_popover(
9070 &self,
9071 min_width: Pixels,
9072 max_width: Pixels,
9073 cursor_point: Point,
9074 style: &EditorStyle,
9075 accept_keystroke: Option<&gpui::Keystroke>,
9076 _window: &Window,
9077 cx: &mut Context<Editor>,
9078 ) -> Option<AnyElement> {
9079 let provider = self.edit_prediction_provider.as_ref()?;
9080
9081 if provider.provider.needs_terms_acceptance(cx) {
9082 return Some(
9083 h_flex()
9084 .min_w(min_width)
9085 .flex_1()
9086 .px_2()
9087 .py_1()
9088 .gap_3()
9089 .elevation_2(cx)
9090 .hover(|style| style.bg(cx.theme().colors().element_hover))
9091 .id("accept-terms")
9092 .cursor_pointer()
9093 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9094 .on_click(cx.listener(|this, _event, window, cx| {
9095 cx.stop_propagation();
9096 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9097 window.dispatch_action(
9098 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9099 cx,
9100 );
9101 }))
9102 .child(
9103 h_flex()
9104 .flex_1()
9105 .gap_2()
9106 .child(Icon::new(IconName::ZedPredict))
9107 .child(Label::new("Accept Terms of Service"))
9108 .child(div().w_full())
9109 .child(
9110 Icon::new(IconName::ArrowUpRight)
9111 .color(Color::Muted)
9112 .size(IconSize::Small),
9113 )
9114 .into_any_element(),
9115 )
9116 .into_any(),
9117 );
9118 }
9119
9120 let is_refreshing = provider.provider.is_refreshing(cx);
9121
9122 fn pending_completion_container() -> Div {
9123 h_flex()
9124 .h_full()
9125 .flex_1()
9126 .gap_2()
9127 .child(Icon::new(IconName::ZedPredict))
9128 }
9129
9130 let completion = match &self.active_inline_completion {
9131 Some(prediction) => {
9132 if !self.has_visible_completions_menu() {
9133 const RADIUS: Pixels = px(6.);
9134 const BORDER_WIDTH: Pixels = px(1.);
9135
9136 return Some(
9137 h_flex()
9138 .elevation_2(cx)
9139 .border(BORDER_WIDTH)
9140 .border_color(cx.theme().colors().border)
9141 .when(accept_keystroke.is_none(), |el| {
9142 el.border_color(cx.theme().status().error)
9143 })
9144 .rounded(RADIUS)
9145 .rounded_tl(px(0.))
9146 .overflow_hidden()
9147 .child(div().px_1p5().child(match &prediction.completion {
9148 InlineCompletion::Move { target, snapshot } => {
9149 use text::ToPoint as _;
9150 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9151 {
9152 Icon::new(IconName::ZedPredictDown)
9153 } else {
9154 Icon::new(IconName::ZedPredictUp)
9155 }
9156 }
9157 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9158 }))
9159 .child(
9160 h_flex()
9161 .gap_1()
9162 .py_1()
9163 .px_2()
9164 .rounded_r(RADIUS - BORDER_WIDTH)
9165 .border_l_1()
9166 .border_color(cx.theme().colors().border)
9167 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9168 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9169 el.child(
9170 Label::new("Hold")
9171 .size(LabelSize::Small)
9172 .when(accept_keystroke.is_none(), |el| {
9173 el.strikethrough()
9174 })
9175 .line_height_style(LineHeightStyle::UiLabel),
9176 )
9177 })
9178 .id("edit_prediction_cursor_popover_keybind")
9179 .when(accept_keystroke.is_none(), |el| {
9180 let status_colors = cx.theme().status();
9181
9182 el.bg(status_colors.error_background)
9183 .border_color(status_colors.error.opacity(0.6))
9184 .child(Icon::new(IconName::Info).color(Color::Error))
9185 .cursor_default()
9186 .hoverable_tooltip(move |_window, cx| {
9187 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9188 .into()
9189 })
9190 })
9191 .when_some(
9192 accept_keystroke.as_ref(),
9193 |el, accept_keystroke| {
9194 el.child(h_flex().children(ui::render_modifiers(
9195 &accept_keystroke.modifiers,
9196 PlatformStyle::platform(),
9197 Some(Color::Default),
9198 Some(IconSize::XSmall.rems().into()),
9199 false,
9200 )))
9201 },
9202 ),
9203 )
9204 .into_any(),
9205 );
9206 }
9207
9208 self.render_edit_prediction_cursor_popover_preview(
9209 prediction,
9210 cursor_point,
9211 style,
9212 cx,
9213 )?
9214 }
9215
9216 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9217 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9218 stale_completion,
9219 cursor_point,
9220 style,
9221 cx,
9222 )?,
9223
9224 None => {
9225 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9226 }
9227 },
9228
9229 None => pending_completion_container().child(Label::new("No Prediction")),
9230 };
9231
9232 let completion = if is_refreshing {
9233 completion
9234 .with_animation(
9235 "loading-completion",
9236 Animation::new(Duration::from_secs(2))
9237 .repeat()
9238 .with_easing(pulsating_between(0.4, 0.8)),
9239 |label, delta| label.opacity(delta),
9240 )
9241 .into_any_element()
9242 } else {
9243 completion.into_any_element()
9244 };
9245
9246 let has_completion = self.active_inline_completion.is_some();
9247
9248 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9249 Some(
9250 h_flex()
9251 .min_w(min_width)
9252 .max_w(max_width)
9253 .flex_1()
9254 .elevation_2(cx)
9255 .border_color(cx.theme().colors().border)
9256 .child(
9257 div()
9258 .flex_1()
9259 .py_1()
9260 .px_2()
9261 .overflow_hidden()
9262 .child(completion),
9263 )
9264 .when_some(accept_keystroke, |el, accept_keystroke| {
9265 if !accept_keystroke.modifiers.modified() {
9266 return el;
9267 }
9268
9269 el.child(
9270 h_flex()
9271 .h_full()
9272 .border_l_1()
9273 .rounded_r_lg()
9274 .border_color(cx.theme().colors().border)
9275 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9276 .gap_1()
9277 .py_1()
9278 .px_2()
9279 .child(
9280 h_flex()
9281 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9282 .when(is_platform_style_mac, |parent| parent.gap_1())
9283 .child(h_flex().children(ui::render_modifiers(
9284 &accept_keystroke.modifiers,
9285 PlatformStyle::platform(),
9286 Some(if !has_completion {
9287 Color::Muted
9288 } else {
9289 Color::Default
9290 }),
9291 None,
9292 false,
9293 ))),
9294 )
9295 .child(Label::new("Preview").into_any_element())
9296 .opacity(if has_completion { 1.0 } else { 0.4 }),
9297 )
9298 })
9299 .into_any(),
9300 )
9301 }
9302
9303 fn render_edit_prediction_cursor_popover_preview(
9304 &self,
9305 completion: &InlineCompletionState,
9306 cursor_point: Point,
9307 style: &EditorStyle,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<Div> {
9310 use text::ToPoint as _;
9311
9312 fn render_relative_row_jump(
9313 prefix: impl Into<String>,
9314 current_row: u32,
9315 target_row: u32,
9316 ) -> Div {
9317 let (row_diff, arrow) = if target_row < current_row {
9318 (current_row - target_row, IconName::ArrowUp)
9319 } else {
9320 (target_row - current_row, IconName::ArrowDown)
9321 };
9322
9323 h_flex()
9324 .child(
9325 Label::new(format!("{}{}", prefix.into(), row_diff))
9326 .color(Color::Muted)
9327 .size(LabelSize::Small),
9328 )
9329 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9330 }
9331
9332 match &completion.completion {
9333 InlineCompletion::Move {
9334 target, snapshot, ..
9335 } => Some(
9336 h_flex()
9337 .px_2()
9338 .gap_2()
9339 .flex_1()
9340 .child(
9341 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9342 Icon::new(IconName::ZedPredictDown)
9343 } else {
9344 Icon::new(IconName::ZedPredictUp)
9345 },
9346 )
9347 .child(Label::new("Jump to Edit")),
9348 ),
9349
9350 InlineCompletion::Edit {
9351 edits,
9352 edit_preview,
9353 snapshot,
9354 display_mode: _,
9355 } => {
9356 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9357
9358 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9359 &snapshot,
9360 &edits,
9361 edit_preview.as_ref()?,
9362 true,
9363 cx,
9364 )
9365 .first_line_preview();
9366
9367 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9368 .with_default_highlights(&style.text, highlighted_edits.highlights);
9369
9370 let preview = h_flex()
9371 .gap_1()
9372 .min_w_16()
9373 .child(styled_text)
9374 .when(has_more_lines, |parent| parent.child("…"));
9375
9376 let left = if first_edit_row != cursor_point.row {
9377 render_relative_row_jump("", cursor_point.row, first_edit_row)
9378 .into_any_element()
9379 } else {
9380 Icon::new(IconName::ZedPredict).into_any_element()
9381 };
9382
9383 Some(
9384 h_flex()
9385 .h_full()
9386 .flex_1()
9387 .gap_2()
9388 .pr_1()
9389 .overflow_x_hidden()
9390 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9391 .child(left)
9392 .child(preview),
9393 )
9394 }
9395 }
9396 }
9397
9398 pub fn render_context_menu(
9399 &self,
9400 style: &EditorStyle,
9401 max_height_in_lines: u32,
9402 window: &mut Window,
9403 cx: &mut Context<Editor>,
9404 ) -> Option<AnyElement> {
9405 let menu = self.context_menu.borrow();
9406 let menu = menu.as_ref()?;
9407 if !menu.visible() {
9408 return None;
9409 };
9410 Some(menu.render(style, max_height_in_lines, window, cx))
9411 }
9412
9413 fn render_context_menu_aside(
9414 &mut self,
9415 max_size: Size<Pixels>,
9416 window: &mut Window,
9417 cx: &mut Context<Editor>,
9418 ) -> Option<AnyElement> {
9419 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9420 if menu.visible() {
9421 menu.render_aside(max_size, window, cx)
9422 } else {
9423 None
9424 }
9425 })
9426 }
9427
9428 fn hide_context_menu(
9429 &mut self,
9430 window: &mut Window,
9431 cx: &mut Context<Self>,
9432 ) -> Option<CodeContextMenu> {
9433 cx.notify();
9434 self.completion_tasks.clear();
9435 let context_menu = self.context_menu.borrow_mut().take();
9436 self.stale_inline_completion_in_menu.take();
9437 self.update_visible_inline_completion(window, cx);
9438 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9439 if let Some(completion_provider) = &self.completion_provider {
9440 completion_provider.selection_changed(None, window, cx);
9441 }
9442 }
9443 context_menu
9444 }
9445
9446 fn show_snippet_choices(
9447 &mut self,
9448 choices: &Vec<String>,
9449 selection: Range<Anchor>,
9450 cx: &mut Context<Self>,
9451 ) {
9452 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9453 (Some(a), Some(b)) if a == b => a,
9454 _ => {
9455 log::error!("expected anchor range to have matching buffer IDs");
9456 return;
9457 }
9458 };
9459 let multi_buffer = self.buffer().read(cx);
9460 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9461 return;
9462 };
9463
9464 let id = post_inc(&mut self.next_completion_id);
9465 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9466 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9467 CompletionsMenu::new_snippet_choices(
9468 id,
9469 true,
9470 choices,
9471 selection,
9472 buffer,
9473 snippet_sort_order,
9474 ),
9475 ));
9476 }
9477
9478 pub fn insert_snippet(
9479 &mut self,
9480 insertion_ranges: &[Range<usize>],
9481 snippet: Snippet,
9482 window: &mut Window,
9483 cx: &mut Context<Self>,
9484 ) -> Result<()> {
9485 struct Tabstop<T> {
9486 is_end_tabstop: bool,
9487 ranges: Vec<Range<T>>,
9488 choices: Option<Vec<String>>,
9489 }
9490
9491 let tabstops = self.buffer.update(cx, |buffer, cx| {
9492 let snippet_text: Arc<str> = snippet.text.clone().into();
9493 let edits = insertion_ranges
9494 .iter()
9495 .cloned()
9496 .map(|range| (range, snippet_text.clone()));
9497 let autoindent_mode = AutoindentMode::Block {
9498 original_indent_columns: Vec::new(),
9499 };
9500 buffer.edit(edits, Some(autoindent_mode), cx);
9501
9502 let snapshot = &*buffer.read(cx);
9503 let snippet = &snippet;
9504 snippet
9505 .tabstops
9506 .iter()
9507 .map(|tabstop| {
9508 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9509 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9510 });
9511 let mut tabstop_ranges = tabstop
9512 .ranges
9513 .iter()
9514 .flat_map(|tabstop_range| {
9515 let mut delta = 0_isize;
9516 insertion_ranges.iter().map(move |insertion_range| {
9517 let insertion_start = insertion_range.start as isize + delta;
9518 delta +=
9519 snippet.text.len() as isize - insertion_range.len() as isize;
9520
9521 let start = ((insertion_start + tabstop_range.start) as usize)
9522 .min(snapshot.len());
9523 let end = ((insertion_start + tabstop_range.end) as usize)
9524 .min(snapshot.len());
9525 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9526 })
9527 })
9528 .collect::<Vec<_>>();
9529 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9530
9531 Tabstop {
9532 is_end_tabstop,
9533 ranges: tabstop_ranges,
9534 choices: tabstop.choices.clone(),
9535 }
9536 })
9537 .collect::<Vec<_>>()
9538 });
9539 if let Some(tabstop) = tabstops.first() {
9540 self.change_selections(Default::default(), window, cx, |s| {
9541 // Reverse order so that the first range is the newest created selection.
9542 // Completions will use it and autoscroll will prioritize it.
9543 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9544 });
9545
9546 if let Some(choices) = &tabstop.choices {
9547 if let Some(selection) = tabstop.ranges.first() {
9548 self.show_snippet_choices(choices, selection.clone(), cx)
9549 }
9550 }
9551
9552 // If we're already at the last tabstop and it's at the end of the snippet,
9553 // we're done, we don't need to keep the state around.
9554 if !tabstop.is_end_tabstop {
9555 let choices = tabstops
9556 .iter()
9557 .map(|tabstop| tabstop.choices.clone())
9558 .collect();
9559
9560 let ranges = tabstops
9561 .into_iter()
9562 .map(|tabstop| tabstop.ranges)
9563 .collect::<Vec<_>>();
9564
9565 self.snippet_stack.push(SnippetState {
9566 active_index: 0,
9567 ranges,
9568 choices,
9569 });
9570 }
9571
9572 // Check whether the just-entered snippet ends with an auto-closable bracket.
9573 if self.autoclose_regions.is_empty() {
9574 let snapshot = self.buffer.read(cx).snapshot(cx);
9575 let mut all_selections = self.selections.all::<Point>(cx);
9576 for selection in &mut all_selections {
9577 let selection_head = selection.head();
9578 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9579 continue;
9580 };
9581
9582 let mut bracket_pair = None;
9583 let max_lookup_length = scope
9584 .brackets()
9585 .map(|(pair, _)| {
9586 pair.start
9587 .as_str()
9588 .chars()
9589 .count()
9590 .max(pair.end.as_str().chars().count())
9591 })
9592 .max();
9593 if let Some(max_lookup_length) = max_lookup_length {
9594 let next_text = snapshot
9595 .chars_at(selection_head)
9596 .take(max_lookup_length)
9597 .collect::<String>();
9598 let prev_text = snapshot
9599 .reversed_chars_at(selection_head)
9600 .take(max_lookup_length)
9601 .collect::<String>();
9602
9603 for (pair, enabled) in scope.brackets() {
9604 if enabled
9605 && pair.close
9606 && prev_text.starts_with(pair.start.as_str())
9607 && next_text.starts_with(pair.end.as_str())
9608 {
9609 bracket_pair = Some(pair.clone());
9610 break;
9611 }
9612 }
9613 }
9614
9615 if let Some(pair) = bracket_pair {
9616 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9617 let autoclose_enabled =
9618 self.use_autoclose && snapshot_settings.use_autoclose;
9619 if autoclose_enabled {
9620 let start = snapshot.anchor_after(selection_head);
9621 let end = snapshot.anchor_after(selection_head);
9622 self.autoclose_regions.push(AutocloseRegion {
9623 selection_id: selection.id,
9624 range: start..end,
9625 pair,
9626 });
9627 }
9628 }
9629 }
9630 }
9631 }
9632 Ok(())
9633 }
9634
9635 pub fn move_to_next_snippet_tabstop(
9636 &mut self,
9637 window: &mut Window,
9638 cx: &mut Context<Self>,
9639 ) -> bool {
9640 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9641 }
9642
9643 pub fn move_to_prev_snippet_tabstop(
9644 &mut self,
9645 window: &mut Window,
9646 cx: &mut Context<Self>,
9647 ) -> bool {
9648 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9649 }
9650
9651 pub fn move_to_snippet_tabstop(
9652 &mut self,
9653 bias: Bias,
9654 window: &mut Window,
9655 cx: &mut Context<Self>,
9656 ) -> bool {
9657 if let Some(mut snippet) = self.snippet_stack.pop() {
9658 match bias {
9659 Bias::Left => {
9660 if snippet.active_index > 0 {
9661 snippet.active_index -= 1;
9662 } else {
9663 self.snippet_stack.push(snippet);
9664 return false;
9665 }
9666 }
9667 Bias::Right => {
9668 if snippet.active_index + 1 < snippet.ranges.len() {
9669 snippet.active_index += 1;
9670 } else {
9671 self.snippet_stack.push(snippet);
9672 return false;
9673 }
9674 }
9675 }
9676 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9677 self.change_selections(Default::default(), window, cx, |s| {
9678 // Reverse order so that the first range is the newest created selection.
9679 // Completions will use it and autoscroll will prioritize it.
9680 s.select_ranges(current_ranges.iter().rev().cloned())
9681 });
9682
9683 if let Some(choices) = &snippet.choices[snippet.active_index] {
9684 if let Some(selection) = current_ranges.first() {
9685 self.show_snippet_choices(&choices, selection.clone(), cx);
9686 }
9687 }
9688
9689 // If snippet state is not at the last tabstop, push it back on the stack
9690 if snippet.active_index + 1 < snippet.ranges.len() {
9691 self.snippet_stack.push(snippet);
9692 }
9693 return true;
9694 }
9695 }
9696
9697 false
9698 }
9699
9700 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9701 self.transact(window, cx, |this, window, cx| {
9702 this.select_all(&SelectAll, window, cx);
9703 this.insert("", window, cx);
9704 });
9705 }
9706
9707 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9709 self.transact(window, cx, |this, window, cx| {
9710 this.select_autoclose_pair(window, cx);
9711 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9712 if !this.linked_edit_ranges.is_empty() {
9713 let selections = this.selections.all::<MultiBufferPoint>(cx);
9714 let snapshot = this.buffer.read(cx).snapshot(cx);
9715
9716 for selection in selections.iter() {
9717 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9718 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9719 if selection_start.buffer_id != selection_end.buffer_id {
9720 continue;
9721 }
9722 if let Some(ranges) =
9723 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9724 {
9725 for (buffer, entries) in ranges {
9726 linked_ranges.entry(buffer).or_default().extend(entries);
9727 }
9728 }
9729 }
9730 }
9731
9732 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9733 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9734 for selection in &mut selections {
9735 if selection.is_empty() {
9736 let old_head = selection.head();
9737 let mut new_head =
9738 movement::left(&display_map, old_head.to_display_point(&display_map))
9739 .to_point(&display_map);
9740 if let Some((buffer, line_buffer_range)) = display_map
9741 .buffer_snapshot
9742 .buffer_line_for_row(MultiBufferRow(old_head.row))
9743 {
9744 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9745 let indent_len = match indent_size.kind {
9746 IndentKind::Space => {
9747 buffer.settings_at(line_buffer_range.start, cx).tab_size
9748 }
9749 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9750 };
9751 if old_head.column <= indent_size.len && old_head.column > 0 {
9752 let indent_len = indent_len.get();
9753 new_head = cmp::min(
9754 new_head,
9755 MultiBufferPoint::new(
9756 old_head.row,
9757 ((old_head.column - 1) / indent_len) * indent_len,
9758 ),
9759 );
9760 }
9761 }
9762
9763 selection.set_head(new_head, SelectionGoal::None);
9764 }
9765 }
9766
9767 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9768 this.insert("", window, cx);
9769 let empty_str: Arc<str> = Arc::from("");
9770 for (buffer, edits) in linked_ranges {
9771 let snapshot = buffer.read(cx).snapshot();
9772 use text::ToPoint as TP;
9773
9774 let edits = edits
9775 .into_iter()
9776 .map(|range| {
9777 let end_point = TP::to_point(&range.end, &snapshot);
9778 let mut start_point = TP::to_point(&range.start, &snapshot);
9779
9780 if end_point == start_point {
9781 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9782 .saturating_sub(1);
9783 start_point =
9784 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9785 };
9786
9787 (start_point..end_point, empty_str.clone())
9788 })
9789 .sorted_by_key(|(range, _)| range.start)
9790 .collect::<Vec<_>>();
9791 buffer.update(cx, |this, cx| {
9792 this.edit(edits, None, cx);
9793 })
9794 }
9795 this.refresh_inline_completion(true, false, window, cx);
9796 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9797 });
9798 }
9799
9800 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9802 self.transact(window, cx, |this, window, cx| {
9803 this.change_selections(Default::default(), window, cx, |s| {
9804 s.move_with(|map, selection| {
9805 if selection.is_empty() {
9806 let cursor = movement::right(map, selection.head());
9807 selection.end = cursor;
9808 selection.reversed = true;
9809 selection.goal = SelectionGoal::None;
9810 }
9811 })
9812 });
9813 this.insert("", window, cx);
9814 this.refresh_inline_completion(true, false, window, cx);
9815 });
9816 }
9817
9818 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9819 if self.mode.is_single_line() {
9820 cx.propagate();
9821 return;
9822 }
9823
9824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9825 if self.move_to_prev_snippet_tabstop(window, cx) {
9826 return;
9827 }
9828 self.outdent(&Outdent, window, cx);
9829 }
9830
9831 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9832 if self.mode.is_single_line() {
9833 cx.propagate();
9834 return;
9835 }
9836
9837 if self.move_to_next_snippet_tabstop(window, cx) {
9838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9839 return;
9840 }
9841 if self.read_only(cx) {
9842 return;
9843 }
9844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9845 let mut selections = self.selections.all_adjusted(cx);
9846 let buffer = self.buffer.read(cx);
9847 let snapshot = buffer.snapshot(cx);
9848 let rows_iter = selections.iter().map(|s| s.head().row);
9849 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9850
9851 let has_some_cursor_in_whitespace = selections
9852 .iter()
9853 .filter(|selection| selection.is_empty())
9854 .any(|selection| {
9855 let cursor = selection.head();
9856 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9857 cursor.column < current_indent.len
9858 });
9859
9860 let mut edits = Vec::new();
9861 let mut prev_edited_row = 0;
9862 let mut row_delta = 0;
9863 for selection in &mut selections {
9864 if selection.start.row != prev_edited_row {
9865 row_delta = 0;
9866 }
9867 prev_edited_row = selection.end.row;
9868
9869 // If the selection is non-empty, then increase the indentation of the selected lines.
9870 if !selection.is_empty() {
9871 row_delta =
9872 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9873 continue;
9874 }
9875
9876 let cursor = selection.head();
9877 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9878 if let Some(suggested_indent) =
9879 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9880 {
9881 // Don't do anything if already at suggested indent
9882 // and there is any other cursor which is not
9883 if has_some_cursor_in_whitespace
9884 && cursor.column == current_indent.len
9885 && current_indent.len == suggested_indent.len
9886 {
9887 continue;
9888 }
9889
9890 // Adjust line and move cursor to suggested indent
9891 // if cursor is not at suggested indent
9892 if cursor.column < suggested_indent.len
9893 && cursor.column <= current_indent.len
9894 && current_indent.len <= suggested_indent.len
9895 {
9896 selection.start = Point::new(cursor.row, suggested_indent.len);
9897 selection.end = selection.start;
9898 if row_delta == 0 {
9899 edits.extend(Buffer::edit_for_indent_size_adjustment(
9900 cursor.row,
9901 current_indent,
9902 suggested_indent,
9903 ));
9904 row_delta = suggested_indent.len - current_indent.len;
9905 }
9906 continue;
9907 }
9908
9909 // If current indent is more than suggested indent
9910 // only move cursor to current indent and skip indent
9911 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9912 selection.start = Point::new(cursor.row, current_indent.len);
9913 selection.end = selection.start;
9914 continue;
9915 }
9916 }
9917
9918 // Otherwise, insert a hard or soft tab.
9919 let settings = buffer.language_settings_at(cursor, cx);
9920 let tab_size = if settings.hard_tabs {
9921 IndentSize::tab()
9922 } else {
9923 let tab_size = settings.tab_size.get();
9924 let indent_remainder = snapshot
9925 .text_for_range(Point::new(cursor.row, 0)..cursor)
9926 .flat_map(str::chars)
9927 .fold(row_delta % tab_size, |counter: u32, c| {
9928 if c == '\t' {
9929 0
9930 } else {
9931 (counter + 1) % tab_size
9932 }
9933 });
9934
9935 let chars_to_next_tab_stop = tab_size - indent_remainder;
9936 IndentSize::spaces(chars_to_next_tab_stop)
9937 };
9938 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9939 selection.end = selection.start;
9940 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9941 row_delta += tab_size.len;
9942 }
9943
9944 self.transact(window, cx, |this, window, cx| {
9945 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9946 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9947 this.refresh_inline_completion(true, false, window, cx);
9948 });
9949 }
9950
9951 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9952 if self.read_only(cx) {
9953 return;
9954 }
9955 if self.mode.is_single_line() {
9956 cx.propagate();
9957 return;
9958 }
9959
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 let mut selections = self.selections.all::<Point>(cx);
9962 let mut prev_edited_row = 0;
9963 let mut row_delta = 0;
9964 let mut edits = Vec::new();
9965 let buffer = self.buffer.read(cx);
9966 let snapshot = buffer.snapshot(cx);
9967 for selection in &mut selections {
9968 if selection.start.row != prev_edited_row {
9969 row_delta = 0;
9970 }
9971 prev_edited_row = selection.end.row;
9972
9973 row_delta =
9974 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9975 }
9976
9977 self.transact(window, cx, |this, window, cx| {
9978 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9979 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9980 });
9981 }
9982
9983 fn indent_selection(
9984 buffer: &MultiBuffer,
9985 snapshot: &MultiBufferSnapshot,
9986 selection: &mut Selection<Point>,
9987 edits: &mut Vec<(Range<Point>, String)>,
9988 delta_for_start_row: u32,
9989 cx: &App,
9990 ) -> u32 {
9991 let settings = buffer.language_settings_at(selection.start, cx);
9992 let tab_size = settings.tab_size.get();
9993 let indent_kind = if settings.hard_tabs {
9994 IndentKind::Tab
9995 } else {
9996 IndentKind::Space
9997 };
9998 let mut start_row = selection.start.row;
9999 let mut end_row = selection.end.row + 1;
10000
10001 // If a selection ends at the beginning of a line, don't indent
10002 // that last line.
10003 if selection.end.column == 0 && selection.end.row > selection.start.row {
10004 end_row -= 1;
10005 }
10006
10007 // Avoid re-indenting a row that has already been indented by a
10008 // previous selection, but still update this selection's column
10009 // to reflect that indentation.
10010 if delta_for_start_row > 0 {
10011 start_row += 1;
10012 selection.start.column += delta_for_start_row;
10013 if selection.end.row == selection.start.row {
10014 selection.end.column += delta_for_start_row;
10015 }
10016 }
10017
10018 let mut delta_for_end_row = 0;
10019 let has_multiple_rows = start_row + 1 != end_row;
10020 for row in start_row..end_row {
10021 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10022 let indent_delta = match (current_indent.kind, indent_kind) {
10023 (IndentKind::Space, IndentKind::Space) => {
10024 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10025 IndentSize::spaces(columns_to_next_tab_stop)
10026 }
10027 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10028 (_, IndentKind::Tab) => IndentSize::tab(),
10029 };
10030
10031 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10032 0
10033 } else {
10034 selection.start.column
10035 };
10036 let row_start = Point::new(row, start);
10037 edits.push((
10038 row_start..row_start,
10039 indent_delta.chars().collect::<String>(),
10040 ));
10041
10042 // Update this selection's endpoints to reflect the indentation.
10043 if row == selection.start.row {
10044 selection.start.column += indent_delta.len;
10045 }
10046 if row == selection.end.row {
10047 selection.end.column += indent_delta.len;
10048 delta_for_end_row = indent_delta.len;
10049 }
10050 }
10051
10052 if selection.start.row == selection.end.row {
10053 delta_for_start_row + delta_for_end_row
10054 } else {
10055 delta_for_end_row
10056 }
10057 }
10058
10059 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10060 if self.read_only(cx) {
10061 return;
10062 }
10063 if self.mode.is_single_line() {
10064 cx.propagate();
10065 return;
10066 }
10067
10068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10070 let selections = self.selections.all::<Point>(cx);
10071 let mut deletion_ranges = Vec::new();
10072 let mut last_outdent = None;
10073 {
10074 let buffer = self.buffer.read(cx);
10075 let snapshot = buffer.snapshot(cx);
10076 for selection in &selections {
10077 let settings = buffer.language_settings_at(selection.start, cx);
10078 let tab_size = settings.tab_size.get();
10079 let mut rows = selection.spanned_rows(false, &display_map);
10080
10081 // Avoid re-outdenting a row that has already been outdented by a
10082 // previous selection.
10083 if let Some(last_row) = last_outdent {
10084 if last_row == rows.start {
10085 rows.start = rows.start.next_row();
10086 }
10087 }
10088 let has_multiple_rows = rows.len() > 1;
10089 for row in rows.iter_rows() {
10090 let indent_size = snapshot.indent_size_for_line(row);
10091 if indent_size.len > 0 {
10092 let deletion_len = match indent_size.kind {
10093 IndentKind::Space => {
10094 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10095 if columns_to_prev_tab_stop == 0 {
10096 tab_size
10097 } else {
10098 columns_to_prev_tab_stop
10099 }
10100 }
10101 IndentKind::Tab => 1,
10102 };
10103 let start = if has_multiple_rows
10104 || deletion_len > selection.start.column
10105 || indent_size.len < selection.start.column
10106 {
10107 0
10108 } else {
10109 selection.start.column - deletion_len
10110 };
10111 deletion_ranges.push(
10112 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10113 );
10114 last_outdent = Some(row);
10115 }
10116 }
10117 }
10118 }
10119
10120 self.transact(window, cx, |this, window, cx| {
10121 this.buffer.update(cx, |buffer, cx| {
10122 let empty_str: Arc<str> = Arc::default();
10123 buffer.edit(
10124 deletion_ranges
10125 .into_iter()
10126 .map(|range| (range, empty_str.clone())),
10127 None,
10128 cx,
10129 );
10130 });
10131 let selections = this.selections.all::<usize>(cx);
10132 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10133 });
10134 }
10135
10136 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10137 if self.read_only(cx) {
10138 return;
10139 }
10140 if self.mode.is_single_line() {
10141 cx.propagate();
10142 return;
10143 }
10144
10145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10146 let selections = self
10147 .selections
10148 .all::<usize>(cx)
10149 .into_iter()
10150 .map(|s| s.range());
10151
10152 self.transact(window, cx, |this, window, cx| {
10153 this.buffer.update(cx, |buffer, cx| {
10154 buffer.autoindent_ranges(selections, cx);
10155 });
10156 let selections = this.selections.all::<usize>(cx);
10157 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10158 });
10159 }
10160
10161 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10164 let selections = self.selections.all::<Point>(cx);
10165
10166 let mut new_cursors = Vec::new();
10167 let mut edit_ranges = Vec::new();
10168 let mut selections = selections.iter().peekable();
10169 while let Some(selection) = selections.next() {
10170 let mut rows = selection.spanned_rows(false, &display_map);
10171 let goal_display_column = selection.head().to_display_point(&display_map).column();
10172
10173 // Accumulate contiguous regions of rows that we want to delete.
10174 while let Some(next_selection) = selections.peek() {
10175 let next_rows = next_selection.spanned_rows(false, &display_map);
10176 if next_rows.start <= rows.end {
10177 rows.end = next_rows.end;
10178 selections.next().unwrap();
10179 } else {
10180 break;
10181 }
10182 }
10183
10184 let buffer = &display_map.buffer_snapshot;
10185 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10186 let edit_end;
10187 let cursor_buffer_row;
10188 if buffer.max_point().row >= rows.end.0 {
10189 // If there's a line after the range, delete the \n from the end of the row range
10190 // and position the cursor on the next line.
10191 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10192 cursor_buffer_row = rows.end;
10193 } else {
10194 // If there isn't a line after the range, delete the \n from the line before the
10195 // start of the row range and position the cursor there.
10196 edit_start = edit_start.saturating_sub(1);
10197 edit_end = buffer.len();
10198 cursor_buffer_row = rows.start.previous_row();
10199 }
10200
10201 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10202 *cursor.column_mut() =
10203 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10204
10205 new_cursors.push((
10206 selection.id,
10207 buffer.anchor_after(cursor.to_point(&display_map)),
10208 ));
10209 edit_ranges.push(edit_start..edit_end);
10210 }
10211
10212 self.transact(window, cx, |this, window, cx| {
10213 let buffer = this.buffer.update(cx, |buffer, cx| {
10214 let empty_str: Arc<str> = Arc::default();
10215 buffer.edit(
10216 edit_ranges
10217 .into_iter()
10218 .map(|range| (range, empty_str.clone())),
10219 None,
10220 cx,
10221 );
10222 buffer.snapshot(cx)
10223 });
10224 let new_selections = new_cursors
10225 .into_iter()
10226 .map(|(id, cursor)| {
10227 let cursor = cursor.to_point(&buffer);
10228 Selection {
10229 id,
10230 start: cursor,
10231 end: cursor,
10232 reversed: false,
10233 goal: SelectionGoal::None,
10234 }
10235 })
10236 .collect();
10237
10238 this.change_selections(Default::default(), window, cx, |s| {
10239 s.select(new_selections);
10240 });
10241 });
10242 }
10243
10244 pub fn join_lines_impl(
10245 &mut self,
10246 insert_whitespace: bool,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 if self.read_only(cx) {
10251 return;
10252 }
10253 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10254 for selection in self.selections.all::<Point>(cx) {
10255 let start = MultiBufferRow(selection.start.row);
10256 // Treat single line selections as if they include the next line. Otherwise this action
10257 // would do nothing for single line selections individual cursors.
10258 let end = if selection.start.row == selection.end.row {
10259 MultiBufferRow(selection.start.row + 1)
10260 } else {
10261 MultiBufferRow(selection.end.row)
10262 };
10263
10264 if let Some(last_row_range) = row_ranges.last_mut() {
10265 if start <= last_row_range.end {
10266 last_row_range.end = end;
10267 continue;
10268 }
10269 }
10270 row_ranges.push(start..end);
10271 }
10272
10273 let snapshot = self.buffer.read(cx).snapshot(cx);
10274 let mut cursor_positions = Vec::new();
10275 for row_range in &row_ranges {
10276 let anchor = snapshot.anchor_before(Point::new(
10277 row_range.end.previous_row().0,
10278 snapshot.line_len(row_range.end.previous_row()),
10279 ));
10280 cursor_positions.push(anchor..anchor);
10281 }
10282
10283 self.transact(window, cx, |this, window, cx| {
10284 for row_range in row_ranges.into_iter().rev() {
10285 for row in row_range.iter_rows().rev() {
10286 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10287 let next_line_row = row.next_row();
10288 let indent = snapshot.indent_size_for_line(next_line_row);
10289 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10290
10291 let replace =
10292 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10293 " "
10294 } else {
10295 ""
10296 };
10297
10298 this.buffer.update(cx, |buffer, cx| {
10299 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10300 });
10301 }
10302 }
10303
10304 this.change_selections(Default::default(), window, cx, |s| {
10305 s.select_anchor_ranges(cursor_positions)
10306 });
10307 });
10308 }
10309
10310 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10312 self.join_lines_impl(true, window, cx);
10313 }
10314
10315 pub fn sort_lines_case_sensitive(
10316 &mut self,
10317 _: &SortLinesCaseSensitive,
10318 window: &mut Window,
10319 cx: &mut Context<Self>,
10320 ) {
10321 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10322 }
10323
10324 pub fn sort_lines_by_length(
10325 &mut self,
10326 _: &SortLinesByLength,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 self.manipulate_immutable_lines(window, cx, |lines| {
10331 lines.sort_by_key(|&line| line.chars().count())
10332 })
10333 }
10334
10335 pub fn sort_lines_case_insensitive(
10336 &mut self,
10337 _: &SortLinesCaseInsensitive,
10338 window: &mut Window,
10339 cx: &mut Context<Self>,
10340 ) {
10341 self.manipulate_immutable_lines(window, cx, |lines| {
10342 lines.sort_by_key(|line| line.to_lowercase())
10343 })
10344 }
10345
10346 pub fn unique_lines_case_insensitive(
10347 &mut self,
10348 _: &UniqueLinesCaseInsensitive,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 self.manipulate_immutable_lines(window, cx, |lines| {
10353 let mut seen = HashSet::default();
10354 lines.retain(|line| seen.insert(line.to_lowercase()));
10355 })
10356 }
10357
10358 pub fn unique_lines_case_sensitive(
10359 &mut self,
10360 _: &UniqueLinesCaseSensitive,
10361 window: &mut Window,
10362 cx: &mut Context<Self>,
10363 ) {
10364 self.manipulate_immutable_lines(window, cx, |lines| {
10365 let mut seen = HashSet::default();
10366 lines.retain(|line| seen.insert(*line));
10367 })
10368 }
10369
10370 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10371 let Some(project) = self.project.clone() else {
10372 return;
10373 };
10374 self.reload(project, window, cx)
10375 .detach_and_notify_err(window, cx);
10376 }
10377
10378 pub fn restore_file(
10379 &mut self,
10380 _: &::git::RestoreFile,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10385 let mut buffer_ids = HashSet::default();
10386 let snapshot = self.buffer().read(cx).snapshot(cx);
10387 for selection in self.selections.all::<usize>(cx) {
10388 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10389 }
10390
10391 let buffer = self.buffer().read(cx);
10392 let ranges = buffer_ids
10393 .into_iter()
10394 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10395 .collect::<Vec<_>>();
10396
10397 self.restore_hunks_in_ranges(ranges, window, cx);
10398 }
10399
10400 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10402 let selections = self
10403 .selections
10404 .all(cx)
10405 .into_iter()
10406 .map(|s| s.range())
10407 .collect();
10408 self.restore_hunks_in_ranges(selections, window, cx);
10409 }
10410
10411 pub fn restore_hunks_in_ranges(
10412 &mut self,
10413 ranges: Vec<Range<Point>>,
10414 window: &mut Window,
10415 cx: &mut Context<Editor>,
10416 ) {
10417 let mut revert_changes = HashMap::default();
10418 let chunk_by = self
10419 .snapshot(window, cx)
10420 .hunks_for_ranges(ranges)
10421 .into_iter()
10422 .chunk_by(|hunk| hunk.buffer_id);
10423 for (buffer_id, hunks) in &chunk_by {
10424 let hunks = hunks.collect::<Vec<_>>();
10425 for hunk in &hunks {
10426 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10427 }
10428 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10429 }
10430 drop(chunk_by);
10431 if !revert_changes.is_empty() {
10432 self.transact(window, cx, |editor, window, cx| {
10433 editor.restore(revert_changes, window, cx);
10434 });
10435 }
10436 }
10437
10438 pub fn open_active_item_in_terminal(
10439 &mut self,
10440 _: &OpenInTerminal,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10445 let project_path = buffer.read(cx).project_path(cx)?;
10446 let project = self.project.as_ref()?.read(cx);
10447 let entry = project.entry_for_path(&project_path, cx)?;
10448 let parent = match &entry.canonical_path {
10449 Some(canonical_path) => canonical_path.to_path_buf(),
10450 None => project.absolute_path(&project_path, cx)?,
10451 }
10452 .parent()?
10453 .to_path_buf();
10454 Some(parent)
10455 }) {
10456 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10457 }
10458 }
10459
10460 fn set_breakpoint_context_menu(
10461 &mut self,
10462 display_row: DisplayRow,
10463 position: Option<Anchor>,
10464 clicked_point: gpui::Point<Pixels>,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 let source = self
10469 .buffer
10470 .read(cx)
10471 .snapshot(cx)
10472 .anchor_before(Point::new(display_row.0, 0u32));
10473
10474 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10475
10476 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10477 self,
10478 source,
10479 clicked_point,
10480 context_menu,
10481 window,
10482 cx,
10483 );
10484 }
10485
10486 fn add_edit_breakpoint_block(
10487 &mut self,
10488 anchor: Anchor,
10489 breakpoint: &Breakpoint,
10490 edit_action: BreakpointPromptEditAction,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 let weak_editor = cx.weak_entity();
10495 let bp_prompt = cx.new(|cx| {
10496 BreakpointPromptEditor::new(
10497 weak_editor,
10498 anchor,
10499 breakpoint.clone(),
10500 edit_action,
10501 window,
10502 cx,
10503 )
10504 });
10505
10506 let height = bp_prompt.update(cx, |this, cx| {
10507 this.prompt
10508 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10509 });
10510 let cloned_prompt = bp_prompt.clone();
10511 let blocks = vec![BlockProperties {
10512 style: BlockStyle::Sticky,
10513 placement: BlockPlacement::Above(anchor),
10514 height: Some(height),
10515 render: Arc::new(move |cx| {
10516 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10517 cloned_prompt.clone().into_any_element()
10518 }),
10519 priority: 0,
10520 }];
10521
10522 let focus_handle = bp_prompt.focus_handle(cx);
10523 window.focus(&focus_handle);
10524
10525 let block_ids = self.insert_blocks(blocks, None, cx);
10526 bp_prompt.update(cx, |prompt, _| {
10527 prompt.add_block_ids(block_ids);
10528 });
10529 }
10530
10531 pub(crate) fn breakpoint_at_row(
10532 &self,
10533 row: u32,
10534 window: &mut Window,
10535 cx: &mut Context<Self>,
10536 ) -> Option<(Anchor, Breakpoint)> {
10537 let snapshot = self.snapshot(window, cx);
10538 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10539
10540 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10541 }
10542
10543 pub(crate) fn breakpoint_at_anchor(
10544 &self,
10545 breakpoint_position: Anchor,
10546 snapshot: &EditorSnapshot,
10547 cx: &mut Context<Self>,
10548 ) -> Option<(Anchor, Breakpoint)> {
10549 let project = self.project.clone()?;
10550
10551 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10552 snapshot
10553 .buffer_snapshot
10554 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10555 })?;
10556
10557 let enclosing_excerpt = breakpoint_position.excerpt_id;
10558 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10559 let buffer_snapshot = buffer.read(cx).snapshot();
10560
10561 let row = buffer_snapshot
10562 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10563 .row;
10564
10565 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10566 let anchor_end = snapshot
10567 .buffer_snapshot
10568 .anchor_after(Point::new(row, line_len));
10569
10570 let bp = self
10571 .breakpoint_store
10572 .as_ref()?
10573 .read_with(cx, |breakpoint_store, cx| {
10574 breakpoint_store
10575 .breakpoints(
10576 &buffer,
10577 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10578 &buffer_snapshot,
10579 cx,
10580 )
10581 .next()
10582 .and_then(|(bp, _)| {
10583 let breakpoint_row = buffer_snapshot
10584 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10585 .row;
10586
10587 if breakpoint_row == row {
10588 snapshot
10589 .buffer_snapshot
10590 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10591 .map(|position| (position, bp.bp.clone()))
10592 } else {
10593 None
10594 }
10595 })
10596 });
10597 bp
10598 }
10599
10600 pub fn edit_log_breakpoint(
10601 &mut self,
10602 _: &EditLogBreakpoint,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10607 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10608 message: None,
10609 state: BreakpointState::Enabled,
10610 condition: None,
10611 hit_condition: None,
10612 });
10613
10614 self.add_edit_breakpoint_block(
10615 anchor,
10616 &breakpoint,
10617 BreakpointPromptEditAction::Log,
10618 window,
10619 cx,
10620 );
10621 }
10622 }
10623
10624 fn breakpoints_at_cursors(
10625 &self,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10629 let snapshot = self.snapshot(window, cx);
10630 let cursors = self
10631 .selections
10632 .disjoint_anchors()
10633 .into_iter()
10634 .map(|selection| {
10635 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10636
10637 let breakpoint_position = self
10638 .breakpoint_at_row(cursor_position.row, window, cx)
10639 .map(|bp| bp.0)
10640 .unwrap_or_else(|| {
10641 snapshot
10642 .display_snapshot
10643 .buffer_snapshot
10644 .anchor_after(Point::new(cursor_position.row, 0))
10645 });
10646
10647 let breakpoint = self
10648 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10649 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10650
10651 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10652 })
10653 // 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.
10654 .collect::<HashMap<Anchor, _>>();
10655
10656 cursors.into_iter().collect()
10657 }
10658
10659 pub fn enable_breakpoint(
10660 &mut self,
10661 _: &crate::actions::EnableBreakpoint,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10666 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10667 continue;
10668 };
10669 self.edit_breakpoint_at_anchor(
10670 anchor,
10671 breakpoint,
10672 BreakpointEditAction::InvertState,
10673 cx,
10674 );
10675 }
10676 }
10677
10678 pub fn disable_breakpoint(
10679 &mut self,
10680 _: &crate::actions::DisableBreakpoint,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10685 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10686 continue;
10687 };
10688 self.edit_breakpoint_at_anchor(
10689 anchor,
10690 breakpoint,
10691 BreakpointEditAction::InvertState,
10692 cx,
10693 );
10694 }
10695 }
10696
10697 pub fn toggle_breakpoint(
10698 &mut self,
10699 _: &crate::actions::ToggleBreakpoint,
10700 window: &mut Window,
10701 cx: &mut Context<Self>,
10702 ) {
10703 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10704 if let Some(breakpoint) = breakpoint {
10705 self.edit_breakpoint_at_anchor(
10706 anchor,
10707 breakpoint,
10708 BreakpointEditAction::Toggle,
10709 cx,
10710 );
10711 } else {
10712 self.edit_breakpoint_at_anchor(
10713 anchor,
10714 Breakpoint::new_standard(),
10715 BreakpointEditAction::Toggle,
10716 cx,
10717 );
10718 }
10719 }
10720 }
10721
10722 pub fn edit_breakpoint_at_anchor(
10723 &mut self,
10724 breakpoint_position: Anchor,
10725 breakpoint: Breakpoint,
10726 edit_action: BreakpointEditAction,
10727 cx: &mut Context<Self>,
10728 ) {
10729 let Some(breakpoint_store) = &self.breakpoint_store else {
10730 return;
10731 };
10732
10733 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10734 if breakpoint_position == Anchor::min() {
10735 self.buffer()
10736 .read(cx)
10737 .excerpt_buffer_ids()
10738 .into_iter()
10739 .next()
10740 } else {
10741 None
10742 }
10743 }) else {
10744 return;
10745 };
10746
10747 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10748 return;
10749 };
10750
10751 breakpoint_store.update(cx, |breakpoint_store, cx| {
10752 breakpoint_store.toggle_breakpoint(
10753 buffer,
10754 BreakpointWithPosition {
10755 position: breakpoint_position.text_anchor,
10756 bp: breakpoint,
10757 },
10758 edit_action,
10759 cx,
10760 );
10761 });
10762
10763 cx.notify();
10764 }
10765
10766 #[cfg(any(test, feature = "test-support"))]
10767 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10768 self.breakpoint_store.clone()
10769 }
10770
10771 pub fn prepare_restore_change(
10772 &self,
10773 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10774 hunk: &MultiBufferDiffHunk,
10775 cx: &mut App,
10776 ) -> Option<()> {
10777 if hunk.is_created_file() {
10778 return None;
10779 }
10780 let buffer = self.buffer.read(cx);
10781 let diff = buffer.diff_for(hunk.buffer_id)?;
10782 let buffer = buffer.buffer(hunk.buffer_id)?;
10783 let buffer = buffer.read(cx);
10784 let original_text = diff
10785 .read(cx)
10786 .base_text()
10787 .as_rope()
10788 .slice(hunk.diff_base_byte_range.clone());
10789 let buffer_snapshot = buffer.snapshot();
10790 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10791 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10792 probe
10793 .0
10794 .start
10795 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10796 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10797 }) {
10798 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10799 Some(())
10800 } else {
10801 None
10802 }
10803 }
10804
10805 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10806 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10807 }
10808
10809 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10810 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10811 }
10812
10813 fn manipulate_lines<M>(
10814 &mut self,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 mut manipulate: M,
10818 ) where
10819 M: FnMut(&str) -> LineManipulationResult,
10820 {
10821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10822
10823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10824 let buffer = self.buffer.read(cx).snapshot(cx);
10825
10826 let mut edits = Vec::new();
10827
10828 let selections = self.selections.all::<Point>(cx);
10829 let mut selections = selections.iter().peekable();
10830 let mut contiguous_row_selections = Vec::new();
10831 let mut new_selections = Vec::new();
10832 let mut added_lines = 0;
10833 let mut removed_lines = 0;
10834
10835 while let Some(selection) = selections.next() {
10836 let (start_row, end_row) = consume_contiguous_rows(
10837 &mut contiguous_row_selections,
10838 selection,
10839 &display_map,
10840 &mut selections,
10841 );
10842
10843 let start_point = Point::new(start_row.0, 0);
10844 let end_point = Point::new(
10845 end_row.previous_row().0,
10846 buffer.line_len(end_row.previous_row()),
10847 );
10848 let text = buffer
10849 .text_for_range(start_point..end_point)
10850 .collect::<String>();
10851
10852 let LineManipulationResult {
10853 new_text,
10854 line_count_before,
10855 line_count_after,
10856 } = manipulate(&text);
10857
10858 edits.push((start_point..end_point, new_text));
10859
10860 // Selections must change based on added and removed line count
10861 let start_row =
10862 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10863 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10864 new_selections.push(Selection {
10865 id: selection.id,
10866 start: start_row,
10867 end: end_row,
10868 goal: SelectionGoal::None,
10869 reversed: selection.reversed,
10870 });
10871
10872 if line_count_after > line_count_before {
10873 added_lines += line_count_after - line_count_before;
10874 } else if line_count_before > line_count_after {
10875 removed_lines += line_count_before - line_count_after;
10876 }
10877 }
10878
10879 self.transact(window, cx, |this, window, cx| {
10880 let buffer = this.buffer.update(cx, |buffer, cx| {
10881 buffer.edit(edits, None, cx);
10882 buffer.snapshot(cx)
10883 });
10884
10885 // Recalculate offsets on newly edited buffer
10886 let new_selections = new_selections
10887 .iter()
10888 .map(|s| {
10889 let start_point = Point::new(s.start.0, 0);
10890 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10891 Selection {
10892 id: s.id,
10893 start: buffer.point_to_offset(start_point),
10894 end: buffer.point_to_offset(end_point),
10895 goal: s.goal,
10896 reversed: s.reversed,
10897 }
10898 })
10899 .collect();
10900
10901 this.change_selections(Default::default(), window, cx, |s| {
10902 s.select(new_selections);
10903 });
10904
10905 this.request_autoscroll(Autoscroll::fit(), cx);
10906 });
10907 }
10908
10909 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10910 self.manipulate_text(window, cx, |text| {
10911 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10912 if has_upper_case_characters {
10913 text.to_lowercase()
10914 } else {
10915 text.to_uppercase()
10916 }
10917 })
10918 }
10919
10920 fn manipulate_immutable_lines<Fn>(
10921 &mut self,
10922 window: &mut Window,
10923 cx: &mut Context<Self>,
10924 mut callback: Fn,
10925 ) where
10926 Fn: FnMut(&mut Vec<&str>),
10927 {
10928 self.manipulate_lines(window, cx, |text| {
10929 let mut lines: Vec<&str> = text.split('\n').collect();
10930 let line_count_before = lines.len();
10931
10932 callback(&mut lines);
10933
10934 LineManipulationResult {
10935 new_text: lines.join("\n"),
10936 line_count_before,
10937 line_count_after: lines.len(),
10938 }
10939 });
10940 }
10941
10942 fn manipulate_mutable_lines<Fn>(
10943 &mut self,
10944 window: &mut Window,
10945 cx: &mut Context<Self>,
10946 mut callback: Fn,
10947 ) where
10948 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10949 {
10950 self.manipulate_lines(window, cx, |text| {
10951 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10952 let line_count_before = lines.len();
10953
10954 callback(&mut lines);
10955
10956 LineManipulationResult {
10957 new_text: lines.join("\n"),
10958 line_count_before,
10959 line_count_after: lines.len(),
10960 }
10961 });
10962 }
10963
10964 pub fn convert_indentation_to_spaces(
10965 &mut self,
10966 _: &ConvertIndentationToSpaces,
10967 window: &mut Window,
10968 cx: &mut Context<Self>,
10969 ) {
10970 let settings = self.buffer.read(cx).language_settings(cx);
10971 let tab_size = settings.tab_size.get() as usize;
10972
10973 self.manipulate_mutable_lines(window, cx, |lines| {
10974 // Allocates a reasonably sized scratch buffer once for the whole loop
10975 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10976 // Avoids recomputing spaces that could be inserted many times
10977 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10978 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10979 .collect();
10980
10981 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10982 let mut chars = line.as_ref().chars();
10983 let mut col = 0;
10984 let mut changed = false;
10985
10986 while let Some(ch) = chars.next() {
10987 match ch {
10988 ' ' => {
10989 reindented_line.push(' ');
10990 col += 1;
10991 }
10992 '\t' => {
10993 // \t are converted to spaces depending on the current column
10994 let spaces_len = tab_size - (col % tab_size);
10995 reindented_line.extend(&space_cache[spaces_len - 1]);
10996 col += spaces_len;
10997 changed = true;
10998 }
10999 _ => {
11000 // If we dont append before break, the character is consumed
11001 reindented_line.push(ch);
11002 break;
11003 }
11004 }
11005 }
11006
11007 if !changed {
11008 reindented_line.clear();
11009 continue;
11010 }
11011 // Append the rest of the line and replace old reference with new one
11012 reindented_line.extend(chars);
11013 *line = Cow::Owned(reindented_line.clone());
11014 reindented_line.clear();
11015 }
11016 });
11017 }
11018
11019 pub fn convert_indentation_to_tabs(
11020 &mut self,
11021 _: &ConvertIndentationToTabs,
11022 window: &mut Window,
11023 cx: &mut Context<Self>,
11024 ) {
11025 let settings = self.buffer.read(cx).language_settings(cx);
11026 let tab_size = settings.tab_size.get() as usize;
11027
11028 self.manipulate_mutable_lines(window, cx, |lines| {
11029 // Allocates a reasonably sized buffer once for the whole loop
11030 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11031 // Avoids recomputing spaces that could be inserted many times
11032 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11033 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11034 .collect();
11035
11036 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11037 let mut chars = line.chars();
11038 let mut spaces_count = 0;
11039 let mut first_non_indent_char = None;
11040 let mut changed = false;
11041
11042 while let Some(ch) = chars.next() {
11043 match ch {
11044 ' ' => {
11045 // Keep track of spaces. Append \t when we reach tab_size
11046 spaces_count += 1;
11047 changed = true;
11048 if spaces_count == tab_size {
11049 reindented_line.push('\t');
11050 spaces_count = 0;
11051 }
11052 }
11053 '\t' => {
11054 reindented_line.push('\t');
11055 spaces_count = 0;
11056 }
11057 _ => {
11058 // Dont append it yet, we might have remaining spaces
11059 first_non_indent_char = Some(ch);
11060 break;
11061 }
11062 }
11063 }
11064
11065 if !changed {
11066 reindented_line.clear();
11067 continue;
11068 }
11069 // Remaining spaces that didn't make a full tab stop
11070 if spaces_count > 0 {
11071 reindented_line.extend(&space_cache[spaces_count - 1]);
11072 }
11073 // If we consume an extra character that was not indentation, add it back
11074 if let Some(extra_char) = first_non_indent_char {
11075 reindented_line.push(extra_char);
11076 }
11077 // Append the rest of the line and replace old reference with new one
11078 reindented_line.extend(chars);
11079 *line = Cow::Owned(reindented_line.clone());
11080 reindented_line.clear();
11081 }
11082 });
11083 }
11084
11085 pub fn convert_to_upper_case(
11086 &mut self,
11087 _: &ConvertToUpperCase,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.manipulate_text(window, cx, |text| text.to_uppercase())
11092 }
11093
11094 pub fn convert_to_lower_case(
11095 &mut self,
11096 _: &ConvertToLowerCase,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 self.manipulate_text(window, cx, |text| text.to_lowercase())
11101 }
11102
11103 pub fn convert_to_title_case(
11104 &mut self,
11105 _: &ConvertToTitleCase,
11106 window: &mut Window,
11107 cx: &mut Context<Self>,
11108 ) {
11109 self.manipulate_text(window, cx, |text| {
11110 text.split('\n')
11111 .map(|line| line.to_case(Case::Title))
11112 .join("\n")
11113 })
11114 }
11115
11116 pub fn convert_to_snake_case(
11117 &mut self,
11118 _: &ConvertToSnakeCase,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11123 }
11124
11125 pub fn convert_to_kebab_case(
11126 &mut self,
11127 _: &ConvertToKebabCase,
11128 window: &mut Window,
11129 cx: &mut Context<Self>,
11130 ) {
11131 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11132 }
11133
11134 pub fn convert_to_upper_camel_case(
11135 &mut self,
11136 _: &ConvertToUpperCamelCase,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 self.manipulate_text(window, cx, |text| {
11141 text.split('\n')
11142 .map(|line| line.to_case(Case::UpperCamel))
11143 .join("\n")
11144 })
11145 }
11146
11147 pub fn convert_to_lower_camel_case(
11148 &mut self,
11149 _: &ConvertToLowerCamelCase,
11150 window: &mut Window,
11151 cx: &mut Context<Self>,
11152 ) {
11153 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11154 }
11155
11156 pub fn convert_to_opposite_case(
11157 &mut self,
11158 _: &ConvertToOppositeCase,
11159 window: &mut Window,
11160 cx: &mut Context<Self>,
11161 ) {
11162 self.manipulate_text(window, cx, |text| {
11163 text.chars()
11164 .fold(String::with_capacity(text.len()), |mut t, c| {
11165 if c.is_uppercase() {
11166 t.extend(c.to_lowercase());
11167 } else {
11168 t.extend(c.to_uppercase());
11169 }
11170 t
11171 })
11172 })
11173 }
11174
11175 pub fn convert_to_rot13(
11176 &mut self,
11177 _: &ConvertToRot13,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 self.manipulate_text(window, cx, |text| {
11182 text.chars()
11183 .map(|c| match c {
11184 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11185 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11186 _ => c,
11187 })
11188 .collect()
11189 })
11190 }
11191
11192 pub fn convert_to_rot47(
11193 &mut self,
11194 _: &ConvertToRot47,
11195 window: &mut Window,
11196 cx: &mut Context<Self>,
11197 ) {
11198 self.manipulate_text(window, cx, |text| {
11199 text.chars()
11200 .map(|c| {
11201 let code_point = c as u32;
11202 if code_point >= 33 && code_point <= 126 {
11203 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11204 }
11205 c
11206 })
11207 .collect()
11208 })
11209 }
11210
11211 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11212 where
11213 Fn: FnMut(&str) -> String,
11214 {
11215 let buffer = self.buffer.read(cx).snapshot(cx);
11216
11217 let mut new_selections = Vec::new();
11218 let mut edits = Vec::new();
11219 let mut selection_adjustment = 0i32;
11220
11221 for selection in self.selections.all::<usize>(cx) {
11222 let selection_is_empty = selection.is_empty();
11223
11224 let (start, end) = if selection_is_empty {
11225 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11226 (word_range.start, word_range.end)
11227 } else {
11228 (selection.start, selection.end)
11229 };
11230
11231 let text = buffer.text_for_range(start..end).collect::<String>();
11232 let old_length = text.len() as i32;
11233 let text = callback(&text);
11234
11235 new_selections.push(Selection {
11236 start: (start as i32 - selection_adjustment) as usize,
11237 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11238 goal: SelectionGoal::None,
11239 ..selection
11240 });
11241
11242 selection_adjustment += old_length - text.len() as i32;
11243
11244 edits.push((start..end, text));
11245 }
11246
11247 self.transact(window, cx, |this, window, cx| {
11248 this.buffer.update(cx, |buffer, cx| {
11249 buffer.edit(edits, None, cx);
11250 });
11251
11252 this.change_selections(Default::default(), window, cx, |s| {
11253 s.select(new_selections);
11254 });
11255
11256 this.request_autoscroll(Autoscroll::fit(), cx);
11257 });
11258 }
11259
11260 pub fn move_selection_on_drop(
11261 &mut self,
11262 selection: &Selection<Anchor>,
11263 target: DisplayPoint,
11264 is_cut: bool,
11265 window: &mut Window,
11266 cx: &mut Context<Self>,
11267 ) {
11268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11269 let buffer = &display_map.buffer_snapshot;
11270 let mut edits = Vec::new();
11271 let insert_point = display_map
11272 .clip_point(target, Bias::Left)
11273 .to_point(&display_map);
11274 let text = buffer
11275 .text_for_range(selection.start..selection.end)
11276 .collect::<String>();
11277 if is_cut {
11278 edits.push(((selection.start..selection.end), String::new()));
11279 }
11280 let insert_anchor = buffer.anchor_before(insert_point);
11281 edits.push(((insert_anchor..insert_anchor), text));
11282 let last_edit_start = insert_anchor.bias_left(buffer);
11283 let last_edit_end = insert_anchor.bias_right(buffer);
11284 self.transact(window, cx, |this, window, cx| {
11285 this.buffer.update(cx, |buffer, cx| {
11286 buffer.edit(edits, None, cx);
11287 });
11288 this.change_selections(Default::default(), window, cx, |s| {
11289 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11290 });
11291 });
11292 }
11293
11294 pub fn clear_selection_drag_state(&mut self) {
11295 self.selection_drag_state = SelectionDragState::None;
11296 }
11297
11298 pub fn duplicate(
11299 &mut self,
11300 upwards: bool,
11301 whole_lines: bool,
11302 window: &mut Window,
11303 cx: &mut Context<Self>,
11304 ) {
11305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11306
11307 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11308 let buffer = &display_map.buffer_snapshot;
11309 let selections = self.selections.all::<Point>(cx);
11310
11311 let mut edits = Vec::new();
11312 let mut selections_iter = selections.iter().peekable();
11313 while let Some(selection) = selections_iter.next() {
11314 let mut rows = selection.spanned_rows(false, &display_map);
11315 // duplicate line-wise
11316 if whole_lines || selection.start == selection.end {
11317 // Avoid duplicating the same lines twice.
11318 while let Some(next_selection) = selections_iter.peek() {
11319 let next_rows = next_selection.spanned_rows(false, &display_map);
11320 if next_rows.start < rows.end {
11321 rows.end = next_rows.end;
11322 selections_iter.next().unwrap();
11323 } else {
11324 break;
11325 }
11326 }
11327
11328 // Copy the text from the selected row region and splice it either at the start
11329 // or end of the region.
11330 let start = Point::new(rows.start.0, 0);
11331 let end = Point::new(
11332 rows.end.previous_row().0,
11333 buffer.line_len(rows.end.previous_row()),
11334 );
11335 let text = buffer
11336 .text_for_range(start..end)
11337 .chain(Some("\n"))
11338 .collect::<String>();
11339 let insert_location = if upwards {
11340 Point::new(rows.end.0, 0)
11341 } else {
11342 start
11343 };
11344 edits.push((insert_location..insert_location, text));
11345 } else {
11346 // duplicate character-wise
11347 let start = selection.start;
11348 let end = selection.end;
11349 let text = buffer.text_for_range(start..end).collect::<String>();
11350 edits.push((selection.end..selection.end, text));
11351 }
11352 }
11353
11354 self.transact(window, cx, |this, _, cx| {
11355 this.buffer.update(cx, |buffer, cx| {
11356 buffer.edit(edits, None, cx);
11357 });
11358
11359 this.request_autoscroll(Autoscroll::fit(), cx);
11360 });
11361 }
11362
11363 pub fn duplicate_line_up(
11364 &mut self,
11365 _: &DuplicateLineUp,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 self.duplicate(true, true, window, cx);
11370 }
11371
11372 pub fn duplicate_line_down(
11373 &mut self,
11374 _: &DuplicateLineDown,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.duplicate(false, true, window, cx);
11379 }
11380
11381 pub fn duplicate_selection(
11382 &mut self,
11383 _: &DuplicateSelection,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.duplicate(false, false, window, cx);
11388 }
11389
11390 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11392 if self.mode.is_single_line() {
11393 cx.propagate();
11394 return;
11395 }
11396
11397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11398 let buffer = self.buffer.read(cx).snapshot(cx);
11399
11400 let mut edits = Vec::new();
11401 let mut unfold_ranges = Vec::new();
11402 let mut refold_creases = Vec::new();
11403
11404 let selections = self.selections.all::<Point>(cx);
11405 let mut selections = selections.iter().peekable();
11406 let mut contiguous_row_selections = Vec::new();
11407 let mut new_selections = Vec::new();
11408
11409 while let Some(selection) = selections.next() {
11410 // Find all the selections that span a contiguous row range
11411 let (start_row, end_row) = consume_contiguous_rows(
11412 &mut contiguous_row_selections,
11413 selection,
11414 &display_map,
11415 &mut selections,
11416 );
11417
11418 // Move the text spanned by the row range to be before the line preceding the row range
11419 if start_row.0 > 0 {
11420 let range_to_move = Point::new(
11421 start_row.previous_row().0,
11422 buffer.line_len(start_row.previous_row()),
11423 )
11424 ..Point::new(
11425 end_row.previous_row().0,
11426 buffer.line_len(end_row.previous_row()),
11427 );
11428 let insertion_point = display_map
11429 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11430 .0;
11431
11432 // Don't move lines across excerpts
11433 if buffer
11434 .excerpt_containing(insertion_point..range_to_move.end)
11435 .is_some()
11436 {
11437 let text = buffer
11438 .text_for_range(range_to_move.clone())
11439 .flat_map(|s| s.chars())
11440 .skip(1)
11441 .chain(['\n'])
11442 .collect::<String>();
11443
11444 edits.push((
11445 buffer.anchor_after(range_to_move.start)
11446 ..buffer.anchor_before(range_to_move.end),
11447 String::new(),
11448 ));
11449 let insertion_anchor = buffer.anchor_after(insertion_point);
11450 edits.push((insertion_anchor..insertion_anchor, text));
11451
11452 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11453
11454 // Move selections up
11455 new_selections.extend(contiguous_row_selections.drain(..).map(
11456 |mut selection| {
11457 selection.start.row -= row_delta;
11458 selection.end.row -= row_delta;
11459 selection
11460 },
11461 ));
11462
11463 // Move folds up
11464 unfold_ranges.push(range_to_move.clone());
11465 for fold in display_map.folds_in_range(
11466 buffer.anchor_before(range_to_move.start)
11467 ..buffer.anchor_after(range_to_move.end),
11468 ) {
11469 let mut start = fold.range.start.to_point(&buffer);
11470 let mut end = fold.range.end.to_point(&buffer);
11471 start.row -= row_delta;
11472 end.row -= row_delta;
11473 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11474 }
11475 }
11476 }
11477
11478 // If we didn't move line(s), preserve the existing selections
11479 new_selections.append(&mut contiguous_row_selections);
11480 }
11481
11482 self.transact(window, cx, |this, window, cx| {
11483 this.unfold_ranges(&unfold_ranges, true, true, cx);
11484 this.buffer.update(cx, |buffer, cx| {
11485 for (range, text) in edits {
11486 buffer.edit([(range, text)], None, cx);
11487 }
11488 });
11489 this.fold_creases(refold_creases, true, window, cx);
11490 this.change_selections(Default::default(), window, cx, |s| {
11491 s.select(new_selections);
11492 })
11493 });
11494 }
11495
11496 pub fn move_line_down(
11497 &mut self,
11498 _: &MoveLineDown,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11503 if self.mode.is_single_line() {
11504 cx.propagate();
11505 return;
11506 }
11507
11508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11509 let buffer = self.buffer.read(cx).snapshot(cx);
11510
11511 let mut edits = Vec::new();
11512 let mut unfold_ranges = Vec::new();
11513 let mut refold_creases = Vec::new();
11514
11515 let selections = self.selections.all::<Point>(cx);
11516 let mut selections = selections.iter().peekable();
11517 let mut contiguous_row_selections = Vec::new();
11518 let mut new_selections = Vec::new();
11519
11520 while let Some(selection) = selections.next() {
11521 // Find all the selections that span a contiguous row range
11522 let (start_row, end_row) = consume_contiguous_rows(
11523 &mut contiguous_row_selections,
11524 selection,
11525 &display_map,
11526 &mut selections,
11527 );
11528
11529 // Move the text spanned by the row range to be after the last line of the row range
11530 if end_row.0 <= buffer.max_point().row {
11531 let range_to_move =
11532 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11533 let insertion_point = display_map
11534 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11535 .0;
11536
11537 // Don't move lines across excerpt boundaries
11538 if buffer
11539 .excerpt_containing(range_to_move.start..insertion_point)
11540 .is_some()
11541 {
11542 let mut text = String::from("\n");
11543 text.extend(buffer.text_for_range(range_to_move.clone()));
11544 text.pop(); // Drop trailing newline
11545 edits.push((
11546 buffer.anchor_after(range_to_move.start)
11547 ..buffer.anchor_before(range_to_move.end),
11548 String::new(),
11549 ));
11550 let insertion_anchor = buffer.anchor_after(insertion_point);
11551 edits.push((insertion_anchor..insertion_anchor, text));
11552
11553 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11554
11555 // Move selections down
11556 new_selections.extend(contiguous_row_selections.drain(..).map(
11557 |mut selection| {
11558 selection.start.row += row_delta;
11559 selection.end.row += row_delta;
11560 selection
11561 },
11562 ));
11563
11564 // Move folds down
11565 unfold_ranges.push(range_to_move.clone());
11566 for fold in display_map.folds_in_range(
11567 buffer.anchor_before(range_to_move.start)
11568 ..buffer.anchor_after(range_to_move.end),
11569 ) {
11570 let mut start = fold.range.start.to_point(&buffer);
11571 let mut end = fold.range.end.to_point(&buffer);
11572 start.row += row_delta;
11573 end.row += row_delta;
11574 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11575 }
11576 }
11577 }
11578
11579 // If we didn't move line(s), preserve the existing selections
11580 new_selections.append(&mut contiguous_row_selections);
11581 }
11582
11583 self.transact(window, cx, |this, window, cx| {
11584 this.unfold_ranges(&unfold_ranges, true, true, cx);
11585 this.buffer.update(cx, |buffer, cx| {
11586 for (range, text) in edits {
11587 buffer.edit([(range, text)], None, cx);
11588 }
11589 });
11590 this.fold_creases(refold_creases, true, window, cx);
11591 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11592 });
11593 }
11594
11595 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11597 let text_layout_details = &self.text_layout_details(window);
11598 self.transact(window, cx, |this, window, cx| {
11599 let edits = this.change_selections(Default::default(), window, cx, |s| {
11600 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11601 s.move_with(|display_map, selection| {
11602 if !selection.is_empty() {
11603 return;
11604 }
11605
11606 let mut head = selection.head();
11607 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11608 if head.column() == display_map.line_len(head.row()) {
11609 transpose_offset = display_map
11610 .buffer_snapshot
11611 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11612 }
11613
11614 if transpose_offset == 0 {
11615 return;
11616 }
11617
11618 *head.column_mut() += 1;
11619 head = display_map.clip_point(head, Bias::Right);
11620 let goal = SelectionGoal::HorizontalPosition(
11621 display_map
11622 .x_for_display_point(head, text_layout_details)
11623 .into(),
11624 );
11625 selection.collapse_to(head, goal);
11626
11627 let transpose_start = display_map
11628 .buffer_snapshot
11629 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11630 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11631 let transpose_end = display_map
11632 .buffer_snapshot
11633 .clip_offset(transpose_offset + 1, Bias::Right);
11634 if let Some(ch) =
11635 display_map.buffer_snapshot.chars_at(transpose_start).next()
11636 {
11637 edits.push((transpose_start..transpose_offset, String::new()));
11638 edits.push((transpose_end..transpose_end, ch.to_string()));
11639 }
11640 }
11641 });
11642 edits
11643 });
11644 this.buffer
11645 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11646 let selections = this.selections.all::<usize>(cx);
11647 this.change_selections(Default::default(), window, cx, |s| {
11648 s.select(selections);
11649 });
11650 });
11651 }
11652
11653 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11655 if self.mode.is_single_line() {
11656 cx.propagate();
11657 return;
11658 }
11659
11660 self.rewrap_impl(RewrapOptions::default(), cx)
11661 }
11662
11663 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11664 let buffer = self.buffer.read(cx).snapshot(cx);
11665 let selections = self.selections.all::<Point>(cx);
11666
11667 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11668 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11669 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11670 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11671 .peekable();
11672
11673 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11674 row
11675 } else {
11676 return Vec::new();
11677 };
11678
11679 let language_settings = buffer.language_settings_at(selection.head(), cx);
11680 let language_scope = buffer.language_scope_at(selection.head());
11681
11682 let indent_and_prefix_for_row =
11683 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11684 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11685 let (comment_prefix, rewrap_prefix) =
11686 if let Some(language_scope) = &language_scope {
11687 let indent_end = Point::new(row, indent.len);
11688 let comment_prefix = language_scope
11689 .line_comment_prefixes()
11690 .iter()
11691 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11692 .map(|prefix| prefix.to_string());
11693 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11694 let line_text_after_indent = buffer
11695 .text_for_range(indent_end..line_end)
11696 .collect::<String>();
11697 let rewrap_prefix = language_scope
11698 .rewrap_prefixes()
11699 .iter()
11700 .find_map(|prefix_regex| {
11701 prefix_regex.find(&line_text_after_indent).map(|mat| {
11702 if mat.start() == 0 {
11703 Some(mat.as_str().to_string())
11704 } else {
11705 None
11706 }
11707 })
11708 })
11709 .flatten();
11710 (comment_prefix, rewrap_prefix)
11711 } else {
11712 (None, None)
11713 };
11714 (indent, comment_prefix, rewrap_prefix)
11715 };
11716
11717 let mut ranges = Vec::new();
11718 let from_empty_selection = selection.is_empty();
11719
11720 let mut current_range_start = first_row;
11721 let mut prev_row = first_row;
11722 let (
11723 mut current_range_indent,
11724 mut current_range_comment_prefix,
11725 mut current_range_rewrap_prefix,
11726 ) = indent_and_prefix_for_row(first_row);
11727
11728 for row in non_blank_rows_iter.skip(1) {
11729 let has_paragraph_break = row > prev_row + 1;
11730
11731 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11732 indent_and_prefix_for_row(row);
11733
11734 let has_indent_change = row_indent != current_range_indent;
11735 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11736
11737 let has_boundary_change = has_comment_change
11738 || row_rewrap_prefix.is_some()
11739 || (has_indent_change && current_range_comment_prefix.is_some());
11740
11741 if has_paragraph_break || has_boundary_change {
11742 ranges.push((
11743 language_settings.clone(),
11744 Point::new(current_range_start, 0)
11745 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11746 current_range_indent,
11747 current_range_comment_prefix.clone(),
11748 current_range_rewrap_prefix.clone(),
11749 from_empty_selection,
11750 ));
11751 current_range_start = row;
11752 current_range_indent = row_indent;
11753 current_range_comment_prefix = row_comment_prefix;
11754 current_range_rewrap_prefix = row_rewrap_prefix;
11755 }
11756 prev_row = row;
11757 }
11758
11759 ranges.push((
11760 language_settings.clone(),
11761 Point::new(current_range_start, 0)
11762 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11763 current_range_indent,
11764 current_range_comment_prefix,
11765 current_range_rewrap_prefix,
11766 from_empty_selection,
11767 ));
11768
11769 ranges
11770 });
11771
11772 let mut edits = Vec::new();
11773 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11774
11775 for (
11776 language_settings,
11777 wrap_range,
11778 indent_size,
11779 comment_prefix,
11780 rewrap_prefix,
11781 from_empty_selection,
11782 ) in wrap_ranges
11783 {
11784 let mut start_row = wrap_range.start.row;
11785 let mut end_row = wrap_range.end.row;
11786
11787 // Skip selections that overlap with a range that has already been rewrapped.
11788 let selection_range = start_row..end_row;
11789 if rewrapped_row_ranges
11790 .iter()
11791 .any(|range| range.overlaps(&selection_range))
11792 {
11793 continue;
11794 }
11795
11796 let tab_size = language_settings.tab_size;
11797
11798 let indent_prefix = indent_size.chars().collect::<String>();
11799 let mut line_prefix = indent_prefix.clone();
11800 let mut inside_comment = false;
11801 if let Some(prefix) = &comment_prefix {
11802 line_prefix.push_str(prefix);
11803 inside_comment = true;
11804 }
11805 if let Some(prefix) = &rewrap_prefix {
11806 line_prefix.push_str(prefix);
11807 }
11808
11809 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11810 RewrapBehavior::InComments => inside_comment,
11811 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11812 RewrapBehavior::Anywhere => true,
11813 };
11814
11815 let should_rewrap = options.override_language_settings
11816 || allow_rewrap_based_on_language
11817 || self.hard_wrap.is_some();
11818 if !should_rewrap {
11819 continue;
11820 }
11821
11822 if from_empty_selection {
11823 'expand_upwards: while start_row > 0 {
11824 let prev_row = start_row - 1;
11825 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11826 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11827 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11828 {
11829 start_row = prev_row;
11830 } else {
11831 break 'expand_upwards;
11832 }
11833 }
11834
11835 'expand_downwards: while end_row < buffer.max_point().row {
11836 let next_row = end_row + 1;
11837 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11838 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11839 && !buffer.is_line_blank(MultiBufferRow(next_row))
11840 {
11841 end_row = next_row;
11842 } else {
11843 break 'expand_downwards;
11844 }
11845 }
11846 }
11847
11848 let start = Point::new(start_row, 0);
11849 let start_offset = start.to_offset(&buffer);
11850 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11851 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11852 let Some(lines_without_prefixes) = selection_text
11853 .lines()
11854 .enumerate()
11855 .map(|(ix, line)| {
11856 let line_trimmed = line.trim_start();
11857 if rewrap_prefix.is_some() && ix > 0 {
11858 Ok(line_trimmed)
11859 } else {
11860 line_trimmed
11861 .strip_prefix(&line_prefix.trim_start())
11862 .with_context(|| {
11863 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11864 })
11865 }
11866 })
11867 .collect::<Result<Vec<_>, _>>()
11868 .log_err()
11869 else {
11870 continue;
11871 };
11872
11873 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11874 buffer
11875 .language_settings_at(Point::new(start_row, 0), cx)
11876 .preferred_line_length as usize
11877 });
11878
11879 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11880 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11881 } else {
11882 line_prefix.clone()
11883 };
11884
11885 let wrapped_text = wrap_with_prefix(
11886 line_prefix,
11887 subsequent_lines_prefix,
11888 lines_without_prefixes.join("\n"),
11889 wrap_column,
11890 tab_size,
11891 options.preserve_existing_whitespace,
11892 );
11893
11894 // TODO: should always use char-based diff while still supporting cursor behavior that
11895 // matches vim.
11896 let mut diff_options = DiffOptions::default();
11897 if options.override_language_settings {
11898 diff_options.max_word_diff_len = 0;
11899 diff_options.max_word_diff_line_count = 0;
11900 } else {
11901 diff_options.max_word_diff_len = usize::MAX;
11902 diff_options.max_word_diff_line_count = usize::MAX;
11903 }
11904
11905 for (old_range, new_text) in
11906 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11907 {
11908 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11909 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11910 edits.push((edit_start..edit_end, new_text));
11911 }
11912
11913 rewrapped_row_ranges.push(start_row..=end_row);
11914 }
11915
11916 self.buffer
11917 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11918 }
11919
11920 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11921 let mut text = String::new();
11922 let buffer = self.buffer.read(cx).snapshot(cx);
11923 let mut selections = self.selections.all::<Point>(cx);
11924 let mut clipboard_selections = Vec::with_capacity(selections.len());
11925 {
11926 let max_point = buffer.max_point();
11927 let mut is_first = true;
11928 for selection in &mut selections {
11929 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11930 if is_entire_line {
11931 selection.start = Point::new(selection.start.row, 0);
11932 if !selection.is_empty() && selection.end.column == 0 {
11933 selection.end = cmp::min(max_point, selection.end);
11934 } else {
11935 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11936 }
11937 selection.goal = SelectionGoal::None;
11938 }
11939 if is_first {
11940 is_first = false;
11941 } else {
11942 text += "\n";
11943 }
11944 let mut len = 0;
11945 for chunk in buffer.text_for_range(selection.start..selection.end) {
11946 text.push_str(chunk);
11947 len += chunk.len();
11948 }
11949 clipboard_selections.push(ClipboardSelection {
11950 len,
11951 is_entire_line,
11952 first_line_indent: buffer
11953 .indent_size_for_line(MultiBufferRow(selection.start.row))
11954 .len,
11955 });
11956 }
11957 }
11958
11959 self.transact(window, cx, |this, window, cx| {
11960 this.change_selections(Default::default(), window, cx, |s| {
11961 s.select(selections);
11962 });
11963 this.insert("", window, cx);
11964 });
11965 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11966 }
11967
11968 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11970 let item = self.cut_common(window, cx);
11971 cx.write_to_clipboard(item);
11972 }
11973
11974 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11976 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11977 s.move_with(|snapshot, sel| {
11978 if sel.is_empty() {
11979 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11980 }
11981 });
11982 });
11983 let item = self.cut_common(window, cx);
11984 cx.set_global(KillRing(item))
11985 }
11986
11987 pub fn kill_ring_yank(
11988 &mut self,
11989 _: &KillRingYank,
11990 window: &mut Window,
11991 cx: &mut Context<Self>,
11992 ) {
11993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11994 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11995 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11996 (kill_ring.text().to_string(), kill_ring.metadata_json())
11997 } else {
11998 return;
11999 }
12000 } else {
12001 return;
12002 };
12003 self.do_paste(&text, metadata, false, window, cx);
12004 }
12005
12006 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12007 self.do_copy(true, cx);
12008 }
12009
12010 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12011 self.do_copy(false, cx);
12012 }
12013
12014 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12015 let selections = self.selections.all::<Point>(cx);
12016 let buffer = self.buffer.read(cx).read(cx);
12017 let mut text = String::new();
12018
12019 let mut clipboard_selections = Vec::with_capacity(selections.len());
12020 {
12021 let max_point = buffer.max_point();
12022 let mut is_first = true;
12023 for selection in &selections {
12024 let mut start = selection.start;
12025 let mut end = selection.end;
12026 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12027 if is_entire_line {
12028 start = Point::new(start.row, 0);
12029 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12030 }
12031
12032 let mut trimmed_selections = Vec::new();
12033 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12034 let row = MultiBufferRow(start.row);
12035 let first_indent = buffer.indent_size_for_line(row);
12036 if first_indent.len == 0 || start.column > first_indent.len {
12037 trimmed_selections.push(start..end);
12038 } else {
12039 trimmed_selections.push(
12040 Point::new(row.0, first_indent.len)
12041 ..Point::new(row.0, buffer.line_len(row)),
12042 );
12043 for row in start.row + 1..=end.row {
12044 let mut line_len = buffer.line_len(MultiBufferRow(row));
12045 if row == end.row {
12046 line_len = end.column;
12047 }
12048 if line_len == 0 {
12049 trimmed_selections
12050 .push(Point::new(row, 0)..Point::new(row, line_len));
12051 continue;
12052 }
12053 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12054 if row_indent_size.len >= first_indent.len {
12055 trimmed_selections.push(
12056 Point::new(row, first_indent.len)..Point::new(row, line_len),
12057 );
12058 } else {
12059 trimmed_selections.clear();
12060 trimmed_selections.push(start..end);
12061 break;
12062 }
12063 }
12064 }
12065 } else {
12066 trimmed_selections.push(start..end);
12067 }
12068
12069 for trimmed_range in trimmed_selections {
12070 if is_first {
12071 is_first = false;
12072 } else {
12073 text += "\n";
12074 }
12075 let mut len = 0;
12076 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12077 text.push_str(chunk);
12078 len += chunk.len();
12079 }
12080 clipboard_selections.push(ClipboardSelection {
12081 len,
12082 is_entire_line,
12083 first_line_indent: buffer
12084 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12085 .len,
12086 });
12087 }
12088 }
12089 }
12090
12091 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12092 text,
12093 clipboard_selections,
12094 ));
12095 }
12096
12097 pub fn do_paste(
12098 &mut self,
12099 text: &String,
12100 clipboard_selections: Option<Vec<ClipboardSelection>>,
12101 handle_entire_lines: bool,
12102 window: &mut Window,
12103 cx: &mut Context<Self>,
12104 ) {
12105 if self.read_only(cx) {
12106 return;
12107 }
12108
12109 let clipboard_text = Cow::Borrowed(text);
12110
12111 self.transact(window, cx, |this, window, cx| {
12112 if let Some(mut clipboard_selections) = clipboard_selections {
12113 let old_selections = this.selections.all::<usize>(cx);
12114 let all_selections_were_entire_line =
12115 clipboard_selections.iter().all(|s| s.is_entire_line);
12116 let first_selection_indent_column =
12117 clipboard_selections.first().map(|s| s.first_line_indent);
12118 if clipboard_selections.len() != old_selections.len() {
12119 clipboard_selections.drain(..);
12120 }
12121 let cursor_offset = this.selections.last::<usize>(cx).head();
12122 let mut auto_indent_on_paste = true;
12123
12124 this.buffer.update(cx, |buffer, cx| {
12125 let snapshot = buffer.read(cx);
12126 auto_indent_on_paste = snapshot
12127 .language_settings_at(cursor_offset, cx)
12128 .auto_indent_on_paste;
12129
12130 let mut start_offset = 0;
12131 let mut edits = Vec::new();
12132 let mut original_indent_columns = Vec::new();
12133 for (ix, selection) in old_selections.iter().enumerate() {
12134 let to_insert;
12135 let entire_line;
12136 let original_indent_column;
12137 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12138 let end_offset = start_offset + clipboard_selection.len;
12139 to_insert = &clipboard_text[start_offset..end_offset];
12140 entire_line = clipboard_selection.is_entire_line;
12141 start_offset = end_offset + 1;
12142 original_indent_column = Some(clipboard_selection.first_line_indent);
12143 } else {
12144 to_insert = clipboard_text.as_str();
12145 entire_line = all_selections_were_entire_line;
12146 original_indent_column = first_selection_indent_column
12147 }
12148
12149 // If the corresponding selection was empty when this slice of the
12150 // clipboard text was written, then the entire line containing the
12151 // selection was copied. If this selection is also currently empty,
12152 // then paste the line before the current line of the buffer.
12153 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12154 let column = selection.start.to_point(&snapshot).column as usize;
12155 let line_start = selection.start - column;
12156 line_start..line_start
12157 } else {
12158 selection.range()
12159 };
12160
12161 edits.push((range, to_insert));
12162 original_indent_columns.push(original_indent_column);
12163 }
12164 drop(snapshot);
12165
12166 buffer.edit(
12167 edits,
12168 if auto_indent_on_paste {
12169 Some(AutoindentMode::Block {
12170 original_indent_columns,
12171 })
12172 } else {
12173 None
12174 },
12175 cx,
12176 );
12177 });
12178
12179 let selections = this.selections.all::<usize>(cx);
12180 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12181 } else {
12182 this.insert(&clipboard_text, window, cx);
12183 }
12184 });
12185 }
12186
12187 pub fn diff_clipboard_with_selection(
12188 &mut self,
12189 _: &DiffClipboardWithSelection,
12190 window: &mut Window,
12191 cx: &mut Context<Self>,
12192 ) {
12193 let selections = self.selections.all::<usize>(cx);
12194
12195 if selections.is_empty() {
12196 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12197 return;
12198 };
12199
12200 let clipboard_text = match cx.read_from_clipboard() {
12201 Some(item) => match item.entries().first() {
12202 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12203 _ => None,
12204 },
12205 None => None,
12206 };
12207
12208 let Some(clipboard_text) = clipboard_text else {
12209 log::warn!("Clipboard doesn't contain text.");
12210 return;
12211 };
12212
12213 window.dispatch_action(
12214 Box::new(DiffClipboardWithSelectionData {
12215 clipboard_text,
12216 editor: cx.entity(),
12217 }),
12218 cx,
12219 );
12220 }
12221
12222 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12224 if let Some(item) = cx.read_from_clipboard() {
12225 let entries = item.entries();
12226
12227 match entries.first() {
12228 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12229 // of all the pasted entries.
12230 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12231 .do_paste(
12232 clipboard_string.text(),
12233 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12234 true,
12235 window,
12236 cx,
12237 ),
12238 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12239 }
12240 }
12241 }
12242
12243 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12244 if self.read_only(cx) {
12245 return;
12246 }
12247
12248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12249
12250 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12251 if let Some((selections, _)) =
12252 self.selection_history.transaction(transaction_id).cloned()
12253 {
12254 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12255 s.select_anchors(selections.to_vec());
12256 });
12257 } else {
12258 log::error!(
12259 "No entry in selection_history found for undo. \
12260 This may correspond to a bug where undo does not update the selection. \
12261 If this is occurring, please add details to \
12262 https://github.com/zed-industries/zed/issues/22692"
12263 );
12264 }
12265 self.request_autoscroll(Autoscroll::fit(), cx);
12266 self.unmark_text(window, cx);
12267 self.refresh_inline_completion(true, false, window, cx);
12268 cx.emit(EditorEvent::Edited { transaction_id });
12269 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12270 }
12271 }
12272
12273 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12274 if self.read_only(cx) {
12275 return;
12276 }
12277
12278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12279
12280 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12281 if let Some((_, Some(selections))) =
12282 self.selection_history.transaction(transaction_id).cloned()
12283 {
12284 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12285 s.select_anchors(selections.to_vec());
12286 });
12287 } else {
12288 log::error!(
12289 "No entry in selection_history found for redo. \
12290 This may correspond to a bug where undo does not update the selection. \
12291 If this is occurring, please add details to \
12292 https://github.com/zed-industries/zed/issues/22692"
12293 );
12294 }
12295 self.request_autoscroll(Autoscroll::fit(), cx);
12296 self.unmark_text(window, cx);
12297 self.refresh_inline_completion(true, false, window, cx);
12298 cx.emit(EditorEvent::Edited { transaction_id });
12299 }
12300 }
12301
12302 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12303 self.buffer
12304 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12305 }
12306
12307 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12308 self.buffer
12309 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12310 }
12311
12312 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12314 self.change_selections(Default::default(), window, cx, |s| {
12315 s.move_with(|map, selection| {
12316 let cursor = if selection.is_empty() {
12317 movement::left(map, selection.start)
12318 } else {
12319 selection.start
12320 };
12321 selection.collapse_to(cursor, SelectionGoal::None);
12322 });
12323 })
12324 }
12325
12326 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12328 self.change_selections(Default::default(), window, cx, |s| {
12329 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12330 })
12331 }
12332
12333 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12335 self.change_selections(Default::default(), window, cx, |s| {
12336 s.move_with(|map, selection| {
12337 let cursor = if selection.is_empty() {
12338 movement::right(map, selection.end)
12339 } else {
12340 selection.end
12341 };
12342 selection.collapse_to(cursor, SelectionGoal::None)
12343 });
12344 })
12345 }
12346
12347 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12349 self.change_selections(Default::default(), window, cx, |s| {
12350 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12351 })
12352 }
12353
12354 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12355 if self.take_rename(true, window, cx).is_some() {
12356 return;
12357 }
12358
12359 if self.mode.is_single_line() {
12360 cx.propagate();
12361 return;
12362 }
12363
12364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12365
12366 let text_layout_details = &self.text_layout_details(window);
12367 let selection_count = self.selections.count();
12368 let first_selection = self.selections.first_anchor();
12369
12370 self.change_selections(Default::default(), window, cx, |s| {
12371 s.move_with(|map, selection| {
12372 if !selection.is_empty() {
12373 selection.goal = SelectionGoal::None;
12374 }
12375 let (cursor, goal) = movement::up(
12376 map,
12377 selection.start,
12378 selection.goal,
12379 false,
12380 text_layout_details,
12381 );
12382 selection.collapse_to(cursor, goal);
12383 });
12384 });
12385
12386 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12387 {
12388 cx.propagate();
12389 }
12390 }
12391
12392 pub fn move_up_by_lines(
12393 &mut self,
12394 action: &MoveUpByLines,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) {
12398 if self.take_rename(true, window, cx).is_some() {
12399 return;
12400 }
12401
12402 if self.mode.is_single_line() {
12403 cx.propagate();
12404 return;
12405 }
12406
12407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12408
12409 let text_layout_details = &self.text_layout_details(window);
12410
12411 self.change_selections(Default::default(), window, cx, |s| {
12412 s.move_with(|map, selection| {
12413 if !selection.is_empty() {
12414 selection.goal = SelectionGoal::None;
12415 }
12416 let (cursor, goal) = movement::up_by_rows(
12417 map,
12418 selection.start,
12419 action.lines,
12420 selection.goal,
12421 false,
12422 text_layout_details,
12423 );
12424 selection.collapse_to(cursor, goal);
12425 });
12426 })
12427 }
12428
12429 pub fn move_down_by_lines(
12430 &mut self,
12431 action: &MoveDownByLines,
12432 window: &mut Window,
12433 cx: &mut Context<Self>,
12434 ) {
12435 if self.take_rename(true, window, cx).is_some() {
12436 return;
12437 }
12438
12439 if self.mode.is_single_line() {
12440 cx.propagate();
12441 return;
12442 }
12443
12444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12445
12446 let text_layout_details = &self.text_layout_details(window);
12447
12448 self.change_selections(Default::default(), window, cx, |s| {
12449 s.move_with(|map, selection| {
12450 if !selection.is_empty() {
12451 selection.goal = SelectionGoal::None;
12452 }
12453 let (cursor, goal) = movement::down_by_rows(
12454 map,
12455 selection.start,
12456 action.lines,
12457 selection.goal,
12458 false,
12459 text_layout_details,
12460 );
12461 selection.collapse_to(cursor, goal);
12462 });
12463 })
12464 }
12465
12466 pub fn select_down_by_lines(
12467 &mut self,
12468 action: &SelectDownByLines,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12473 let text_layout_details = &self.text_layout_details(window);
12474 self.change_selections(Default::default(), window, cx, |s| {
12475 s.move_heads_with(|map, head, goal| {
12476 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12477 })
12478 })
12479 }
12480
12481 pub fn select_up_by_lines(
12482 &mut self,
12483 action: &SelectUpByLines,
12484 window: &mut Window,
12485 cx: &mut Context<Self>,
12486 ) {
12487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12488 let text_layout_details = &self.text_layout_details(window);
12489 self.change_selections(Default::default(), window, cx, |s| {
12490 s.move_heads_with(|map, head, goal| {
12491 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12492 })
12493 })
12494 }
12495
12496 pub fn select_page_up(
12497 &mut self,
12498 _: &SelectPageUp,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 let Some(row_count) = self.visible_row_count() else {
12503 return;
12504 };
12505
12506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12507
12508 let text_layout_details = &self.text_layout_details(window);
12509
12510 self.change_selections(Default::default(), window, cx, |s| {
12511 s.move_heads_with(|map, head, goal| {
12512 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12513 })
12514 })
12515 }
12516
12517 pub fn move_page_up(
12518 &mut self,
12519 action: &MovePageUp,
12520 window: &mut Window,
12521 cx: &mut Context<Self>,
12522 ) {
12523 if self.take_rename(true, window, cx).is_some() {
12524 return;
12525 }
12526
12527 if self
12528 .context_menu
12529 .borrow_mut()
12530 .as_mut()
12531 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12532 .unwrap_or(false)
12533 {
12534 return;
12535 }
12536
12537 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12538 cx.propagate();
12539 return;
12540 }
12541
12542 let Some(row_count) = self.visible_row_count() else {
12543 return;
12544 };
12545
12546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12547
12548 let effects = if action.center_cursor {
12549 SelectionEffects::scroll(Autoscroll::center())
12550 } else {
12551 SelectionEffects::default()
12552 };
12553
12554 let text_layout_details = &self.text_layout_details(window);
12555
12556 self.change_selections(effects, window, cx, |s| {
12557 s.move_with(|map, selection| {
12558 if !selection.is_empty() {
12559 selection.goal = SelectionGoal::None;
12560 }
12561 let (cursor, goal) = movement::up_by_rows(
12562 map,
12563 selection.end,
12564 row_count,
12565 selection.goal,
12566 false,
12567 text_layout_details,
12568 );
12569 selection.collapse_to(cursor, goal);
12570 });
12571 });
12572 }
12573
12574 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12576 let text_layout_details = &self.text_layout_details(window);
12577 self.change_selections(Default::default(), window, cx, |s| {
12578 s.move_heads_with(|map, head, goal| {
12579 movement::up(map, head, goal, false, text_layout_details)
12580 })
12581 })
12582 }
12583
12584 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12585 self.take_rename(true, window, cx);
12586
12587 if self.mode.is_single_line() {
12588 cx.propagate();
12589 return;
12590 }
12591
12592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12593
12594 let text_layout_details = &self.text_layout_details(window);
12595 let selection_count = self.selections.count();
12596 let first_selection = self.selections.first_anchor();
12597
12598 self.change_selections(Default::default(), window, cx, |s| {
12599 s.move_with(|map, selection| {
12600 if !selection.is_empty() {
12601 selection.goal = SelectionGoal::None;
12602 }
12603 let (cursor, goal) = movement::down(
12604 map,
12605 selection.end,
12606 selection.goal,
12607 false,
12608 text_layout_details,
12609 );
12610 selection.collapse_to(cursor, goal);
12611 });
12612 });
12613
12614 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12615 {
12616 cx.propagate();
12617 }
12618 }
12619
12620 pub fn select_page_down(
12621 &mut self,
12622 _: &SelectPageDown,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) {
12626 let Some(row_count) = self.visible_row_count() else {
12627 return;
12628 };
12629
12630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12631
12632 let text_layout_details = &self.text_layout_details(window);
12633
12634 self.change_selections(Default::default(), window, cx, |s| {
12635 s.move_heads_with(|map, head, goal| {
12636 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12637 })
12638 })
12639 }
12640
12641 pub fn move_page_down(
12642 &mut self,
12643 action: &MovePageDown,
12644 window: &mut Window,
12645 cx: &mut Context<Self>,
12646 ) {
12647 if self.take_rename(true, window, cx).is_some() {
12648 return;
12649 }
12650
12651 if self
12652 .context_menu
12653 .borrow_mut()
12654 .as_mut()
12655 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12656 .unwrap_or(false)
12657 {
12658 return;
12659 }
12660
12661 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12662 cx.propagate();
12663 return;
12664 }
12665
12666 let Some(row_count) = self.visible_row_count() else {
12667 return;
12668 };
12669
12670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12671
12672 let effects = if action.center_cursor {
12673 SelectionEffects::scroll(Autoscroll::center())
12674 } else {
12675 SelectionEffects::default()
12676 };
12677
12678 let text_layout_details = &self.text_layout_details(window);
12679 self.change_selections(effects, window, cx, |s| {
12680 s.move_with(|map, selection| {
12681 if !selection.is_empty() {
12682 selection.goal = SelectionGoal::None;
12683 }
12684 let (cursor, goal) = movement::down_by_rows(
12685 map,
12686 selection.end,
12687 row_count,
12688 selection.goal,
12689 false,
12690 text_layout_details,
12691 );
12692 selection.collapse_to(cursor, goal);
12693 });
12694 });
12695 }
12696
12697 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12699 let text_layout_details = &self.text_layout_details(window);
12700 self.change_selections(Default::default(), window, cx, |s| {
12701 s.move_heads_with(|map, head, goal| {
12702 movement::down(map, head, goal, false, text_layout_details)
12703 })
12704 });
12705 }
12706
12707 pub fn context_menu_first(
12708 &mut self,
12709 _: &ContextMenuFirst,
12710 window: &mut Window,
12711 cx: &mut Context<Self>,
12712 ) {
12713 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12714 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12715 }
12716 }
12717
12718 pub fn context_menu_prev(
12719 &mut self,
12720 _: &ContextMenuPrevious,
12721 window: &mut Window,
12722 cx: &mut Context<Self>,
12723 ) {
12724 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12725 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12726 }
12727 }
12728
12729 pub fn context_menu_next(
12730 &mut self,
12731 _: &ContextMenuNext,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12736 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12737 }
12738 }
12739
12740 pub fn context_menu_last(
12741 &mut self,
12742 _: &ContextMenuLast,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) {
12746 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12747 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12748 }
12749 }
12750
12751 pub fn signature_help_prev(
12752 &mut self,
12753 _: &SignatureHelpPrevious,
12754 _: &mut Window,
12755 cx: &mut Context<Self>,
12756 ) {
12757 if let Some(popover) = self.signature_help_state.popover_mut() {
12758 if popover.current_signature == 0 {
12759 popover.current_signature = popover.signatures.len() - 1;
12760 } else {
12761 popover.current_signature -= 1;
12762 }
12763 cx.notify();
12764 }
12765 }
12766
12767 pub fn signature_help_next(
12768 &mut self,
12769 _: &SignatureHelpNext,
12770 _: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) {
12773 if let Some(popover) = self.signature_help_state.popover_mut() {
12774 if popover.current_signature + 1 == popover.signatures.len() {
12775 popover.current_signature = 0;
12776 } else {
12777 popover.current_signature += 1;
12778 }
12779 cx.notify();
12780 }
12781 }
12782
12783 pub fn move_to_previous_word_start(
12784 &mut self,
12785 _: &MoveToPreviousWordStart,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790 self.change_selections(Default::default(), window, cx, |s| {
12791 s.move_cursors_with(|map, head, _| {
12792 (
12793 movement::previous_word_start(map, head),
12794 SelectionGoal::None,
12795 )
12796 });
12797 })
12798 }
12799
12800 pub fn move_to_previous_subword_start(
12801 &mut self,
12802 _: &MoveToPreviousSubwordStart,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12807 self.change_selections(Default::default(), window, cx, |s| {
12808 s.move_cursors_with(|map, head, _| {
12809 (
12810 movement::previous_subword_start(map, head),
12811 SelectionGoal::None,
12812 )
12813 });
12814 })
12815 }
12816
12817 pub fn select_to_previous_word_start(
12818 &mut self,
12819 _: &SelectToPreviousWordStart,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12824 self.change_selections(Default::default(), window, cx, |s| {
12825 s.move_heads_with(|map, head, _| {
12826 (
12827 movement::previous_word_start(map, head),
12828 SelectionGoal::None,
12829 )
12830 });
12831 })
12832 }
12833
12834 pub fn select_to_previous_subword_start(
12835 &mut self,
12836 _: &SelectToPreviousSubwordStart,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12841 self.change_selections(Default::default(), window, cx, |s| {
12842 s.move_heads_with(|map, head, _| {
12843 (
12844 movement::previous_subword_start(map, head),
12845 SelectionGoal::None,
12846 )
12847 });
12848 })
12849 }
12850
12851 pub fn delete_to_previous_word_start(
12852 &mut self,
12853 action: &DeleteToPreviousWordStart,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12858 self.transact(window, cx, |this, window, cx| {
12859 this.select_autoclose_pair(window, cx);
12860 this.change_selections(Default::default(), window, cx, |s| {
12861 s.move_with(|map, selection| {
12862 if selection.is_empty() {
12863 let cursor = if action.ignore_newlines {
12864 movement::previous_word_start(map, selection.head())
12865 } else {
12866 movement::previous_word_start_or_newline(map, selection.head())
12867 };
12868 selection.set_head(cursor, SelectionGoal::None);
12869 }
12870 });
12871 });
12872 this.insert("", window, cx);
12873 });
12874 }
12875
12876 pub fn delete_to_previous_subword_start(
12877 &mut self,
12878 _: &DeleteToPreviousSubwordStart,
12879 window: &mut Window,
12880 cx: &mut Context<Self>,
12881 ) {
12882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12883 self.transact(window, cx, |this, window, cx| {
12884 this.select_autoclose_pair(window, cx);
12885 this.change_selections(Default::default(), window, cx, |s| {
12886 s.move_with(|map, selection| {
12887 if selection.is_empty() {
12888 let cursor = movement::previous_subword_start(map, selection.head());
12889 selection.set_head(cursor, SelectionGoal::None);
12890 }
12891 });
12892 });
12893 this.insert("", window, cx);
12894 });
12895 }
12896
12897 pub fn move_to_next_word_end(
12898 &mut self,
12899 _: &MoveToNextWordEnd,
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_cursors_with(|map, head, _| {
12906 (movement::next_word_end(map, head), SelectionGoal::None)
12907 });
12908 })
12909 }
12910
12911 pub fn move_to_next_subword_end(
12912 &mut self,
12913 _: &MoveToNextSubwordEnd,
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_cursors_with(|map, head, _| {
12920 (movement::next_subword_end(map, head), SelectionGoal::None)
12921 });
12922 })
12923 }
12924
12925 pub fn select_to_next_word_end(
12926 &mut self,
12927 _: &SelectToNextWordEnd,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12932 self.change_selections(Default::default(), window, cx, |s| {
12933 s.move_heads_with(|map, head, _| {
12934 (movement::next_word_end(map, head), SelectionGoal::None)
12935 });
12936 })
12937 }
12938
12939 pub fn select_to_next_subword_end(
12940 &mut self,
12941 _: &SelectToNextSubwordEnd,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946 self.change_selections(Default::default(), window, cx, |s| {
12947 s.move_heads_with(|map, head, _| {
12948 (movement::next_subword_end(map, head), SelectionGoal::None)
12949 });
12950 })
12951 }
12952
12953 pub fn delete_to_next_word_end(
12954 &mut self,
12955 action: &DeleteToNextWordEnd,
12956 window: &mut Window,
12957 cx: &mut Context<Self>,
12958 ) {
12959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12960 self.transact(window, cx, |this, window, cx| {
12961 this.change_selections(Default::default(), window, cx, |s| {
12962 s.move_with(|map, selection| {
12963 if selection.is_empty() {
12964 let cursor = if action.ignore_newlines {
12965 movement::next_word_end(map, selection.head())
12966 } else {
12967 movement::next_word_end_or_newline(map, selection.head())
12968 };
12969 selection.set_head(cursor, SelectionGoal::None);
12970 }
12971 });
12972 });
12973 this.insert("", window, cx);
12974 });
12975 }
12976
12977 pub fn delete_to_next_subword_end(
12978 &mut self,
12979 _: &DeleteToNextSubwordEnd,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12984 self.transact(window, cx, |this, window, cx| {
12985 this.change_selections(Default::default(), window, cx, |s| {
12986 s.move_with(|map, selection| {
12987 if selection.is_empty() {
12988 let cursor = movement::next_subword_end(map, selection.head());
12989 selection.set_head(cursor, SelectionGoal::None);
12990 }
12991 });
12992 });
12993 this.insert("", window, cx);
12994 });
12995 }
12996
12997 pub fn move_to_beginning_of_line(
12998 &mut self,
12999 action: &MoveToBeginningOfLine,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) {
13003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13004 self.change_selections(Default::default(), window, cx, |s| {
13005 s.move_cursors_with(|map, head, _| {
13006 (
13007 movement::indented_line_beginning(
13008 map,
13009 head,
13010 action.stop_at_soft_wraps,
13011 action.stop_at_indent,
13012 ),
13013 SelectionGoal::None,
13014 )
13015 });
13016 })
13017 }
13018
13019 pub fn select_to_beginning_of_line(
13020 &mut self,
13021 action: &SelectToBeginningOfLine,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_heads_with(|map, head, _| {
13028 (
13029 movement::indented_line_beginning(
13030 map,
13031 head,
13032 action.stop_at_soft_wraps,
13033 action.stop_at_indent,
13034 ),
13035 SelectionGoal::None,
13036 )
13037 });
13038 });
13039 }
13040
13041 pub fn delete_to_beginning_of_line(
13042 &mut self,
13043 action: &DeleteToBeginningOfLine,
13044 window: &mut Window,
13045 cx: &mut Context<Self>,
13046 ) {
13047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13048 self.transact(window, cx, |this, window, cx| {
13049 this.change_selections(Default::default(), window, cx, |s| {
13050 s.move_with(|_, selection| {
13051 selection.reversed = true;
13052 });
13053 });
13054
13055 this.select_to_beginning_of_line(
13056 &SelectToBeginningOfLine {
13057 stop_at_soft_wraps: false,
13058 stop_at_indent: action.stop_at_indent,
13059 },
13060 window,
13061 cx,
13062 );
13063 this.backspace(&Backspace, window, cx);
13064 });
13065 }
13066
13067 pub fn move_to_end_of_line(
13068 &mut self,
13069 action: &MoveToEndOfLine,
13070 window: &mut Window,
13071 cx: &mut Context<Self>,
13072 ) {
13073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13074 self.change_selections(Default::default(), window, cx, |s| {
13075 s.move_cursors_with(|map, head, _| {
13076 (
13077 movement::line_end(map, head, action.stop_at_soft_wraps),
13078 SelectionGoal::None,
13079 )
13080 });
13081 })
13082 }
13083
13084 pub fn select_to_end_of_line(
13085 &mut self,
13086 action: &SelectToEndOfLine,
13087 window: &mut Window,
13088 cx: &mut Context<Self>,
13089 ) {
13090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13091 self.change_selections(Default::default(), window, cx, |s| {
13092 s.move_heads_with(|map, head, _| {
13093 (
13094 movement::line_end(map, head, action.stop_at_soft_wraps),
13095 SelectionGoal::None,
13096 )
13097 });
13098 })
13099 }
13100
13101 pub fn delete_to_end_of_line(
13102 &mut self,
13103 _: &DeleteToEndOfLine,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13108 self.transact(window, cx, |this, window, cx| {
13109 this.select_to_end_of_line(
13110 &SelectToEndOfLine {
13111 stop_at_soft_wraps: false,
13112 },
13113 window,
13114 cx,
13115 );
13116 this.delete(&Delete, window, cx);
13117 });
13118 }
13119
13120 pub fn cut_to_end_of_line(
13121 &mut self,
13122 _: &CutToEndOfLine,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13127 self.transact(window, cx, |this, window, cx| {
13128 this.select_to_end_of_line(
13129 &SelectToEndOfLine {
13130 stop_at_soft_wraps: false,
13131 },
13132 window,
13133 cx,
13134 );
13135 this.cut(&Cut, window, cx);
13136 });
13137 }
13138
13139 pub fn move_to_start_of_paragraph(
13140 &mut self,
13141 _: &MoveToStartOfParagraph,
13142 window: &mut Window,
13143 cx: &mut Context<Self>,
13144 ) {
13145 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13146 cx.propagate();
13147 return;
13148 }
13149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13150 self.change_selections(Default::default(), window, cx, |s| {
13151 s.move_with(|map, selection| {
13152 selection.collapse_to(
13153 movement::start_of_paragraph(map, selection.head(), 1),
13154 SelectionGoal::None,
13155 )
13156 });
13157 })
13158 }
13159
13160 pub fn move_to_end_of_paragraph(
13161 &mut self,
13162 _: &MoveToEndOfParagraph,
13163 window: &mut Window,
13164 cx: &mut Context<Self>,
13165 ) {
13166 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13167 cx.propagate();
13168 return;
13169 }
13170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13171 self.change_selections(Default::default(), window, cx, |s| {
13172 s.move_with(|map, selection| {
13173 selection.collapse_to(
13174 movement::end_of_paragraph(map, selection.head(), 1),
13175 SelectionGoal::None,
13176 )
13177 });
13178 })
13179 }
13180
13181 pub fn select_to_start_of_paragraph(
13182 &mut self,
13183 _: &SelectToStartOfParagraph,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13188 cx.propagate();
13189 return;
13190 }
13191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13192 self.change_selections(Default::default(), window, cx, |s| {
13193 s.move_heads_with(|map, head, _| {
13194 (
13195 movement::start_of_paragraph(map, head, 1),
13196 SelectionGoal::None,
13197 )
13198 });
13199 })
13200 }
13201
13202 pub fn select_to_end_of_paragraph(
13203 &mut self,
13204 _: &SelectToEndOfParagraph,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13209 cx.propagate();
13210 return;
13211 }
13212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13213 self.change_selections(Default::default(), window, cx, |s| {
13214 s.move_heads_with(|map, head, _| {
13215 (
13216 movement::end_of_paragraph(map, head, 1),
13217 SelectionGoal::None,
13218 )
13219 });
13220 })
13221 }
13222
13223 pub fn move_to_start_of_excerpt(
13224 &mut self,
13225 _: &MoveToStartOfExcerpt,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) {
13229 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13230 cx.propagate();
13231 return;
13232 }
13233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13234 self.change_selections(Default::default(), window, cx, |s| {
13235 s.move_with(|map, selection| {
13236 selection.collapse_to(
13237 movement::start_of_excerpt(
13238 map,
13239 selection.head(),
13240 workspace::searchable::Direction::Prev,
13241 ),
13242 SelectionGoal::None,
13243 )
13244 });
13245 })
13246 }
13247
13248 pub fn move_to_start_of_next_excerpt(
13249 &mut self,
13250 _: &MoveToStartOfNextExcerpt,
13251 window: &mut Window,
13252 cx: &mut Context<Self>,
13253 ) {
13254 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13255 cx.propagate();
13256 return;
13257 }
13258
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_with(|map, selection| {
13261 selection.collapse_to(
13262 movement::start_of_excerpt(
13263 map,
13264 selection.head(),
13265 workspace::searchable::Direction::Next,
13266 ),
13267 SelectionGoal::None,
13268 )
13269 });
13270 })
13271 }
13272
13273 pub fn move_to_end_of_excerpt(
13274 &mut self,
13275 _: &MoveToEndOfExcerpt,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13280 cx.propagate();
13281 return;
13282 }
13283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13284 self.change_selections(Default::default(), window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 selection.collapse_to(
13287 movement::end_of_excerpt(
13288 map,
13289 selection.head(),
13290 workspace::searchable::Direction::Next,
13291 ),
13292 SelectionGoal::None,
13293 )
13294 });
13295 })
13296 }
13297
13298 pub fn move_to_end_of_previous_excerpt(
13299 &mut self,
13300 _: &MoveToEndOfPreviousExcerpt,
13301 window: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13305 cx.propagate();
13306 return;
13307 }
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_with(|map, selection| {
13311 selection.collapse_to(
13312 movement::end_of_excerpt(
13313 map,
13314 selection.head(),
13315 workspace::searchable::Direction::Prev,
13316 ),
13317 SelectionGoal::None,
13318 )
13319 });
13320 })
13321 }
13322
13323 pub fn select_to_start_of_excerpt(
13324 &mut self,
13325 _: &SelectToStartOfExcerpt,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13330 cx.propagate();
13331 return;
13332 }
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13334 self.change_selections(Default::default(), window, cx, |s| {
13335 s.move_heads_with(|map, head, _| {
13336 (
13337 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13338 SelectionGoal::None,
13339 )
13340 });
13341 })
13342 }
13343
13344 pub fn select_to_start_of_next_excerpt(
13345 &mut self,
13346 _: &SelectToStartOfNextExcerpt,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13351 cx.propagate();
13352 return;
13353 }
13354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13355 self.change_selections(Default::default(), window, cx, |s| {
13356 s.move_heads_with(|map, head, _| {
13357 (
13358 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13359 SelectionGoal::None,
13360 )
13361 });
13362 })
13363 }
13364
13365 pub fn select_to_end_of_excerpt(
13366 &mut self,
13367 _: &SelectToEndOfExcerpt,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13372 cx.propagate();
13373 return;
13374 }
13375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13376 self.change_selections(Default::default(), window, cx, |s| {
13377 s.move_heads_with(|map, head, _| {
13378 (
13379 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13380 SelectionGoal::None,
13381 )
13382 });
13383 })
13384 }
13385
13386 pub fn select_to_end_of_previous_excerpt(
13387 &mut self,
13388 _: &SelectToEndOfPreviousExcerpt,
13389 window: &mut Window,
13390 cx: &mut Context<Self>,
13391 ) {
13392 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13393 cx.propagate();
13394 return;
13395 }
13396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13397 self.change_selections(Default::default(), window, cx, |s| {
13398 s.move_heads_with(|map, head, _| {
13399 (
13400 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13401 SelectionGoal::None,
13402 )
13403 });
13404 })
13405 }
13406
13407 pub fn move_to_beginning(
13408 &mut self,
13409 _: &MoveToBeginning,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13414 cx.propagate();
13415 return;
13416 }
13417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13418 self.change_selections(Default::default(), window, cx, |s| {
13419 s.select_ranges(vec![0..0]);
13420 });
13421 }
13422
13423 pub fn select_to_beginning(
13424 &mut self,
13425 _: &SelectToBeginning,
13426 window: &mut Window,
13427 cx: &mut Context<Self>,
13428 ) {
13429 let mut selection = self.selections.last::<Point>(cx);
13430 selection.set_head(Point::zero(), SelectionGoal::None);
13431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13432 self.change_selections(Default::default(), window, cx, |s| {
13433 s.select(vec![selection]);
13434 });
13435 }
13436
13437 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13438 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13439 cx.propagate();
13440 return;
13441 }
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 let cursor = self.buffer.read(cx).read(cx).len();
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.select_ranges(vec![cursor..cursor])
13446 });
13447 }
13448
13449 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13450 self.nav_history = nav_history;
13451 }
13452
13453 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13454 self.nav_history.as_ref()
13455 }
13456
13457 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13458 self.push_to_nav_history(
13459 self.selections.newest_anchor().head(),
13460 None,
13461 false,
13462 true,
13463 cx,
13464 );
13465 }
13466
13467 fn push_to_nav_history(
13468 &mut self,
13469 cursor_anchor: Anchor,
13470 new_position: Option<Point>,
13471 is_deactivate: bool,
13472 always: bool,
13473 cx: &mut Context<Self>,
13474 ) {
13475 if let Some(nav_history) = self.nav_history.as_mut() {
13476 let buffer = self.buffer.read(cx).read(cx);
13477 let cursor_position = cursor_anchor.to_point(&buffer);
13478 let scroll_state = self.scroll_manager.anchor();
13479 let scroll_top_row = scroll_state.top_row(&buffer);
13480 drop(buffer);
13481
13482 if let Some(new_position) = new_position {
13483 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13484 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13485 return;
13486 }
13487 }
13488
13489 nav_history.push(
13490 Some(NavigationData {
13491 cursor_anchor,
13492 cursor_position,
13493 scroll_anchor: scroll_state,
13494 scroll_top_row,
13495 }),
13496 cx,
13497 );
13498 cx.emit(EditorEvent::PushedToNavHistory {
13499 anchor: cursor_anchor,
13500 is_deactivate,
13501 })
13502 }
13503 }
13504
13505 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507 let buffer = self.buffer.read(cx).snapshot(cx);
13508 let mut selection = self.selections.first::<usize>(cx);
13509 selection.set_head(buffer.len(), SelectionGoal::None);
13510 self.change_selections(Default::default(), window, cx, |s| {
13511 s.select(vec![selection]);
13512 });
13513 }
13514
13515 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13517 let end = self.buffer.read(cx).read(cx).len();
13518 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13519 s.select_ranges(vec![0..end]);
13520 });
13521 }
13522
13523 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13526 let mut selections = self.selections.all::<Point>(cx);
13527 let max_point = display_map.buffer_snapshot.max_point();
13528 for selection in &mut selections {
13529 let rows = selection.spanned_rows(true, &display_map);
13530 selection.start = Point::new(rows.start.0, 0);
13531 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13532 selection.reversed = false;
13533 }
13534 self.change_selections(Default::default(), window, cx, |s| {
13535 s.select(selections);
13536 });
13537 }
13538
13539 pub fn split_selection_into_lines(
13540 &mut self,
13541 _: &SplitSelectionIntoLines,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 let selections = self
13546 .selections
13547 .all::<Point>(cx)
13548 .into_iter()
13549 .map(|selection| selection.start..selection.end)
13550 .collect::<Vec<_>>();
13551 self.unfold_ranges(&selections, true, true, cx);
13552
13553 let mut new_selection_ranges = Vec::new();
13554 {
13555 let buffer = self.buffer.read(cx).read(cx);
13556 for selection in selections {
13557 for row in selection.start.row..selection.end.row {
13558 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13559 new_selection_ranges.push(cursor..cursor);
13560 }
13561
13562 let is_multiline_selection = selection.start.row != selection.end.row;
13563 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13564 // so this action feels more ergonomic when paired with other selection operations
13565 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13566 if !should_skip_last {
13567 new_selection_ranges.push(selection.end..selection.end);
13568 }
13569 }
13570 }
13571 self.change_selections(Default::default(), window, cx, |s| {
13572 s.select_ranges(new_selection_ranges);
13573 });
13574 }
13575
13576 pub fn add_selection_above(
13577 &mut self,
13578 _: &AddSelectionAbove,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 self.add_selection(true, window, cx);
13583 }
13584
13585 pub fn add_selection_below(
13586 &mut self,
13587 _: &AddSelectionBelow,
13588 window: &mut Window,
13589 cx: &mut Context<Self>,
13590 ) {
13591 self.add_selection(false, window, cx);
13592 }
13593
13594 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13596
13597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13598 let all_selections = self.selections.all::<Point>(cx);
13599 let text_layout_details = self.text_layout_details(window);
13600
13601 let (mut columnar_selections, new_selections_to_columnarize) = {
13602 if let Some(state) = self.add_selections_state.as_ref() {
13603 let columnar_selection_ids: HashSet<_> = state
13604 .groups
13605 .iter()
13606 .flat_map(|group| group.stack.iter())
13607 .copied()
13608 .collect();
13609
13610 all_selections
13611 .into_iter()
13612 .partition(|s| columnar_selection_ids.contains(&s.id))
13613 } else {
13614 (Vec::new(), all_selections)
13615 }
13616 };
13617
13618 let mut state = self
13619 .add_selections_state
13620 .take()
13621 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13622
13623 for selection in new_selections_to_columnarize {
13624 let range = selection.display_range(&display_map).sorted();
13625 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13626 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13627 let positions = start_x.min(end_x)..start_x.max(end_x);
13628 let mut stack = Vec::new();
13629 for row in range.start.row().0..=range.end.row().0 {
13630 if let Some(selection) = self.selections.build_columnar_selection(
13631 &display_map,
13632 DisplayRow(row),
13633 &positions,
13634 selection.reversed,
13635 &text_layout_details,
13636 ) {
13637 stack.push(selection.id);
13638 columnar_selections.push(selection);
13639 }
13640 }
13641 if !stack.is_empty() {
13642 if above {
13643 stack.reverse();
13644 }
13645 state.groups.push(AddSelectionsGroup { above, stack });
13646 }
13647 }
13648
13649 let mut final_selections = Vec::new();
13650 let end_row = if above {
13651 DisplayRow(0)
13652 } else {
13653 display_map.max_point().row()
13654 };
13655
13656 let mut last_added_item_per_group = HashMap::default();
13657 for group in state.groups.iter_mut() {
13658 if let Some(last_id) = group.stack.last() {
13659 last_added_item_per_group.insert(*last_id, group);
13660 }
13661 }
13662
13663 for selection in columnar_selections {
13664 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13665 if above == group.above {
13666 let range = selection.display_range(&display_map).sorted();
13667 debug_assert_eq!(range.start.row(), range.end.row());
13668 let mut row = range.start.row();
13669 let positions =
13670 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13671 px(start)..px(end)
13672 } else {
13673 let start_x =
13674 display_map.x_for_display_point(range.start, &text_layout_details);
13675 let end_x =
13676 display_map.x_for_display_point(range.end, &text_layout_details);
13677 start_x.min(end_x)..start_x.max(end_x)
13678 };
13679
13680 let mut maybe_new_selection = None;
13681 while row != end_row {
13682 if above {
13683 row.0 -= 1;
13684 } else {
13685 row.0 += 1;
13686 }
13687 if let Some(new_selection) = self.selections.build_columnar_selection(
13688 &display_map,
13689 row,
13690 &positions,
13691 selection.reversed,
13692 &text_layout_details,
13693 ) {
13694 maybe_new_selection = Some(new_selection);
13695 break;
13696 }
13697 }
13698
13699 if let Some(new_selection) = maybe_new_selection {
13700 group.stack.push(new_selection.id);
13701 if above {
13702 final_selections.push(new_selection);
13703 final_selections.push(selection);
13704 } else {
13705 final_selections.push(selection);
13706 final_selections.push(new_selection);
13707 }
13708 } else {
13709 final_selections.push(selection);
13710 }
13711 } else {
13712 group.stack.pop();
13713 }
13714 } else {
13715 final_selections.push(selection);
13716 }
13717 }
13718
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.select(final_selections);
13721 });
13722
13723 let final_selection_ids: HashSet<_> = self
13724 .selections
13725 .all::<Point>(cx)
13726 .iter()
13727 .map(|s| s.id)
13728 .collect();
13729 state.groups.retain_mut(|group| {
13730 // selections might get merged above so we remove invalid items from stacks
13731 group.stack.retain(|id| final_selection_ids.contains(id));
13732
13733 // single selection in stack can be treated as initial state
13734 group.stack.len() > 1
13735 });
13736
13737 if !state.groups.is_empty() {
13738 self.add_selections_state = Some(state);
13739 }
13740 }
13741
13742 fn select_match_ranges(
13743 &mut self,
13744 range: Range<usize>,
13745 reversed: bool,
13746 replace_newest: bool,
13747 auto_scroll: Option<Autoscroll>,
13748 window: &mut Window,
13749 cx: &mut Context<Editor>,
13750 ) {
13751 self.unfold_ranges(
13752 std::slice::from_ref(&range),
13753 false,
13754 auto_scroll.is_some(),
13755 cx,
13756 );
13757 let effects = if let Some(scroll) = auto_scroll {
13758 SelectionEffects::scroll(scroll)
13759 } else {
13760 SelectionEffects::no_scroll()
13761 };
13762 self.change_selections(effects, window, cx, |s| {
13763 if replace_newest {
13764 s.delete(s.newest_anchor().id);
13765 }
13766 if reversed {
13767 s.insert_range(range.end..range.start);
13768 } else {
13769 s.insert_range(range);
13770 }
13771 });
13772 }
13773
13774 pub fn select_next_match_internal(
13775 &mut self,
13776 display_map: &DisplaySnapshot,
13777 replace_newest: bool,
13778 autoscroll: Option<Autoscroll>,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) -> Result<()> {
13782 let buffer = &display_map.buffer_snapshot;
13783 let mut selections = self.selections.all::<usize>(cx);
13784 if let Some(mut select_next_state) = self.select_next_state.take() {
13785 let query = &select_next_state.query;
13786 if !select_next_state.done {
13787 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13788 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13789 let mut next_selected_range = None;
13790
13791 let bytes_after_last_selection =
13792 buffer.bytes_in_range(last_selection.end..buffer.len());
13793 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13794 let query_matches = query
13795 .stream_find_iter(bytes_after_last_selection)
13796 .map(|result| (last_selection.end, result))
13797 .chain(
13798 query
13799 .stream_find_iter(bytes_before_first_selection)
13800 .map(|result| (0, result)),
13801 );
13802
13803 for (start_offset, query_match) in query_matches {
13804 let query_match = query_match.unwrap(); // can only fail due to I/O
13805 let offset_range =
13806 start_offset + query_match.start()..start_offset + query_match.end();
13807
13808 if !select_next_state.wordwise
13809 || (!buffer.is_inside_word(offset_range.start, false)
13810 && !buffer.is_inside_word(offset_range.end, false))
13811 {
13812 // TODO: This is n^2, because we might check all the selections
13813 if !selections
13814 .iter()
13815 .any(|selection| selection.range().overlaps(&offset_range))
13816 {
13817 next_selected_range = Some(offset_range);
13818 break;
13819 }
13820 }
13821 }
13822
13823 if let Some(next_selected_range) = next_selected_range {
13824 self.select_match_ranges(
13825 next_selected_range,
13826 last_selection.reversed,
13827 replace_newest,
13828 autoscroll,
13829 window,
13830 cx,
13831 );
13832 } else {
13833 select_next_state.done = true;
13834 }
13835 }
13836
13837 self.select_next_state = Some(select_next_state);
13838 } else {
13839 let mut only_carets = true;
13840 let mut same_text_selected = true;
13841 let mut selected_text = None;
13842
13843 let mut selections_iter = selections.iter().peekable();
13844 while let Some(selection) = selections_iter.next() {
13845 if selection.start != selection.end {
13846 only_carets = false;
13847 }
13848
13849 if same_text_selected {
13850 if selected_text.is_none() {
13851 selected_text =
13852 Some(buffer.text_for_range(selection.range()).collect::<String>());
13853 }
13854
13855 if let Some(next_selection) = selections_iter.peek() {
13856 if next_selection.range().len() == selection.range().len() {
13857 let next_selected_text = buffer
13858 .text_for_range(next_selection.range())
13859 .collect::<String>();
13860 if Some(next_selected_text) != selected_text {
13861 same_text_selected = false;
13862 selected_text = None;
13863 }
13864 } else {
13865 same_text_selected = false;
13866 selected_text = None;
13867 }
13868 }
13869 }
13870 }
13871
13872 if only_carets {
13873 for selection in &mut selections {
13874 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13875 selection.start = word_range.start;
13876 selection.end = word_range.end;
13877 selection.goal = SelectionGoal::None;
13878 selection.reversed = false;
13879 self.select_match_ranges(
13880 selection.start..selection.end,
13881 selection.reversed,
13882 replace_newest,
13883 autoscroll,
13884 window,
13885 cx,
13886 );
13887 }
13888
13889 if selections.len() == 1 {
13890 let selection = selections
13891 .last()
13892 .expect("ensured that there's only one selection");
13893 let query = buffer
13894 .text_for_range(selection.start..selection.end)
13895 .collect::<String>();
13896 let is_empty = query.is_empty();
13897 let select_state = SelectNextState {
13898 query: AhoCorasick::new(&[query])?,
13899 wordwise: true,
13900 done: is_empty,
13901 };
13902 self.select_next_state = Some(select_state);
13903 } else {
13904 self.select_next_state = None;
13905 }
13906 } else if let Some(selected_text) = selected_text {
13907 self.select_next_state = Some(SelectNextState {
13908 query: AhoCorasick::new(&[selected_text])?,
13909 wordwise: false,
13910 done: false,
13911 });
13912 self.select_next_match_internal(
13913 display_map,
13914 replace_newest,
13915 autoscroll,
13916 window,
13917 cx,
13918 )?;
13919 }
13920 }
13921 Ok(())
13922 }
13923
13924 pub fn select_all_matches(
13925 &mut self,
13926 _action: &SelectAllMatches,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) -> Result<()> {
13930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13931
13932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13933
13934 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13935 let Some(select_next_state) = self.select_next_state.as_mut() else {
13936 return Ok(());
13937 };
13938 if select_next_state.done {
13939 return Ok(());
13940 }
13941
13942 let mut new_selections = Vec::new();
13943
13944 let reversed = self.selections.oldest::<usize>(cx).reversed;
13945 let buffer = &display_map.buffer_snapshot;
13946 let query_matches = select_next_state
13947 .query
13948 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13949
13950 for query_match in query_matches.into_iter() {
13951 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13952 let offset_range = if reversed {
13953 query_match.end()..query_match.start()
13954 } else {
13955 query_match.start()..query_match.end()
13956 };
13957
13958 if !select_next_state.wordwise
13959 || (!buffer.is_inside_word(offset_range.start, false)
13960 && !buffer.is_inside_word(offset_range.end, false))
13961 {
13962 new_selections.push(offset_range.start..offset_range.end);
13963 }
13964 }
13965
13966 select_next_state.done = true;
13967
13968 if new_selections.is_empty() {
13969 log::error!("bug: new_selections is empty in select_all_matches");
13970 return Ok(());
13971 }
13972
13973 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13974 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13975 selections.select_ranges(new_selections)
13976 });
13977
13978 Ok(())
13979 }
13980
13981 pub fn select_next(
13982 &mut self,
13983 action: &SelectNext,
13984 window: &mut Window,
13985 cx: &mut Context<Self>,
13986 ) -> Result<()> {
13987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13989 self.select_next_match_internal(
13990 &display_map,
13991 action.replace_newest,
13992 Some(Autoscroll::newest()),
13993 window,
13994 cx,
13995 )?;
13996 Ok(())
13997 }
13998
13999 pub fn select_previous(
14000 &mut self,
14001 action: &SelectPrevious,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) -> Result<()> {
14005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14007 let buffer = &display_map.buffer_snapshot;
14008 let mut selections = self.selections.all::<usize>(cx);
14009 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14010 let query = &select_prev_state.query;
14011 if !select_prev_state.done {
14012 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14013 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14014 let mut next_selected_range = None;
14015 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14016 let bytes_before_last_selection =
14017 buffer.reversed_bytes_in_range(0..last_selection.start);
14018 let bytes_after_first_selection =
14019 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14020 let query_matches = query
14021 .stream_find_iter(bytes_before_last_selection)
14022 .map(|result| (last_selection.start, result))
14023 .chain(
14024 query
14025 .stream_find_iter(bytes_after_first_selection)
14026 .map(|result| (buffer.len(), result)),
14027 );
14028 for (end_offset, query_match) in query_matches {
14029 let query_match = query_match.unwrap(); // can only fail due to I/O
14030 let offset_range =
14031 end_offset - query_match.end()..end_offset - query_match.start();
14032
14033 if !select_prev_state.wordwise
14034 || (!buffer.is_inside_word(offset_range.start, false)
14035 && !buffer.is_inside_word(offset_range.end, false))
14036 {
14037 next_selected_range = Some(offset_range);
14038 break;
14039 }
14040 }
14041
14042 if let Some(next_selected_range) = next_selected_range {
14043 self.select_match_ranges(
14044 next_selected_range,
14045 last_selection.reversed,
14046 action.replace_newest,
14047 Some(Autoscroll::newest()),
14048 window,
14049 cx,
14050 );
14051 } else {
14052 select_prev_state.done = true;
14053 }
14054 }
14055
14056 self.select_prev_state = Some(select_prev_state);
14057 } else {
14058 let mut only_carets = true;
14059 let mut same_text_selected = true;
14060 let mut selected_text = None;
14061
14062 let mut selections_iter = selections.iter().peekable();
14063 while let Some(selection) = selections_iter.next() {
14064 if selection.start != selection.end {
14065 only_carets = false;
14066 }
14067
14068 if same_text_selected {
14069 if selected_text.is_none() {
14070 selected_text =
14071 Some(buffer.text_for_range(selection.range()).collect::<String>());
14072 }
14073
14074 if let Some(next_selection) = selections_iter.peek() {
14075 if next_selection.range().len() == selection.range().len() {
14076 let next_selected_text = buffer
14077 .text_for_range(next_selection.range())
14078 .collect::<String>();
14079 if Some(next_selected_text) != selected_text {
14080 same_text_selected = false;
14081 selected_text = None;
14082 }
14083 } else {
14084 same_text_selected = false;
14085 selected_text = None;
14086 }
14087 }
14088 }
14089 }
14090
14091 if only_carets {
14092 for selection in &mut selections {
14093 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14094 selection.start = word_range.start;
14095 selection.end = word_range.end;
14096 selection.goal = SelectionGoal::None;
14097 selection.reversed = false;
14098 self.select_match_ranges(
14099 selection.start..selection.end,
14100 selection.reversed,
14101 action.replace_newest,
14102 Some(Autoscroll::newest()),
14103 window,
14104 cx,
14105 );
14106 }
14107 if selections.len() == 1 {
14108 let selection = selections
14109 .last()
14110 .expect("ensured that there's only one selection");
14111 let query = buffer
14112 .text_for_range(selection.start..selection.end)
14113 .collect::<String>();
14114 let is_empty = query.is_empty();
14115 let select_state = SelectNextState {
14116 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14117 wordwise: true,
14118 done: is_empty,
14119 };
14120 self.select_prev_state = Some(select_state);
14121 } else {
14122 self.select_prev_state = None;
14123 }
14124 } else if let Some(selected_text) = selected_text {
14125 self.select_prev_state = Some(SelectNextState {
14126 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14127 wordwise: false,
14128 done: false,
14129 });
14130 self.select_previous(action, window, cx)?;
14131 }
14132 }
14133 Ok(())
14134 }
14135
14136 pub fn find_next_match(
14137 &mut self,
14138 _: &FindNextMatch,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) -> Result<()> {
14142 let selections = self.selections.disjoint_anchors();
14143 match selections.first() {
14144 Some(first) if selections.len() >= 2 => {
14145 self.change_selections(Default::default(), window, cx, |s| {
14146 s.select_ranges([first.range()]);
14147 });
14148 }
14149 _ => self.select_next(
14150 &SelectNext {
14151 replace_newest: true,
14152 },
14153 window,
14154 cx,
14155 )?,
14156 }
14157 Ok(())
14158 }
14159
14160 pub fn find_previous_match(
14161 &mut self,
14162 _: &FindPreviousMatch,
14163 window: &mut Window,
14164 cx: &mut Context<Self>,
14165 ) -> Result<()> {
14166 let selections = self.selections.disjoint_anchors();
14167 match selections.last() {
14168 Some(last) if selections.len() >= 2 => {
14169 self.change_selections(Default::default(), window, cx, |s| {
14170 s.select_ranges([last.range()]);
14171 });
14172 }
14173 _ => self.select_previous(
14174 &SelectPrevious {
14175 replace_newest: true,
14176 },
14177 window,
14178 cx,
14179 )?,
14180 }
14181 Ok(())
14182 }
14183
14184 pub fn toggle_comments(
14185 &mut self,
14186 action: &ToggleComments,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 if self.read_only(cx) {
14191 return;
14192 }
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14194 let text_layout_details = &self.text_layout_details(window);
14195 self.transact(window, cx, |this, window, cx| {
14196 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14197 let mut edits = Vec::new();
14198 let mut selection_edit_ranges = Vec::new();
14199 let mut last_toggled_row = None;
14200 let snapshot = this.buffer.read(cx).read(cx);
14201 let empty_str: Arc<str> = Arc::default();
14202 let mut suffixes_inserted = Vec::new();
14203 let ignore_indent = action.ignore_indent;
14204
14205 fn comment_prefix_range(
14206 snapshot: &MultiBufferSnapshot,
14207 row: MultiBufferRow,
14208 comment_prefix: &str,
14209 comment_prefix_whitespace: &str,
14210 ignore_indent: bool,
14211 ) -> Range<Point> {
14212 let indent_size = if ignore_indent {
14213 0
14214 } else {
14215 snapshot.indent_size_for_line(row).len
14216 };
14217
14218 let start = Point::new(row.0, indent_size);
14219
14220 let mut line_bytes = snapshot
14221 .bytes_in_range(start..snapshot.max_point())
14222 .flatten()
14223 .copied();
14224
14225 // If this line currently begins with the line comment prefix, then record
14226 // the range containing the prefix.
14227 if line_bytes
14228 .by_ref()
14229 .take(comment_prefix.len())
14230 .eq(comment_prefix.bytes())
14231 {
14232 // Include any whitespace that matches the comment prefix.
14233 let matching_whitespace_len = line_bytes
14234 .zip(comment_prefix_whitespace.bytes())
14235 .take_while(|(a, b)| a == b)
14236 .count() as u32;
14237 let end = Point::new(
14238 start.row,
14239 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14240 );
14241 start..end
14242 } else {
14243 start..start
14244 }
14245 }
14246
14247 fn comment_suffix_range(
14248 snapshot: &MultiBufferSnapshot,
14249 row: MultiBufferRow,
14250 comment_suffix: &str,
14251 comment_suffix_has_leading_space: bool,
14252 ) -> Range<Point> {
14253 let end = Point::new(row.0, snapshot.line_len(row));
14254 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14255
14256 let mut line_end_bytes = snapshot
14257 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14258 .flatten()
14259 .copied();
14260
14261 let leading_space_len = if suffix_start_column > 0
14262 && line_end_bytes.next() == Some(b' ')
14263 && comment_suffix_has_leading_space
14264 {
14265 1
14266 } else {
14267 0
14268 };
14269
14270 // If this line currently begins with the line comment prefix, then record
14271 // the range containing the prefix.
14272 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14273 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14274 start..end
14275 } else {
14276 end..end
14277 }
14278 }
14279
14280 // TODO: Handle selections that cross excerpts
14281 for selection in &mut selections {
14282 let start_column = snapshot
14283 .indent_size_for_line(MultiBufferRow(selection.start.row))
14284 .len;
14285 let language = if let Some(language) =
14286 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14287 {
14288 language
14289 } else {
14290 continue;
14291 };
14292
14293 selection_edit_ranges.clear();
14294
14295 // If multiple selections contain a given row, avoid processing that
14296 // row more than once.
14297 let mut start_row = MultiBufferRow(selection.start.row);
14298 if last_toggled_row == Some(start_row) {
14299 start_row = start_row.next_row();
14300 }
14301 let end_row =
14302 if selection.end.row > selection.start.row && selection.end.column == 0 {
14303 MultiBufferRow(selection.end.row - 1)
14304 } else {
14305 MultiBufferRow(selection.end.row)
14306 };
14307 last_toggled_row = Some(end_row);
14308
14309 if start_row > end_row {
14310 continue;
14311 }
14312
14313 // If the language has line comments, toggle those.
14314 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14315
14316 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14317 if ignore_indent {
14318 full_comment_prefixes = full_comment_prefixes
14319 .into_iter()
14320 .map(|s| Arc::from(s.trim_end()))
14321 .collect();
14322 }
14323
14324 if !full_comment_prefixes.is_empty() {
14325 let first_prefix = full_comment_prefixes
14326 .first()
14327 .expect("prefixes is non-empty");
14328 let prefix_trimmed_lengths = full_comment_prefixes
14329 .iter()
14330 .map(|p| p.trim_end_matches(' ').len())
14331 .collect::<SmallVec<[usize; 4]>>();
14332
14333 let mut all_selection_lines_are_comments = true;
14334
14335 for row in start_row.0..=end_row.0 {
14336 let row = MultiBufferRow(row);
14337 if start_row < end_row && snapshot.is_line_blank(row) {
14338 continue;
14339 }
14340
14341 let prefix_range = full_comment_prefixes
14342 .iter()
14343 .zip(prefix_trimmed_lengths.iter().copied())
14344 .map(|(prefix, trimmed_prefix_len)| {
14345 comment_prefix_range(
14346 snapshot.deref(),
14347 row,
14348 &prefix[..trimmed_prefix_len],
14349 &prefix[trimmed_prefix_len..],
14350 ignore_indent,
14351 )
14352 })
14353 .max_by_key(|range| range.end.column - range.start.column)
14354 .expect("prefixes is non-empty");
14355
14356 if prefix_range.is_empty() {
14357 all_selection_lines_are_comments = false;
14358 }
14359
14360 selection_edit_ranges.push(prefix_range);
14361 }
14362
14363 if all_selection_lines_are_comments {
14364 edits.extend(
14365 selection_edit_ranges
14366 .iter()
14367 .cloned()
14368 .map(|range| (range, empty_str.clone())),
14369 );
14370 } else {
14371 let min_column = selection_edit_ranges
14372 .iter()
14373 .map(|range| range.start.column)
14374 .min()
14375 .unwrap_or(0);
14376 edits.extend(selection_edit_ranges.iter().map(|range| {
14377 let position = Point::new(range.start.row, min_column);
14378 (position..position, first_prefix.clone())
14379 }));
14380 }
14381 } else if let Some(BlockCommentConfig {
14382 start: full_comment_prefix,
14383 end: comment_suffix,
14384 ..
14385 }) = language.block_comment()
14386 {
14387 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14388 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14389 let prefix_range = comment_prefix_range(
14390 snapshot.deref(),
14391 start_row,
14392 comment_prefix,
14393 comment_prefix_whitespace,
14394 ignore_indent,
14395 );
14396 let suffix_range = comment_suffix_range(
14397 snapshot.deref(),
14398 end_row,
14399 comment_suffix.trim_start_matches(' '),
14400 comment_suffix.starts_with(' '),
14401 );
14402
14403 if prefix_range.is_empty() || suffix_range.is_empty() {
14404 edits.push((
14405 prefix_range.start..prefix_range.start,
14406 full_comment_prefix.clone(),
14407 ));
14408 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14409 suffixes_inserted.push((end_row, comment_suffix.len()));
14410 } else {
14411 edits.push((prefix_range, empty_str.clone()));
14412 edits.push((suffix_range, empty_str.clone()));
14413 }
14414 } else {
14415 continue;
14416 }
14417 }
14418
14419 drop(snapshot);
14420 this.buffer.update(cx, |buffer, cx| {
14421 buffer.edit(edits, None, cx);
14422 });
14423
14424 // Adjust selections so that they end before any comment suffixes that
14425 // were inserted.
14426 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14427 let mut selections = this.selections.all::<Point>(cx);
14428 let snapshot = this.buffer.read(cx).read(cx);
14429 for selection in &mut selections {
14430 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14431 match row.cmp(&MultiBufferRow(selection.end.row)) {
14432 Ordering::Less => {
14433 suffixes_inserted.next();
14434 continue;
14435 }
14436 Ordering::Greater => break,
14437 Ordering::Equal => {
14438 if selection.end.column == snapshot.line_len(row) {
14439 if selection.is_empty() {
14440 selection.start.column -= suffix_len as u32;
14441 }
14442 selection.end.column -= suffix_len as u32;
14443 }
14444 break;
14445 }
14446 }
14447 }
14448 }
14449
14450 drop(snapshot);
14451 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14452
14453 let selections = this.selections.all::<Point>(cx);
14454 let selections_on_single_row = selections.windows(2).all(|selections| {
14455 selections[0].start.row == selections[1].start.row
14456 && selections[0].end.row == selections[1].end.row
14457 && selections[0].start.row == selections[0].end.row
14458 });
14459 let selections_selecting = selections
14460 .iter()
14461 .any(|selection| selection.start != selection.end);
14462 let advance_downwards = action.advance_downwards
14463 && selections_on_single_row
14464 && !selections_selecting
14465 && !matches!(this.mode, EditorMode::SingleLine { .. });
14466
14467 if advance_downwards {
14468 let snapshot = this.buffer.read(cx).snapshot(cx);
14469
14470 this.change_selections(Default::default(), window, cx, |s| {
14471 s.move_cursors_with(|display_snapshot, display_point, _| {
14472 let mut point = display_point.to_point(display_snapshot);
14473 point.row += 1;
14474 point = snapshot.clip_point(point, Bias::Left);
14475 let display_point = point.to_display_point(display_snapshot);
14476 let goal = SelectionGoal::HorizontalPosition(
14477 display_snapshot
14478 .x_for_display_point(display_point, text_layout_details)
14479 .into(),
14480 );
14481 (display_point, goal)
14482 })
14483 });
14484 }
14485 });
14486 }
14487
14488 pub fn select_enclosing_symbol(
14489 &mut self,
14490 _: &SelectEnclosingSymbol,
14491 window: &mut Window,
14492 cx: &mut Context<Self>,
14493 ) {
14494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14495
14496 let buffer = self.buffer.read(cx).snapshot(cx);
14497 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14498
14499 fn update_selection(
14500 selection: &Selection<usize>,
14501 buffer_snap: &MultiBufferSnapshot,
14502 ) -> Option<Selection<usize>> {
14503 let cursor = selection.head();
14504 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14505 for symbol in symbols.iter().rev() {
14506 let start = symbol.range.start.to_offset(buffer_snap);
14507 let end = symbol.range.end.to_offset(buffer_snap);
14508 let new_range = start..end;
14509 if start < selection.start || end > selection.end {
14510 return Some(Selection {
14511 id: selection.id,
14512 start: new_range.start,
14513 end: new_range.end,
14514 goal: SelectionGoal::None,
14515 reversed: selection.reversed,
14516 });
14517 }
14518 }
14519 None
14520 }
14521
14522 let mut selected_larger_symbol = false;
14523 let new_selections = old_selections
14524 .iter()
14525 .map(|selection| match update_selection(selection, &buffer) {
14526 Some(new_selection) => {
14527 if new_selection.range() != selection.range() {
14528 selected_larger_symbol = true;
14529 }
14530 new_selection
14531 }
14532 None => selection.clone(),
14533 })
14534 .collect::<Vec<_>>();
14535
14536 if selected_larger_symbol {
14537 self.change_selections(Default::default(), window, cx, |s| {
14538 s.select(new_selections);
14539 });
14540 }
14541 }
14542
14543 pub fn select_larger_syntax_node(
14544 &mut self,
14545 _: &SelectLargerSyntaxNode,
14546 window: &mut Window,
14547 cx: &mut Context<Self>,
14548 ) {
14549 let Some(visible_row_count) = self.visible_row_count() else {
14550 return;
14551 };
14552 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14553 if old_selections.is_empty() {
14554 return;
14555 }
14556
14557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14558
14559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14560 let buffer = self.buffer.read(cx).snapshot(cx);
14561
14562 let mut selected_larger_node = false;
14563 let mut new_selections = old_selections
14564 .iter()
14565 .map(|selection| {
14566 let old_range = selection.start..selection.end;
14567
14568 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14569 // manually select word at selection
14570 if ["string_content", "inline"].contains(&node.kind()) {
14571 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14572 // ignore if word is already selected
14573 if !word_range.is_empty() && old_range != word_range {
14574 let (last_word_range, _) =
14575 buffer.surrounding_word(old_range.end, false);
14576 // only select word if start and end point belongs to same word
14577 if word_range == last_word_range {
14578 selected_larger_node = true;
14579 return Selection {
14580 id: selection.id,
14581 start: word_range.start,
14582 end: word_range.end,
14583 goal: SelectionGoal::None,
14584 reversed: selection.reversed,
14585 };
14586 }
14587 }
14588 }
14589 }
14590
14591 let mut new_range = old_range.clone();
14592 while let Some((_node, containing_range)) =
14593 buffer.syntax_ancestor(new_range.clone())
14594 {
14595 new_range = match containing_range {
14596 MultiOrSingleBufferOffsetRange::Single(_) => break,
14597 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14598 };
14599 if !display_map.intersects_fold(new_range.start)
14600 && !display_map.intersects_fold(new_range.end)
14601 {
14602 break;
14603 }
14604 }
14605
14606 selected_larger_node |= new_range != old_range;
14607 Selection {
14608 id: selection.id,
14609 start: new_range.start,
14610 end: new_range.end,
14611 goal: SelectionGoal::None,
14612 reversed: selection.reversed,
14613 }
14614 })
14615 .collect::<Vec<_>>();
14616
14617 if !selected_larger_node {
14618 return; // don't put this call in the history
14619 }
14620
14621 // scroll based on transformation done to the last selection created by the user
14622 let (last_old, last_new) = old_selections
14623 .last()
14624 .zip(new_selections.last().cloned())
14625 .expect("old_selections isn't empty");
14626
14627 // revert selection
14628 let is_selection_reversed = {
14629 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14630 new_selections.last_mut().expect("checked above").reversed =
14631 should_newest_selection_be_reversed;
14632 should_newest_selection_be_reversed
14633 };
14634
14635 if selected_larger_node {
14636 self.select_syntax_node_history.disable_clearing = true;
14637 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14638 s.select(new_selections.clone());
14639 });
14640 self.select_syntax_node_history.disable_clearing = false;
14641 }
14642
14643 let start_row = last_new.start.to_display_point(&display_map).row().0;
14644 let end_row = last_new.end.to_display_point(&display_map).row().0;
14645 let selection_height = end_row - start_row + 1;
14646 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14647
14648 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14649 let scroll_behavior = if fits_on_the_screen {
14650 self.request_autoscroll(Autoscroll::fit(), cx);
14651 SelectSyntaxNodeScrollBehavior::FitSelection
14652 } else if is_selection_reversed {
14653 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14654 SelectSyntaxNodeScrollBehavior::CursorTop
14655 } else {
14656 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14657 SelectSyntaxNodeScrollBehavior::CursorBottom
14658 };
14659
14660 self.select_syntax_node_history.push((
14661 old_selections,
14662 scroll_behavior,
14663 is_selection_reversed,
14664 ));
14665 }
14666
14667 pub fn select_smaller_syntax_node(
14668 &mut self,
14669 _: &SelectSmallerSyntaxNode,
14670 window: &mut Window,
14671 cx: &mut Context<Self>,
14672 ) {
14673 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14674
14675 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14676 self.select_syntax_node_history.pop()
14677 {
14678 if let Some(selection) = selections.last_mut() {
14679 selection.reversed = is_selection_reversed;
14680 }
14681
14682 self.select_syntax_node_history.disable_clearing = true;
14683 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14684 s.select(selections.to_vec());
14685 });
14686 self.select_syntax_node_history.disable_clearing = false;
14687
14688 match scroll_behavior {
14689 SelectSyntaxNodeScrollBehavior::CursorTop => {
14690 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14691 }
14692 SelectSyntaxNodeScrollBehavior::FitSelection => {
14693 self.request_autoscroll(Autoscroll::fit(), cx);
14694 }
14695 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14696 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14697 }
14698 }
14699 }
14700 }
14701
14702 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14703 if !EditorSettings::get_global(cx).gutter.runnables {
14704 self.clear_tasks();
14705 return Task::ready(());
14706 }
14707 let project = self.project.as_ref().map(Entity::downgrade);
14708 let task_sources = self.lsp_task_sources(cx);
14709 let multi_buffer = self.buffer.downgrade();
14710 cx.spawn_in(window, async move |editor, cx| {
14711 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14712 let Some(project) = project.and_then(|p| p.upgrade()) else {
14713 return;
14714 };
14715 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14716 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14717 }) else {
14718 return;
14719 };
14720
14721 let hide_runnables = project
14722 .update(cx, |project, cx| {
14723 // Do not display any test indicators in non-dev server remote projects.
14724 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14725 })
14726 .unwrap_or(true);
14727 if hide_runnables {
14728 return;
14729 }
14730 let new_rows =
14731 cx.background_spawn({
14732 let snapshot = display_snapshot.clone();
14733 async move {
14734 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14735 }
14736 })
14737 .await;
14738 let Ok(lsp_tasks) =
14739 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14740 else {
14741 return;
14742 };
14743 let lsp_tasks = lsp_tasks.await;
14744
14745 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14746 lsp_tasks
14747 .into_iter()
14748 .flat_map(|(kind, tasks)| {
14749 tasks.into_iter().filter_map(move |(location, task)| {
14750 Some((kind.clone(), location?, task))
14751 })
14752 })
14753 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14754 let buffer = location.target.buffer;
14755 let buffer_snapshot = buffer.read(cx).snapshot();
14756 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14757 |(excerpt_id, snapshot, _)| {
14758 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14759 display_snapshot
14760 .buffer_snapshot
14761 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14762 } else {
14763 None
14764 }
14765 },
14766 );
14767 if let Some(offset) = offset {
14768 let task_buffer_range =
14769 location.target.range.to_point(&buffer_snapshot);
14770 let context_buffer_range =
14771 task_buffer_range.to_offset(&buffer_snapshot);
14772 let context_range = BufferOffset(context_buffer_range.start)
14773 ..BufferOffset(context_buffer_range.end);
14774
14775 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14776 .or_insert_with(|| RunnableTasks {
14777 templates: Vec::new(),
14778 offset,
14779 column: task_buffer_range.start.column,
14780 extra_variables: HashMap::default(),
14781 context_range,
14782 })
14783 .templates
14784 .push((kind, task.original_task().clone()));
14785 }
14786
14787 acc
14788 })
14789 }) else {
14790 return;
14791 };
14792
14793 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14794 buffer.language_settings(cx).tasks.prefer_lsp
14795 }) else {
14796 return;
14797 };
14798
14799 let rows = Self::runnable_rows(
14800 project,
14801 display_snapshot,
14802 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14803 new_rows,
14804 cx.clone(),
14805 )
14806 .await;
14807 editor
14808 .update(cx, |editor, _| {
14809 editor.clear_tasks();
14810 for (key, mut value) in rows {
14811 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14812 value.templates.extend(lsp_tasks.templates);
14813 }
14814
14815 editor.insert_tasks(key, value);
14816 }
14817 for (key, value) in lsp_tasks_by_rows {
14818 editor.insert_tasks(key, value);
14819 }
14820 })
14821 .ok();
14822 })
14823 }
14824 fn fetch_runnable_ranges(
14825 snapshot: &DisplaySnapshot,
14826 range: Range<Anchor>,
14827 ) -> Vec<language::RunnableRange> {
14828 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14829 }
14830
14831 fn runnable_rows(
14832 project: Entity<Project>,
14833 snapshot: DisplaySnapshot,
14834 prefer_lsp: bool,
14835 runnable_ranges: Vec<RunnableRange>,
14836 cx: AsyncWindowContext,
14837 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14838 cx.spawn(async move |cx| {
14839 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14840 for mut runnable in runnable_ranges {
14841 let Some(tasks) = cx
14842 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14843 .ok()
14844 else {
14845 continue;
14846 };
14847 let mut tasks = tasks.await;
14848
14849 if prefer_lsp {
14850 tasks.retain(|(task_kind, _)| {
14851 !matches!(task_kind, TaskSourceKind::Language { .. })
14852 });
14853 }
14854 if tasks.is_empty() {
14855 continue;
14856 }
14857
14858 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14859 let Some(row) = snapshot
14860 .buffer_snapshot
14861 .buffer_line_for_row(MultiBufferRow(point.row))
14862 .map(|(_, range)| range.start.row)
14863 else {
14864 continue;
14865 };
14866
14867 let context_range =
14868 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14869 runnable_rows.push((
14870 (runnable.buffer_id, row),
14871 RunnableTasks {
14872 templates: tasks,
14873 offset: snapshot
14874 .buffer_snapshot
14875 .anchor_before(runnable.run_range.start),
14876 context_range,
14877 column: point.column,
14878 extra_variables: runnable.extra_captures,
14879 },
14880 ));
14881 }
14882 runnable_rows
14883 })
14884 }
14885
14886 fn templates_with_tags(
14887 project: &Entity<Project>,
14888 runnable: &mut Runnable,
14889 cx: &mut App,
14890 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14891 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14892 let (worktree_id, file) = project
14893 .buffer_for_id(runnable.buffer, cx)
14894 .and_then(|buffer| buffer.read(cx).file())
14895 .map(|file| (file.worktree_id(cx), file.clone()))
14896 .unzip();
14897
14898 (
14899 project.task_store().read(cx).task_inventory().cloned(),
14900 worktree_id,
14901 file,
14902 )
14903 });
14904
14905 let tags = mem::take(&mut runnable.tags);
14906 let language = runnable.language.clone();
14907 cx.spawn(async move |cx| {
14908 let mut templates_with_tags = Vec::new();
14909 if let Some(inventory) = inventory {
14910 for RunnableTag(tag) in tags {
14911 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14912 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14913 }) else {
14914 return templates_with_tags;
14915 };
14916 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14917 move |(_, template)| {
14918 template.tags.iter().any(|source_tag| source_tag == &tag)
14919 },
14920 ));
14921 }
14922 }
14923 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14924
14925 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14926 // Strongest source wins; if we have worktree tag binding, prefer that to
14927 // global and language bindings;
14928 // if we have a global binding, prefer that to language binding.
14929 let first_mismatch = templates_with_tags
14930 .iter()
14931 .position(|(tag_source, _)| tag_source != leading_tag_source);
14932 if let Some(index) = first_mismatch {
14933 templates_with_tags.truncate(index);
14934 }
14935 }
14936
14937 templates_with_tags
14938 })
14939 }
14940
14941 pub fn move_to_enclosing_bracket(
14942 &mut self,
14943 _: &MoveToEnclosingBracket,
14944 window: &mut Window,
14945 cx: &mut Context<Self>,
14946 ) {
14947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14948 self.change_selections(Default::default(), window, cx, |s| {
14949 s.move_offsets_with(|snapshot, selection| {
14950 let Some(enclosing_bracket_ranges) =
14951 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14952 else {
14953 return;
14954 };
14955
14956 let mut best_length = usize::MAX;
14957 let mut best_inside = false;
14958 let mut best_in_bracket_range = false;
14959 let mut best_destination = None;
14960 for (open, close) in enclosing_bracket_ranges {
14961 let close = close.to_inclusive();
14962 let length = close.end() - open.start;
14963 let inside = selection.start >= open.end && selection.end <= *close.start();
14964 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14965 || close.contains(&selection.head());
14966
14967 // If best is next to a bracket and current isn't, skip
14968 if !in_bracket_range && best_in_bracket_range {
14969 continue;
14970 }
14971
14972 // Prefer smaller lengths unless best is inside and current isn't
14973 if length > best_length && (best_inside || !inside) {
14974 continue;
14975 }
14976
14977 best_length = length;
14978 best_inside = inside;
14979 best_in_bracket_range = in_bracket_range;
14980 best_destination = Some(
14981 if close.contains(&selection.start) && close.contains(&selection.end) {
14982 if inside { open.end } else { open.start }
14983 } else if inside {
14984 *close.start()
14985 } else {
14986 *close.end()
14987 },
14988 );
14989 }
14990
14991 if let Some(destination) = best_destination {
14992 selection.collapse_to(destination, SelectionGoal::None);
14993 }
14994 })
14995 });
14996 }
14997
14998 pub fn undo_selection(
14999 &mut self,
15000 _: &UndoSelection,
15001 window: &mut Window,
15002 cx: &mut Context<Self>,
15003 ) {
15004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15005 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15006 self.selection_history.mode = SelectionHistoryMode::Undoing;
15007 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15008 this.end_selection(window, cx);
15009 this.change_selections(
15010 SelectionEffects::scroll(Autoscroll::newest()),
15011 window,
15012 cx,
15013 |s| s.select_anchors(entry.selections.to_vec()),
15014 );
15015 });
15016 self.selection_history.mode = SelectionHistoryMode::Normal;
15017
15018 self.select_next_state = entry.select_next_state;
15019 self.select_prev_state = entry.select_prev_state;
15020 self.add_selections_state = entry.add_selections_state;
15021 }
15022 }
15023
15024 pub fn redo_selection(
15025 &mut self,
15026 _: &RedoSelection,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15032 self.selection_history.mode = SelectionHistoryMode::Redoing;
15033 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15034 this.end_selection(window, cx);
15035 this.change_selections(
15036 SelectionEffects::scroll(Autoscroll::newest()),
15037 window,
15038 cx,
15039 |s| s.select_anchors(entry.selections.to_vec()),
15040 );
15041 });
15042 self.selection_history.mode = SelectionHistoryMode::Normal;
15043
15044 self.select_next_state = entry.select_next_state;
15045 self.select_prev_state = entry.select_prev_state;
15046 self.add_selections_state = entry.add_selections_state;
15047 }
15048 }
15049
15050 pub fn expand_excerpts(
15051 &mut self,
15052 action: &ExpandExcerpts,
15053 _: &mut Window,
15054 cx: &mut Context<Self>,
15055 ) {
15056 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15057 }
15058
15059 pub fn expand_excerpts_down(
15060 &mut self,
15061 action: &ExpandExcerptsDown,
15062 _: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15066 }
15067
15068 pub fn expand_excerpts_up(
15069 &mut self,
15070 action: &ExpandExcerptsUp,
15071 _: &mut Window,
15072 cx: &mut Context<Self>,
15073 ) {
15074 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15075 }
15076
15077 pub fn expand_excerpts_for_direction(
15078 &mut self,
15079 lines: u32,
15080 direction: ExpandExcerptDirection,
15081
15082 cx: &mut Context<Self>,
15083 ) {
15084 let selections = self.selections.disjoint_anchors();
15085
15086 let lines = if lines == 0 {
15087 EditorSettings::get_global(cx).expand_excerpt_lines
15088 } else {
15089 lines
15090 };
15091
15092 self.buffer.update(cx, |buffer, cx| {
15093 let snapshot = buffer.snapshot(cx);
15094 let mut excerpt_ids = selections
15095 .iter()
15096 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15097 .collect::<Vec<_>>();
15098 excerpt_ids.sort();
15099 excerpt_ids.dedup();
15100 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15101 })
15102 }
15103
15104 pub fn expand_excerpt(
15105 &mut self,
15106 excerpt: ExcerptId,
15107 direction: ExpandExcerptDirection,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) {
15111 let current_scroll_position = self.scroll_position(cx);
15112 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15113 let mut should_scroll_up = false;
15114
15115 if direction == ExpandExcerptDirection::Down {
15116 let multi_buffer = self.buffer.read(cx);
15117 let snapshot = multi_buffer.snapshot(cx);
15118 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15119 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15120 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15121 let buffer_snapshot = buffer.read(cx).snapshot();
15122 let excerpt_end_row =
15123 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15124 let last_row = buffer_snapshot.max_point().row;
15125 let lines_below = last_row.saturating_sub(excerpt_end_row);
15126 should_scroll_up = lines_below >= lines_to_expand;
15127 }
15128 }
15129 }
15130 }
15131
15132 self.buffer.update(cx, |buffer, cx| {
15133 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15134 });
15135
15136 if should_scroll_up {
15137 let new_scroll_position =
15138 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15139 self.set_scroll_position(new_scroll_position, window, cx);
15140 }
15141 }
15142
15143 pub fn go_to_singleton_buffer_point(
15144 &mut self,
15145 point: Point,
15146 window: &mut Window,
15147 cx: &mut Context<Self>,
15148 ) {
15149 self.go_to_singleton_buffer_range(point..point, window, cx);
15150 }
15151
15152 pub fn go_to_singleton_buffer_range(
15153 &mut self,
15154 range: Range<Point>,
15155 window: &mut Window,
15156 cx: &mut Context<Self>,
15157 ) {
15158 let multibuffer = self.buffer().read(cx);
15159 let Some(buffer) = multibuffer.as_singleton() else {
15160 return;
15161 };
15162 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15163 return;
15164 };
15165 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15166 return;
15167 };
15168 self.change_selections(
15169 SelectionEffects::default().nav_history(true),
15170 window,
15171 cx,
15172 |s| s.select_anchor_ranges([start..end]),
15173 );
15174 }
15175
15176 pub fn go_to_diagnostic(
15177 &mut self,
15178 action: &GoToDiagnostic,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) {
15182 if !self.diagnostics_enabled() {
15183 return;
15184 }
15185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15186 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15187 }
15188
15189 pub fn go_to_prev_diagnostic(
15190 &mut self,
15191 action: &GoToPreviousDiagnostic,
15192 window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) {
15195 if !self.diagnostics_enabled() {
15196 return;
15197 }
15198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15199 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15200 }
15201
15202 pub fn go_to_diagnostic_impl(
15203 &mut self,
15204 direction: Direction,
15205 severity: GoToDiagnosticSeverityFilter,
15206 window: &mut Window,
15207 cx: &mut Context<Self>,
15208 ) {
15209 let buffer = self.buffer.read(cx).snapshot(cx);
15210 let selection = self.selections.newest::<usize>(cx);
15211
15212 let mut active_group_id = None;
15213 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15214 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15215 active_group_id = Some(active_group.group_id);
15216 }
15217 }
15218
15219 fn filtered(
15220 snapshot: EditorSnapshot,
15221 severity: GoToDiagnosticSeverityFilter,
15222 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15223 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15224 diagnostics
15225 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15226 .filter(|entry| entry.range.start != entry.range.end)
15227 .filter(|entry| !entry.diagnostic.is_unnecessary)
15228 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15229 }
15230
15231 let snapshot = self.snapshot(window, cx);
15232 let before = filtered(
15233 snapshot.clone(),
15234 severity,
15235 buffer
15236 .diagnostics_in_range(0..selection.start)
15237 .filter(|entry| entry.range.start <= selection.start),
15238 );
15239 let after = filtered(
15240 snapshot,
15241 severity,
15242 buffer
15243 .diagnostics_in_range(selection.start..buffer.len())
15244 .filter(|entry| entry.range.start >= selection.start),
15245 );
15246
15247 let mut found: Option<DiagnosticEntry<usize>> = None;
15248 if direction == Direction::Prev {
15249 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15250 {
15251 for diagnostic in prev_diagnostics.into_iter().rev() {
15252 if diagnostic.range.start != selection.start
15253 || active_group_id
15254 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15255 {
15256 found = Some(diagnostic);
15257 break 'outer;
15258 }
15259 }
15260 }
15261 } else {
15262 for diagnostic in after.chain(before) {
15263 if diagnostic.range.start != selection.start
15264 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15265 {
15266 found = Some(diagnostic);
15267 break;
15268 }
15269 }
15270 }
15271 let Some(next_diagnostic) = found else {
15272 return;
15273 };
15274
15275 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15276 return;
15277 };
15278 self.change_selections(Default::default(), window, cx, |s| {
15279 s.select_ranges(vec![
15280 next_diagnostic.range.start..next_diagnostic.range.start,
15281 ])
15282 });
15283 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15284 self.refresh_inline_completion(false, true, window, cx);
15285 }
15286
15287 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15289 let snapshot = self.snapshot(window, cx);
15290 let selection = self.selections.newest::<Point>(cx);
15291 self.go_to_hunk_before_or_after_position(
15292 &snapshot,
15293 selection.head(),
15294 Direction::Next,
15295 window,
15296 cx,
15297 );
15298 }
15299
15300 pub fn go_to_hunk_before_or_after_position(
15301 &mut self,
15302 snapshot: &EditorSnapshot,
15303 position: Point,
15304 direction: Direction,
15305 window: &mut Window,
15306 cx: &mut Context<Editor>,
15307 ) {
15308 let row = if direction == Direction::Next {
15309 self.hunk_after_position(snapshot, position)
15310 .map(|hunk| hunk.row_range.start)
15311 } else {
15312 self.hunk_before_position(snapshot, position)
15313 };
15314
15315 if let Some(row) = row {
15316 let destination = Point::new(row.0, 0);
15317 let autoscroll = Autoscroll::center();
15318
15319 self.unfold_ranges(&[destination..destination], false, false, cx);
15320 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15321 s.select_ranges([destination..destination]);
15322 });
15323 }
15324 }
15325
15326 fn hunk_after_position(
15327 &mut self,
15328 snapshot: &EditorSnapshot,
15329 position: Point,
15330 ) -> Option<MultiBufferDiffHunk> {
15331 snapshot
15332 .buffer_snapshot
15333 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15334 .find(|hunk| hunk.row_range.start.0 > position.row)
15335 .or_else(|| {
15336 snapshot
15337 .buffer_snapshot
15338 .diff_hunks_in_range(Point::zero()..position)
15339 .find(|hunk| hunk.row_range.end.0 < position.row)
15340 })
15341 }
15342
15343 fn go_to_prev_hunk(
15344 &mut self,
15345 _: &GoToPreviousHunk,
15346 window: &mut Window,
15347 cx: &mut Context<Self>,
15348 ) {
15349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15350 let snapshot = self.snapshot(window, cx);
15351 let selection = self.selections.newest::<Point>(cx);
15352 self.go_to_hunk_before_or_after_position(
15353 &snapshot,
15354 selection.head(),
15355 Direction::Prev,
15356 window,
15357 cx,
15358 );
15359 }
15360
15361 fn hunk_before_position(
15362 &mut self,
15363 snapshot: &EditorSnapshot,
15364 position: Point,
15365 ) -> Option<MultiBufferRow> {
15366 snapshot
15367 .buffer_snapshot
15368 .diff_hunk_before(position)
15369 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15370 }
15371
15372 fn go_to_next_change(
15373 &mut self,
15374 _: &GoToNextChange,
15375 window: &mut Window,
15376 cx: &mut Context<Self>,
15377 ) {
15378 if let Some(selections) = self
15379 .change_list
15380 .next_change(1, Direction::Next)
15381 .map(|s| s.to_vec())
15382 {
15383 self.change_selections(Default::default(), window, cx, |s| {
15384 let map = s.display_map();
15385 s.select_display_ranges(selections.iter().map(|a| {
15386 let point = a.to_display_point(&map);
15387 point..point
15388 }))
15389 })
15390 }
15391 }
15392
15393 fn go_to_previous_change(
15394 &mut self,
15395 _: &GoToPreviousChange,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) {
15399 if let Some(selections) = self
15400 .change_list
15401 .next_change(1, Direction::Prev)
15402 .map(|s| s.to_vec())
15403 {
15404 self.change_selections(Default::default(), window, cx, |s| {
15405 let map = s.display_map();
15406 s.select_display_ranges(selections.iter().map(|a| {
15407 let point = a.to_display_point(&map);
15408 point..point
15409 }))
15410 })
15411 }
15412 }
15413
15414 fn go_to_line<T: 'static>(
15415 &mut self,
15416 position: Anchor,
15417 highlight_color: Option<Hsla>,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) {
15421 let snapshot = self.snapshot(window, cx).display_snapshot;
15422 let position = position.to_point(&snapshot.buffer_snapshot);
15423 let start = snapshot
15424 .buffer_snapshot
15425 .clip_point(Point::new(position.row, 0), Bias::Left);
15426 let end = start + Point::new(1, 0);
15427 let start = snapshot.buffer_snapshot.anchor_before(start);
15428 let end = snapshot.buffer_snapshot.anchor_before(end);
15429
15430 self.highlight_rows::<T>(
15431 start..end,
15432 highlight_color
15433 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15434 Default::default(),
15435 cx,
15436 );
15437
15438 if self.buffer.read(cx).is_singleton() {
15439 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15440 }
15441 }
15442
15443 pub fn go_to_definition(
15444 &mut self,
15445 _: &GoToDefinition,
15446 window: &mut Window,
15447 cx: &mut Context<Self>,
15448 ) -> Task<Result<Navigated>> {
15449 let definition =
15450 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15451 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15452 cx.spawn_in(window, async move |editor, cx| {
15453 if definition.await? == Navigated::Yes {
15454 return Ok(Navigated::Yes);
15455 }
15456 match fallback_strategy {
15457 GoToDefinitionFallback::None => Ok(Navigated::No),
15458 GoToDefinitionFallback::FindAllReferences => {
15459 match editor.update_in(cx, |editor, window, cx| {
15460 editor.find_all_references(&FindAllReferences, window, cx)
15461 })? {
15462 Some(references) => references.await,
15463 None => Ok(Navigated::No),
15464 }
15465 }
15466 }
15467 })
15468 }
15469
15470 pub fn go_to_declaration(
15471 &mut self,
15472 _: &GoToDeclaration,
15473 window: &mut Window,
15474 cx: &mut Context<Self>,
15475 ) -> Task<Result<Navigated>> {
15476 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15477 }
15478
15479 pub fn go_to_declaration_split(
15480 &mut self,
15481 _: &GoToDeclaration,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) -> Task<Result<Navigated>> {
15485 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15486 }
15487
15488 pub fn go_to_implementation(
15489 &mut self,
15490 _: &GoToImplementation,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) -> Task<Result<Navigated>> {
15494 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15495 }
15496
15497 pub fn go_to_implementation_split(
15498 &mut self,
15499 _: &GoToImplementationSplit,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) -> Task<Result<Navigated>> {
15503 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15504 }
15505
15506 pub fn go_to_type_definition(
15507 &mut self,
15508 _: &GoToTypeDefinition,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) -> Task<Result<Navigated>> {
15512 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15513 }
15514
15515 pub fn go_to_definition_split(
15516 &mut self,
15517 _: &GoToDefinitionSplit,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) -> Task<Result<Navigated>> {
15521 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15522 }
15523
15524 pub fn go_to_type_definition_split(
15525 &mut self,
15526 _: &GoToTypeDefinitionSplit,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) -> Task<Result<Navigated>> {
15530 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15531 }
15532
15533 fn go_to_definition_of_kind(
15534 &mut self,
15535 kind: GotoDefinitionKind,
15536 split: bool,
15537 window: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) -> Task<Result<Navigated>> {
15540 let Some(provider) = self.semantics_provider.clone() else {
15541 return Task::ready(Ok(Navigated::No));
15542 };
15543 let head = self.selections.newest::<usize>(cx).head();
15544 let buffer = self.buffer.read(cx);
15545 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15546 text_anchor
15547 } else {
15548 return Task::ready(Ok(Navigated::No));
15549 };
15550
15551 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15552 return Task::ready(Ok(Navigated::No));
15553 };
15554
15555 cx.spawn_in(window, async move |editor, cx| {
15556 let definitions = definitions.await?;
15557 let navigated = editor
15558 .update_in(cx, |editor, window, cx| {
15559 editor.navigate_to_hover_links(
15560 Some(kind),
15561 definitions
15562 .into_iter()
15563 .filter(|location| {
15564 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15565 })
15566 .map(HoverLink::Text)
15567 .collect::<Vec<_>>(),
15568 split,
15569 window,
15570 cx,
15571 )
15572 })?
15573 .await?;
15574 anyhow::Ok(navigated)
15575 })
15576 }
15577
15578 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15579 let selection = self.selections.newest_anchor();
15580 let head = selection.head();
15581 let tail = selection.tail();
15582
15583 let Some((buffer, start_position)) =
15584 self.buffer.read(cx).text_anchor_for_position(head, cx)
15585 else {
15586 return;
15587 };
15588
15589 let end_position = if head != tail {
15590 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15591 return;
15592 };
15593 Some(pos)
15594 } else {
15595 None
15596 };
15597
15598 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15599 let url = if let Some(end_pos) = end_position {
15600 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15601 } else {
15602 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15603 };
15604
15605 if let Some(url) = url {
15606 editor.update(cx, |_, cx| {
15607 cx.open_url(&url);
15608 })
15609 } else {
15610 Ok(())
15611 }
15612 });
15613
15614 url_finder.detach();
15615 }
15616
15617 pub fn open_selected_filename(
15618 &mut self,
15619 _: &OpenSelectedFilename,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) {
15623 let Some(workspace) = self.workspace() else {
15624 return;
15625 };
15626
15627 let position = self.selections.newest_anchor().head();
15628
15629 let Some((buffer, buffer_position)) =
15630 self.buffer.read(cx).text_anchor_for_position(position, cx)
15631 else {
15632 return;
15633 };
15634
15635 let project = self.project.clone();
15636
15637 cx.spawn_in(window, async move |_, cx| {
15638 let result = find_file(&buffer, project, buffer_position, cx).await;
15639
15640 if let Some((_, path)) = result {
15641 workspace
15642 .update_in(cx, |workspace, window, cx| {
15643 workspace.open_resolved_path(path, window, cx)
15644 })?
15645 .await?;
15646 }
15647 anyhow::Ok(())
15648 })
15649 .detach();
15650 }
15651
15652 pub(crate) fn navigate_to_hover_links(
15653 &mut self,
15654 kind: Option<GotoDefinitionKind>,
15655 mut definitions: Vec<HoverLink>,
15656 split: bool,
15657 window: &mut Window,
15658 cx: &mut Context<Editor>,
15659 ) -> Task<Result<Navigated>> {
15660 // If there is one definition, just open it directly
15661 if definitions.len() == 1 {
15662 let definition = definitions.pop().unwrap();
15663
15664 enum TargetTaskResult {
15665 Location(Option<Location>),
15666 AlreadyNavigated,
15667 }
15668
15669 let target_task = match definition {
15670 HoverLink::Text(link) => {
15671 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15672 }
15673 HoverLink::InlayHint(lsp_location, server_id) => {
15674 let computation =
15675 self.compute_target_location(lsp_location, server_id, window, cx);
15676 cx.background_spawn(async move {
15677 let location = computation.await?;
15678 Ok(TargetTaskResult::Location(location))
15679 })
15680 }
15681 HoverLink::Url(url) => {
15682 cx.open_url(&url);
15683 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15684 }
15685 HoverLink::File(path) => {
15686 if let Some(workspace) = self.workspace() {
15687 cx.spawn_in(window, async move |_, cx| {
15688 workspace
15689 .update_in(cx, |workspace, window, cx| {
15690 workspace.open_resolved_path(path, window, cx)
15691 })?
15692 .await
15693 .map(|_| TargetTaskResult::AlreadyNavigated)
15694 })
15695 } else {
15696 Task::ready(Ok(TargetTaskResult::Location(None)))
15697 }
15698 }
15699 };
15700 cx.spawn_in(window, async move |editor, cx| {
15701 let target = match target_task.await.context("target resolution task")? {
15702 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15703 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15704 TargetTaskResult::Location(Some(target)) => target,
15705 };
15706
15707 editor.update_in(cx, |editor, window, cx| {
15708 let Some(workspace) = editor.workspace() else {
15709 return Navigated::No;
15710 };
15711 let pane = workspace.read(cx).active_pane().clone();
15712
15713 let range = target.range.to_point(target.buffer.read(cx));
15714 let range = editor.range_for_match(&range);
15715 let range = collapse_multiline_range(range);
15716
15717 if !split
15718 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15719 {
15720 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15721 } else {
15722 window.defer(cx, move |window, cx| {
15723 let target_editor: Entity<Self> =
15724 workspace.update(cx, |workspace, cx| {
15725 let pane = if split {
15726 workspace.adjacent_pane(window, cx)
15727 } else {
15728 workspace.active_pane().clone()
15729 };
15730
15731 workspace.open_project_item(
15732 pane,
15733 target.buffer.clone(),
15734 true,
15735 true,
15736 window,
15737 cx,
15738 )
15739 });
15740 target_editor.update(cx, |target_editor, cx| {
15741 // When selecting a definition in a different buffer, disable the nav history
15742 // to avoid creating a history entry at the previous cursor location.
15743 pane.update(cx, |pane, _| pane.disable_history());
15744 target_editor.go_to_singleton_buffer_range(range, window, cx);
15745 pane.update(cx, |pane, _| pane.enable_history());
15746 });
15747 });
15748 }
15749 Navigated::Yes
15750 })
15751 })
15752 } else if !definitions.is_empty() {
15753 cx.spawn_in(window, async move |editor, cx| {
15754 let (title, location_tasks, workspace) = editor
15755 .update_in(cx, |editor, window, cx| {
15756 let tab_kind = match kind {
15757 Some(GotoDefinitionKind::Implementation) => "Implementations",
15758 _ => "Definitions",
15759 };
15760 let title = definitions
15761 .iter()
15762 .find_map(|definition| match definition {
15763 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15764 let buffer = origin.buffer.read(cx);
15765 format!(
15766 "{} for {}",
15767 tab_kind,
15768 buffer
15769 .text_for_range(origin.range.clone())
15770 .collect::<String>()
15771 )
15772 }),
15773 HoverLink::InlayHint(_, _) => None,
15774 HoverLink::Url(_) => None,
15775 HoverLink::File(_) => None,
15776 })
15777 .unwrap_or(tab_kind.to_string());
15778 let location_tasks = definitions
15779 .into_iter()
15780 .map(|definition| match definition {
15781 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15782 HoverLink::InlayHint(lsp_location, server_id) => editor
15783 .compute_target_location(lsp_location, server_id, window, cx),
15784 HoverLink::Url(_) => Task::ready(Ok(None)),
15785 HoverLink::File(_) => Task::ready(Ok(None)),
15786 })
15787 .collect::<Vec<_>>();
15788 (title, location_tasks, editor.workspace().clone())
15789 })
15790 .context("location tasks preparation")?;
15791
15792 let locations: Vec<Location> = future::join_all(location_tasks)
15793 .await
15794 .into_iter()
15795 .filter_map(|location| location.transpose())
15796 .collect::<Result<_>>()
15797 .context("location tasks")?;
15798
15799 if locations.is_empty() {
15800 return Ok(Navigated::No);
15801 }
15802
15803 let Some(workspace) = workspace else {
15804 return Ok(Navigated::No);
15805 };
15806
15807 let opened = workspace
15808 .update_in(cx, |workspace, window, cx| {
15809 Self::open_locations_in_multibuffer(
15810 workspace,
15811 locations,
15812 title,
15813 split,
15814 MultibufferSelectionMode::First,
15815 window,
15816 cx,
15817 )
15818 })
15819 .ok();
15820
15821 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15822 })
15823 } else {
15824 Task::ready(Ok(Navigated::No))
15825 }
15826 }
15827
15828 fn compute_target_location(
15829 &self,
15830 lsp_location: lsp::Location,
15831 server_id: LanguageServerId,
15832 window: &mut Window,
15833 cx: &mut Context<Self>,
15834 ) -> Task<anyhow::Result<Option<Location>>> {
15835 let Some(project) = self.project.clone() else {
15836 return Task::ready(Ok(None));
15837 };
15838
15839 cx.spawn_in(window, async move |editor, cx| {
15840 let location_task = editor.update(cx, |_, cx| {
15841 project.update(cx, |project, cx| {
15842 let language_server_name = project
15843 .language_server_statuses(cx)
15844 .find(|(id, _)| server_id == *id)
15845 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15846 language_server_name.map(|language_server_name| {
15847 project.open_local_buffer_via_lsp(
15848 lsp_location.uri.clone(),
15849 server_id,
15850 language_server_name,
15851 cx,
15852 )
15853 })
15854 })
15855 })?;
15856 let location = match location_task {
15857 Some(task) => Some({
15858 let target_buffer_handle = task.await.context("open local buffer")?;
15859 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15860 let target_start = target_buffer
15861 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15862 let target_end = target_buffer
15863 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15864 target_buffer.anchor_after(target_start)
15865 ..target_buffer.anchor_before(target_end)
15866 })?;
15867 Location {
15868 buffer: target_buffer_handle,
15869 range,
15870 }
15871 }),
15872 None => None,
15873 };
15874 Ok(location)
15875 })
15876 }
15877
15878 pub fn find_all_references(
15879 &mut self,
15880 _: &FindAllReferences,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) -> Option<Task<Result<Navigated>>> {
15884 let selection = self.selections.newest::<usize>(cx);
15885 let multi_buffer = self.buffer.read(cx);
15886 let head = selection.head();
15887
15888 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15889 let head_anchor = multi_buffer_snapshot.anchor_at(
15890 head,
15891 if head < selection.tail() {
15892 Bias::Right
15893 } else {
15894 Bias::Left
15895 },
15896 );
15897
15898 match self
15899 .find_all_references_task_sources
15900 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15901 {
15902 Ok(_) => {
15903 log::info!(
15904 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15905 );
15906 return None;
15907 }
15908 Err(i) => {
15909 self.find_all_references_task_sources.insert(i, head_anchor);
15910 }
15911 }
15912
15913 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15914 let workspace = self.workspace()?;
15915 let project = workspace.read(cx).project().clone();
15916 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15917 Some(cx.spawn_in(window, async move |editor, cx| {
15918 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15919 if let Ok(i) = editor
15920 .find_all_references_task_sources
15921 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15922 {
15923 editor.find_all_references_task_sources.remove(i);
15924 }
15925 });
15926
15927 let locations = references.await?;
15928 if locations.is_empty() {
15929 return anyhow::Ok(Navigated::No);
15930 }
15931
15932 workspace.update_in(cx, |workspace, window, cx| {
15933 let title = locations
15934 .first()
15935 .as_ref()
15936 .map(|location| {
15937 let buffer = location.buffer.read(cx);
15938 format!(
15939 "References to `{}`",
15940 buffer
15941 .text_for_range(location.range.clone())
15942 .collect::<String>()
15943 )
15944 })
15945 .unwrap();
15946 Self::open_locations_in_multibuffer(
15947 workspace,
15948 locations,
15949 title,
15950 false,
15951 MultibufferSelectionMode::First,
15952 window,
15953 cx,
15954 );
15955 Navigated::Yes
15956 })
15957 }))
15958 }
15959
15960 /// Opens a multibuffer with the given project locations in it
15961 pub fn open_locations_in_multibuffer(
15962 workspace: &mut Workspace,
15963 mut locations: Vec<Location>,
15964 title: String,
15965 split: bool,
15966 multibuffer_selection_mode: MultibufferSelectionMode,
15967 window: &mut Window,
15968 cx: &mut Context<Workspace>,
15969 ) {
15970 if locations.is_empty() {
15971 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15972 return;
15973 }
15974
15975 // If there are multiple definitions, open them in a multibuffer
15976 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15977 let mut locations = locations.into_iter().peekable();
15978 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15979 let capability = workspace.project().read(cx).capability();
15980
15981 let excerpt_buffer = cx.new(|cx| {
15982 let mut multibuffer = MultiBuffer::new(capability);
15983 while let Some(location) = locations.next() {
15984 let buffer = location.buffer.read(cx);
15985 let mut ranges_for_buffer = Vec::new();
15986 let range = location.range.to_point(buffer);
15987 ranges_for_buffer.push(range.clone());
15988
15989 while let Some(next_location) = locations.peek() {
15990 if next_location.buffer == location.buffer {
15991 ranges_for_buffer.push(next_location.range.to_point(buffer));
15992 locations.next();
15993 } else {
15994 break;
15995 }
15996 }
15997
15998 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15999 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16000 PathKey::for_buffer(&location.buffer, cx),
16001 location.buffer.clone(),
16002 ranges_for_buffer,
16003 DEFAULT_MULTIBUFFER_CONTEXT,
16004 cx,
16005 );
16006 ranges.extend(new_ranges)
16007 }
16008
16009 multibuffer.with_title(title)
16010 });
16011
16012 let editor = cx.new(|cx| {
16013 Editor::for_multibuffer(
16014 excerpt_buffer,
16015 Some(workspace.project().clone()),
16016 window,
16017 cx,
16018 )
16019 });
16020 editor.update(cx, |editor, cx| {
16021 match multibuffer_selection_mode {
16022 MultibufferSelectionMode::First => {
16023 if let Some(first_range) = ranges.first() {
16024 editor.change_selections(
16025 SelectionEffects::no_scroll(),
16026 window,
16027 cx,
16028 |selections| {
16029 selections.clear_disjoint();
16030 selections
16031 .select_anchor_ranges(std::iter::once(first_range.clone()));
16032 },
16033 );
16034 }
16035 editor.highlight_background::<Self>(
16036 &ranges,
16037 |theme| theme.colors().editor_highlighted_line_background,
16038 cx,
16039 );
16040 }
16041 MultibufferSelectionMode::All => {
16042 editor.change_selections(
16043 SelectionEffects::no_scroll(),
16044 window,
16045 cx,
16046 |selections| {
16047 selections.clear_disjoint();
16048 selections.select_anchor_ranges(ranges);
16049 },
16050 );
16051 }
16052 }
16053 editor.register_buffers_with_language_servers(cx);
16054 });
16055
16056 let item = Box::new(editor);
16057 let item_id = item.item_id();
16058
16059 if split {
16060 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16061 } else {
16062 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16063 let (preview_item_id, preview_item_idx) =
16064 workspace.active_pane().read_with(cx, |pane, _| {
16065 (pane.preview_item_id(), pane.preview_item_idx())
16066 });
16067
16068 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16069
16070 if let Some(preview_item_id) = preview_item_id {
16071 workspace.active_pane().update(cx, |pane, cx| {
16072 pane.remove_item(preview_item_id, false, false, window, cx);
16073 });
16074 }
16075 } else {
16076 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16077 }
16078 }
16079 workspace.active_pane().update(cx, |pane, cx| {
16080 pane.set_preview_item_id(Some(item_id), cx);
16081 });
16082 }
16083
16084 pub fn rename(
16085 &mut self,
16086 _: &Rename,
16087 window: &mut Window,
16088 cx: &mut Context<Self>,
16089 ) -> Option<Task<Result<()>>> {
16090 use language::ToOffset as _;
16091
16092 let provider = self.semantics_provider.clone()?;
16093 let selection = self.selections.newest_anchor().clone();
16094 let (cursor_buffer, cursor_buffer_position) = self
16095 .buffer
16096 .read(cx)
16097 .text_anchor_for_position(selection.head(), cx)?;
16098 let (tail_buffer, cursor_buffer_position_end) = self
16099 .buffer
16100 .read(cx)
16101 .text_anchor_for_position(selection.tail(), cx)?;
16102 if tail_buffer != cursor_buffer {
16103 return None;
16104 }
16105
16106 let snapshot = cursor_buffer.read(cx).snapshot();
16107 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16108 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16109 let prepare_rename = provider
16110 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16111 .unwrap_or_else(|| Task::ready(Ok(None)));
16112 drop(snapshot);
16113
16114 Some(cx.spawn_in(window, async move |this, cx| {
16115 let rename_range = if let Some(range) = prepare_rename.await? {
16116 Some(range)
16117 } else {
16118 this.update(cx, |this, cx| {
16119 let buffer = this.buffer.read(cx).snapshot(cx);
16120 let mut buffer_highlights = this
16121 .document_highlights_for_position(selection.head(), &buffer)
16122 .filter(|highlight| {
16123 highlight.start.excerpt_id == selection.head().excerpt_id
16124 && highlight.end.excerpt_id == selection.head().excerpt_id
16125 });
16126 buffer_highlights
16127 .next()
16128 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16129 })?
16130 };
16131 if let Some(rename_range) = rename_range {
16132 this.update_in(cx, |this, window, cx| {
16133 let snapshot = cursor_buffer.read(cx).snapshot();
16134 let rename_buffer_range = rename_range.to_offset(&snapshot);
16135 let cursor_offset_in_rename_range =
16136 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16137 let cursor_offset_in_rename_range_end =
16138 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16139
16140 this.take_rename(false, window, cx);
16141 let buffer = this.buffer.read(cx).read(cx);
16142 let cursor_offset = selection.head().to_offset(&buffer);
16143 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16144 let rename_end = rename_start + rename_buffer_range.len();
16145 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16146 let mut old_highlight_id = None;
16147 let old_name: Arc<str> = buffer
16148 .chunks(rename_start..rename_end, true)
16149 .map(|chunk| {
16150 if old_highlight_id.is_none() {
16151 old_highlight_id = chunk.syntax_highlight_id;
16152 }
16153 chunk.text
16154 })
16155 .collect::<String>()
16156 .into();
16157
16158 drop(buffer);
16159
16160 // Position the selection in the rename editor so that it matches the current selection.
16161 this.show_local_selections = false;
16162 let rename_editor = cx.new(|cx| {
16163 let mut editor = Editor::single_line(window, cx);
16164 editor.buffer.update(cx, |buffer, cx| {
16165 buffer.edit([(0..0, old_name.clone())], None, cx)
16166 });
16167 let rename_selection_range = match cursor_offset_in_rename_range
16168 .cmp(&cursor_offset_in_rename_range_end)
16169 {
16170 Ordering::Equal => {
16171 editor.select_all(&SelectAll, window, cx);
16172 return editor;
16173 }
16174 Ordering::Less => {
16175 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16176 }
16177 Ordering::Greater => {
16178 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16179 }
16180 };
16181 if rename_selection_range.end > old_name.len() {
16182 editor.select_all(&SelectAll, window, cx);
16183 } else {
16184 editor.change_selections(Default::default(), window, cx, |s| {
16185 s.select_ranges([rename_selection_range]);
16186 });
16187 }
16188 editor
16189 });
16190 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16191 if e == &EditorEvent::Focused {
16192 cx.emit(EditorEvent::FocusedIn)
16193 }
16194 })
16195 .detach();
16196
16197 let write_highlights =
16198 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16199 let read_highlights =
16200 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16201 let ranges = write_highlights
16202 .iter()
16203 .flat_map(|(_, ranges)| ranges.iter())
16204 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16205 .cloned()
16206 .collect();
16207
16208 this.highlight_text::<Rename>(
16209 ranges,
16210 HighlightStyle {
16211 fade_out: Some(0.6),
16212 ..Default::default()
16213 },
16214 cx,
16215 );
16216 let rename_focus_handle = rename_editor.focus_handle(cx);
16217 window.focus(&rename_focus_handle);
16218 let block_id = this.insert_blocks(
16219 [BlockProperties {
16220 style: BlockStyle::Flex,
16221 placement: BlockPlacement::Below(range.start),
16222 height: Some(1),
16223 render: Arc::new({
16224 let rename_editor = rename_editor.clone();
16225 move |cx: &mut BlockContext| {
16226 let mut text_style = cx.editor_style.text.clone();
16227 if let Some(highlight_style) = old_highlight_id
16228 .and_then(|h| h.style(&cx.editor_style.syntax))
16229 {
16230 text_style = text_style.highlight(highlight_style);
16231 }
16232 div()
16233 .block_mouse_except_scroll()
16234 .pl(cx.anchor_x)
16235 .child(EditorElement::new(
16236 &rename_editor,
16237 EditorStyle {
16238 background: cx.theme().system().transparent,
16239 local_player: cx.editor_style.local_player,
16240 text: text_style,
16241 scrollbar_width: cx.editor_style.scrollbar_width,
16242 syntax: cx.editor_style.syntax.clone(),
16243 status: cx.editor_style.status.clone(),
16244 inlay_hints_style: HighlightStyle {
16245 font_weight: Some(FontWeight::BOLD),
16246 ..make_inlay_hints_style(cx.app)
16247 },
16248 inline_completion_styles: make_suggestion_styles(
16249 cx.app,
16250 ),
16251 ..EditorStyle::default()
16252 },
16253 ))
16254 .into_any_element()
16255 }
16256 }),
16257 priority: 0,
16258 }],
16259 Some(Autoscroll::fit()),
16260 cx,
16261 )[0];
16262 this.pending_rename = Some(RenameState {
16263 range,
16264 old_name,
16265 editor: rename_editor,
16266 block_id,
16267 });
16268 })?;
16269 }
16270
16271 Ok(())
16272 }))
16273 }
16274
16275 pub fn confirm_rename(
16276 &mut self,
16277 _: &ConfirmRename,
16278 window: &mut Window,
16279 cx: &mut Context<Self>,
16280 ) -> Option<Task<Result<()>>> {
16281 let rename = self.take_rename(false, window, cx)?;
16282 let workspace = self.workspace()?.downgrade();
16283 let (buffer, start) = self
16284 .buffer
16285 .read(cx)
16286 .text_anchor_for_position(rename.range.start, cx)?;
16287 let (end_buffer, _) = self
16288 .buffer
16289 .read(cx)
16290 .text_anchor_for_position(rename.range.end, cx)?;
16291 if buffer != end_buffer {
16292 return None;
16293 }
16294
16295 let old_name = rename.old_name;
16296 let new_name = rename.editor.read(cx).text(cx);
16297
16298 let rename = self.semantics_provider.as_ref()?.perform_rename(
16299 &buffer,
16300 start,
16301 new_name.clone(),
16302 cx,
16303 )?;
16304
16305 Some(cx.spawn_in(window, async move |editor, cx| {
16306 let project_transaction = rename.await?;
16307 Self::open_project_transaction(
16308 &editor,
16309 workspace,
16310 project_transaction,
16311 format!("Rename: {} → {}", old_name, new_name),
16312 cx,
16313 )
16314 .await?;
16315
16316 editor.update(cx, |editor, cx| {
16317 editor.refresh_document_highlights(cx);
16318 })?;
16319 Ok(())
16320 }))
16321 }
16322
16323 fn take_rename(
16324 &mut self,
16325 moving_cursor: bool,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) -> Option<RenameState> {
16329 let rename = self.pending_rename.take()?;
16330 if rename.editor.focus_handle(cx).is_focused(window) {
16331 window.focus(&self.focus_handle);
16332 }
16333
16334 self.remove_blocks(
16335 [rename.block_id].into_iter().collect(),
16336 Some(Autoscroll::fit()),
16337 cx,
16338 );
16339 self.clear_highlights::<Rename>(cx);
16340 self.show_local_selections = true;
16341
16342 if moving_cursor {
16343 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16344 editor.selections.newest::<usize>(cx).head()
16345 });
16346
16347 // Update the selection to match the position of the selection inside
16348 // the rename editor.
16349 let snapshot = self.buffer.read(cx).read(cx);
16350 let rename_range = rename.range.to_offset(&snapshot);
16351 let cursor_in_editor = snapshot
16352 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16353 .min(rename_range.end);
16354 drop(snapshot);
16355
16356 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16357 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16358 });
16359 } else {
16360 self.refresh_document_highlights(cx);
16361 }
16362
16363 Some(rename)
16364 }
16365
16366 pub fn pending_rename(&self) -> Option<&RenameState> {
16367 self.pending_rename.as_ref()
16368 }
16369
16370 fn format(
16371 &mut self,
16372 _: &Format,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Option<Task<Result<()>>> {
16376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16377
16378 let project = match &self.project {
16379 Some(project) => project.clone(),
16380 None => return None,
16381 };
16382
16383 Some(self.perform_format(
16384 project,
16385 FormatTrigger::Manual,
16386 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16387 window,
16388 cx,
16389 ))
16390 }
16391
16392 fn format_selections(
16393 &mut self,
16394 _: &FormatSelections,
16395 window: &mut Window,
16396 cx: &mut Context<Self>,
16397 ) -> Option<Task<Result<()>>> {
16398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16399
16400 let project = match &self.project {
16401 Some(project) => project.clone(),
16402 None => return None,
16403 };
16404
16405 let ranges = self
16406 .selections
16407 .all_adjusted(cx)
16408 .into_iter()
16409 .map(|selection| selection.range())
16410 .collect_vec();
16411
16412 Some(self.perform_format(
16413 project,
16414 FormatTrigger::Manual,
16415 FormatTarget::Ranges(ranges),
16416 window,
16417 cx,
16418 ))
16419 }
16420
16421 fn perform_format(
16422 &mut self,
16423 project: Entity<Project>,
16424 trigger: FormatTrigger,
16425 target: FormatTarget,
16426 window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) -> Task<Result<()>> {
16429 let buffer = self.buffer.clone();
16430 let (buffers, target) = match target {
16431 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16432 FormatTarget::Ranges(selection_ranges) => {
16433 let multi_buffer = buffer.read(cx);
16434 let snapshot = multi_buffer.read(cx);
16435 let mut buffers = HashSet::default();
16436 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16437 BTreeMap::new();
16438 for selection_range in selection_ranges {
16439 for (buffer, buffer_range, _) in
16440 snapshot.range_to_buffer_ranges(selection_range)
16441 {
16442 let buffer_id = buffer.remote_id();
16443 let start = buffer.anchor_before(buffer_range.start);
16444 let end = buffer.anchor_after(buffer_range.end);
16445 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16446 buffer_id_to_ranges
16447 .entry(buffer_id)
16448 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16449 .or_insert_with(|| vec![start..end]);
16450 }
16451 }
16452 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16453 }
16454 };
16455
16456 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16457 let selections_prev = transaction_id_prev
16458 .and_then(|transaction_id_prev| {
16459 // default to selections as they were after the last edit, if we have them,
16460 // instead of how they are now.
16461 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16462 // will take you back to where you made the last edit, instead of staying where you scrolled
16463 self.selection_history
16464 .transaction(transaction_id_prev)
16465 .map(|t| t.0.clone())
16466 })
16467 .unwrap_or_else(|| {
16468 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16469 self.selections.disjoint_anchors()
16470 });
16471
16472 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16473 let format = project.update(cx, |project, cx| {
16474 project.format(buffers, target, true, trigger, cx)
16475 });
16476
16477 cx.spawn_in(window, async move |editor, cx| {
16478 let transaction = futures::select_biased! {
16479 transaction = format.log_err().fuse() => transaction,
16480 () = timeout => {
16481 log::warn!("timed out waiting for formatting");
16482 None
16483 }
16484 };
16485
16486 buffer
16487 .update(cx, |buffer, cx| {
16488 if let Some(transaction) = transaction {
16489 if !buffer.is_singleton() {
16490 buffer.push_transaction(&transaction.0, cx);
16491 }
16492 }
16493 cx.notify();
16494 })
16495 .ok();
16496
16497 if let Some(transaction_id_now) =
16498 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16499 {
16500 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16501 if has_new_transaction {
16502 _ = editor.update(cx, |editor, _| {
16503 editor
16504 .selection_history
16505 .insert_transaction(transaction_id_now, selections_prev);
16506 });
16507 }
16508 }
16509
16510 Ok(())
16511 })
16512 }
16513
16514 fn organize_imports(
16515 &mut self,
16516 _: &OrganizeImports,
16517 window: &mut Window,
16518 cx: &mut Context<Self>,
16519 ) -> Option<Task<Result<()>>> {
16520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16521 let project = match &self.project {
16522 Some(project) => project.clone(),
16523 None => return None,
16524 };
16525 Some(self.perform_code_action_kind(
16526 project,
16527 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16528 window,
16529 cx,
16530 ))
16531 }
16532
16533 fn perform_code_action_kind(
16534 &mut self,
16535 project: Entity<Project>,
16536 kind: CodeActionKind,
16537 window: &mut Window,
16538 cx: &mut Context<Self>,
16539 ) -> Task<Result<()>> {
16540 let buffer = self.buffer.clone();
16541 let buffers = buffer.read(cx).all_buffers();
16542 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16543 let apply_action = project.update(cx, |project, cx| {
16544 project.apply_code_action_kind(buffers, kind, true, cx)
16545 });
16546 cx.spawn_in(window, async move |_, cx| {
16547 let transaction = futures::select_biased! {
16548 () = timeout => {
16549 log::warn!("timed out waiting for executing code action");
16550 None
16551 }
16552 transaction = apply_action.log_err().fuse() => transaction,
16553 };
16554 buffer
16555 .update(cx, |buffer, cx| {
16556 // check if we need this
16557 if let Some(transaction) = transaction {
16558 if !buffer.is_singleton() {
16559 buffer.push_transaction(&transaction.0, cx);
16560 }
16561 }
16562 cx.notify();
16563 })
16564 .ok();
16565 Ok(())
16566 })
16567 }
16568
16569 pub fn restart_language_server(
16570 &mut self,
16571 _: &RestartLanguageServer,
16572 _: &mut Window,
16573 cx: &mut Context<Self>,
16574 ) {
16575 if let Some(project) = self.project.clone() {
16576 self.buffer.update(cx, |multi_buffer, cx| {
16577 project.update(cx, |project, cx| {
16578 project.restart_language_servers_for_buffers(
16579 multi_buffer.all_buffers().into_iter().collect(),
16580 HashSet::default(),
16581 cx,
16582 );
16583 });
16584 })
16585 }
16586 }
16587
16588 pub fn stop_language_server(
16589 &mut self,
16590 _: &StopLanguageServer,
16591 _: &mut Window,
16592 cx: &mut Context<Self>,
16593 ) {
16594 if let Some(project) = self.project.clone() {
16595 self.buffer.update(cx, |multi_buffer, cx| {
16596 project.update(cx, |project, cx| {
16597 project.stop_language_servers_for_buffers(
16598 multi_buffer.all_buffers().into_iter().collect(),
16599 HashSet::default(),
16600 cx,
16601 );
16602 cx.emit(project::Event::RefreshInlayHints);
16603 });
16604 });
16605 }
16606 }
16607
16608 fn cancel_language_server_work(
16609 workspace: &mut Workspace,
16610 _: &actions::CancelLanguageServerWork,
16611 _: &mut Window,
16612 cx: &mut Context<Workspace>,
16613 ) {
16614 let project = workspace.project();
16615 let buffers = workspace
16616 .active_item(cx)
16617 .and_then(|item| item.act_as::<Editor>(cx))
16618 .map_or(HashSet::default(), |editor| {
16619 editor.read(cx).buffer.read(cx).all_buffers()
16620 });
16621 project.update(cx, |project, cx| {
16622 project.cancel_language_server_work_for_buffers(buffers, cx);
16623 });
16624 }
16625
16626 fn show_character_palette(
16627 &mut self,
16628 _: &ShowCharacterPalette,
16629 window: &mut Window,
16630 _: &mut Context<Self>,
16631 ) {
16632 window.show_character_palette();
16633 }
16634
16635 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16636 if !self.diagnostics_enabled() {
16637 return;
16638 }
16639
16640 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16641 let buffer = self.buffer.read(cx).snapshot(cx);
16642 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16643 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16644 let is_valid = buffer
16645 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16646 .any(|entry| {
16647 entry.diagnostic.is_primary
16648 && !entry.range.is_empty()
16649 && entry.range.start == primary_range_start
16650 && entry.diagnostic.message == active_diagnostics.active_message
16651 });
16652
16653 if !is_valid {
16654 self.dismiss_diagnostics(cx);
16655 }
16656 }
16657 }
16658
16659 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16660 match &self.active_diagnostics {
16661 ActiveDiagnostic::Group(group) => Some(group),
16662 _ => None,
16663 }
16664 }
16665
16666 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16667 if !self.diagnostics_enabled() {
16668 return;
16669 }
16670 self.dismiss_diagnostics(cx);
16671 self.active_diagnostics = ActiveDiagnostic::All;
16672 }
16673
16674 fn activate_diagnostics(
16675 &mut self,
16676 buffer_id: BufferId,
16677 diagnostic: DiagnosticEntry<usize>,
16678 window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16682 return;
16683 }
16684 self.dismiss_diagnostics(cx);
16685 let snapshot = self.snapshot(window, cx);
16686 let buffer = self.buffer.read(cx).snapshot(cx);
16687 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16688 return;
16689 };
16690
16691 let diagnostic_group = buffer
16692 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16693 .collect::<Vec<_>>();
16694
16695 let blocks =
16696 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16697
16698 let blocks = self.display_map.update(cx, |display_map, cx| {
16699 display_map.insert_blocks(blocks, cx).into_iter().collect()
16700 });
16701 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16702 active_range: buffer.anchor_before(diagnostic.range.start)
16703 ..buffer.anchor_after(diagnostic.range.end),
16704 active_message: diagnostic.diagnostic.message.clone(),
16705 group_id: diagnostic.diagnostic.group_id,
16706 blocks,
16707 });
16708 cx.notify();
16709 }
16710
16711 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16712 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16713 return;
16714 };
16715
16716 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16717 if let ActiveDiagnostic::Group(group) = prev {
16718 self.display_map.update(cx, |display_map, cx| {
16719 display_map.remove_blocks(group.blocks, cx);
16720 });
16721 cx.notify();
16722 }
16723 }
16724
16725 /// Disable inline diagnostics rendering for this editor.
16726 pub fn disable_inline_diagnostics(&mut self) {
16727 self.inline_diagnostics_enabled = false;
16728 self.inline_diagnostics_update = Task::ready(());
16729 self.inline_diagnostics.clear();
16730 }
16731
16732 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16733 self.diagnostics_enabled = false;
16734 self.dismiss_diagnostics(cx);
16735 self.inline_diagnostics_update = Task::ready(());
16736 self.inline_diagnostics.clear();
16737 }
16738
16739 pub fn diagnostics_enabled(&self) -> bool {
16740 self.diagnostics_enabled && self.mode.is_full()
16741 }
16742
16743 pub fn inline_diagnostics_enabled(&self) -> bool {
16744 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16745 }
16746
16747 pub fn show_inline_diagnostics(&self) -> bool {
16748 self.show_inline_diagnostics
16749 }
16750
16751 pub fn toggle_inline_diagnostics(
16752 &mut self,
16753 _: &ToggleInlineDiagnostics,
16754 window: &mut Window,
16755 cx: &mut Context<Editor>,
16756 ) {
16757 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16758 self.refresh_inline_diagnostics(false, window, cx);
16759 }
16760
16761 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16762 self.diagnostics_max_severity = severity;
16763 self.display_map.update(cx, |display_map, _| {
16764 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16765 });
16766 }
16767
16768 pub fn toggle_diagnostics(
16769 &mut self,
16770 _: &ToggleDiagnostics,
16771 window: &mut Window,
16772 cx: &mut Context<Editor>,
16773 ) {
16774 if !self.diagnostics_enabled() {
16775 return;
16776 }
16777
16778 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16779 EditorSettings::get_global(cx)
16780 .diagnostics_max_severity
16781 .filter(|severity| severity != &DiagnosticSeverity::Off)
16782 .unwrap_or(DiagnosticSeverity::Hint)
16783 } else {
16784 DiagnosticSeverity::Off
16785 };
16786 self.set_max_diagnostics_severity(new_severity, cx);
16787 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16788 self.active_diagnostics = ActiveDiagnostic::None;
16789 self.inline_diagnostics_update = Task::ready(());
16790 self.inline_diagnostics.clear();
16791 } else {
16792 self.refresh_inline_diagnostics(false, window, cx);
16793 }
16794
16795 cx.notify();
16796 }
16797
16798 pub fn toggle_minimap(
16799 &mut self,
16800 _: &ToggleMinimap,
16801 window: &mut Window,
16802 cx: &mut Context<Editor>,
16803 ) {
16804 if self.supports_minimap(cx) {
16805 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16806 }
16807 }
16808
16809 fn refresh_inline_diagnostics(
16810 &mut self,
16811 debounce: bool,
16812 window: &mut Window,
16813 cx: &mut Context<Self>,
16814 ) {
16815 let max_severity = ProjectSettings::get_global(cx)
16816 .diagnostics
16817 .inline
16818 .max_severity
16819 .unwrap_or(self.diagnostics_max_severity);
16820
16821 if !self.inline_diagnostics_enabled()
16822 || !self.show_inline_diagnostics
16823 || max_severity == DiagnosticSeverity::Off
16824 {
16825 self.inline_diagnostics_update = Task::ready(());
16826 self.inline_diagnostics.clear();
16827 return;
16828 }
16829
16830 let debounce_ms = ProjectSettings::get_global(cx)
16831 .diagnostics
16832 .inline
16833 .update_debounce_ms;
16834 let debounce = if debounce && debounce_ms > 0 {
16835 Some(Duration::from_millis(debounce_ms))
16836 } else {
16837 None
16838 };
16839 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16840 if let Some(debounce) = debounce {
16841 cx.background_executor().timer(debounce).await;
16842 }
16843 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16844 editor
16845 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16846 .ok()
16847 }) else {
16848 return;
16849 };
16850
16851 let new_inline_diagnostics = cx
16852 .background_spawn(async move {
16853 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16854 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16855 let message = diagnostic_entry
16856 .diagnostic
16857 .message
16858 .split_once('\n')
16859 .map(|(line, _)| line)
16860 .map(SharedString::new)
16861 .unwrap_or_else(|| {
16862 SharedString::from(diagnostic_entry.diagnostic.message)
16863 });
16864 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16865 let (Ok(i) | Err(i)) = inline_diagnostics
16866 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16867 inline_diagnostics.insert(
16868 i,
16869 (
16870 start_anchor,
16871 InlineDiagnostic {
16872 message,
16873 group_id: diagnostic_entry.diagnostic.group_id,
16874 start: diagnostic_entry.range.start.to_point(&snapshot),
16875 is_primary: diagnostic_entry.diagnostic.is_primary,
16876 severity: diagnostic_entry.diagnostic.severity,
16877 },
16878 ),
16879 );
16880 }
16881 inline_diagnostics
16882 })
16883 .await;
16884
16885 editor
16886 .update(cx, |editor, cx| {
16887 editor.inline_diagnostics = new_inline_diagnostics;
16888 cx.notify();
16889 })
16890 .ok();
16891 });
16892 }
16893
16894 fn pull_diagnostics(
16895 &mut self,
16896 buffer_id: Option<BufferId>,
16897 window: &Window,
16898 cx: &mut Context<Self>,
16899 ) -> Option<()> {
16900 if !self.mode().is_full() {
16901 return None;
16902 }
16903 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16904 .diagnostics
16905 .lsp_pull_diagnostics;
16906 if !pull_diagnostics_settings.enabled {
16907 return None;
16908 }
16909 let project = self.project.as_ref()?.downgrade();
16910 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16911 let mut buffers = self.buffer.read(cx).all_buffers();
16912 if let Some(buffer_id) = buffer_id {
16913 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16914 }
16915
16916 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16917 cx.background_executor().timer(debounce).await;
16918
16919 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16920 buffers
16921 .into_iter()
16922 .filter_map(|buffer| {
16923 project
16924 .update(cx, |project, cx| {
16925 project.lsp_store().update(cx, |lsp_store, cx| {
16926 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16927 })
16928 })
16929 .ok()
16930 })
16931 .collect::<FuturesUnordered<_>>()
16932 }) else {
16933 return;
16934 };
16935
16936 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16937 match pull_task {
16938 Ok(()) => {
16939 if editor
16940 .update_in(cx, |editor, window, cx| {
16941 editor.update_diagnostics_state(window, cx);
16942 })
16943 .is_err()
16944 {
16945 return;
16946 }
16947 }
16948 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16949 }
16950 }
16951 });
16952
16953 Some(())
16954 }
16955
16956 pub fn set_selections_from_remote(
16957 &mut self,
16958 selections: Vec<Selection<Anchor>>,
16959 pending_selection: Option<Selection<Anchor>>,
16960 window: &mut Window,
16961 cx: &mut Context<Self>,
16962 ) {
16963 let old_cursor_position = self.selections.newest_anchor().head();
16964 self.selections.change_with(cx, |s| {
16965 s.select_anchors(selections);
16966 if let Some(pending_selection) = pending_selection {
16967 s.set_pending(pending_selection, SelectMode::Character);
16968 } else {
16969 s.clear_pending();
16970 }
16971 });
16972 self.selections_did_change(
16973 false,
16974 &old_cursor_position,
16975 SelectionEffects::default(),
16976 window,
16977 cx,
16978 );
16979 }
16980
16981 pub fn transact(
16982 &mut self,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16986 ) -> Option<TransactionId> {
16987 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16988 this.start_transaction_at(Instant::now(), window, cx);
16989 update(this, window, cx);
16990 this.end_transaction_at(Instant::now(), cx)
16991 })
16992 }
16993
16994 pub fn start_transaction_at(
16995 &mut self,
16996 now: Instant,
16997 window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 self.end_selection(window, cx);
17001 if let Some(tx_id) = self
17002 .buffer
17003 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17004 {
17005 self.selection_history
17006 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17007 cx.emit(EditorEvent::TransactionBegun {
17008 transaction_id: tx_id,
17009 })
17010 }
17011 }
17012
17013 pub fn end_transaction_at(
17014 &mut self,
17015 now: Instant,
17016 cx: &mut Context<Self>,
17017 ) -> Option<TransactionId> {
17018 if let Some(transaction_id) = self
17019 .buffer
17020 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17021 {
17022 if let Some((_, end_selections)) =
17023 self.selection_history.transaction_mut(transaction_id)
17024 {
17025 *end_selections = Some(self.selections.disjoint_anchors());
17026 } else {
17027 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17028 }
17029
17030 cx.emit(EditorEvent::Edited { transaction_id });
17031 Some(transaction_id)
17032 } else {
17033 None
17034 }
17035 }
17036
17037 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17038 if self.selection_mark_mode {
17039 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17040 s.move_with(|_, sel| {
17041 sel.collapse_to(sel.head(), SelectionGoal::None);
17042 });
17043 })
17044 }
17045 self.selection_mark_mode = true;
17046 cx.notify();
17047 }
17048
17049 pub fn swap_selection_ends(
17050 &mut self,
17051 _: &actions::SwapSelectionEnds,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 ) {
17055 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17056 s.move_with(|_, sel| {
17057 if sel.start != sel.end {
17058 sel.reversed = !sel.reversed
17059 }
17060 });
17061 });
17062 self.request_autoscroll(Autoscroll::newest(), cx);
17063 cx.notify();
17064 }
17065
17066 pub fn toggle_focus(
17067 workspace: &mut Workspace,
17068 _: &actions::ToggleFocus,
17069 window: &mut Window,
17070 cx: &mut Context<Workspace>,
17071 ) {
17072 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17073 return;
17074 };
17075 workspace.activate_item(&item, true, true, window, cx);
17076 }
17077
17078 pub fn toggle_fold(
17079 &mut self,
17080 _: &actions::ToggleFold,
17081 window: &mut Window,
17082 cx: &mut Context<Self>,
17083 ) {
17084 if self.is_singleton(cx) {
17085 let selection = self.selections.newest::<Point>(cx);
17086
17087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17088 let range = if selection.is_empty() {
17089 let point = selection.head().to_display_point(&display_map);
17090 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17091 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17092 .to_point(&display_map);
17093 start..end
17094 } else {
17095 selection.range()
17096 };
17097 if display_map.folds_in_range(range).next().is_some() {
17098 self.unfold_lines(&Default::default(), window, cx)
17099 } else {
17100 self.fold(&Default::default(), window, cx)
17101 }
17102 } else {
17103 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17104 let buffer_ids: HashSet<_> = self
17105 .selections
17106 .disjoint_anchor_ranges()
17107 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17108 .collect();
17109
17110 let should_unfold = buffer_ids
17111 .iter()
17112 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17113
17114 for buffer_id in buffer_ids {
17115 if should_unfold {
17116 self.unfold_buffer(buffer_id, cx);
17117 } else {
17118 self.fold_buffer(buffer_id, cx);
17119 }
17120 }
17121 }
17122 }
17123
17124 pub fn toggle_fold_recursive(
17125 &mut self,
17126 _: &actions::ToggleFoldRecursive,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 ) {
17130 let selection = self.selections.newest::<Point>(cx);
17131
17132 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17133 let range = if selection.is_empty() {
17134 let point = selection.head().to_display_point(&display_map);
17135 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17136 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17137 .to_point(&display_map);
17138 start..end
17139 } else {
17140 selection.range()
17141 };
17142 if display_map.folds_in_range(range).next().is_some() {
17143 self.unfold_recursive(&Default::default(), window, cx)
17144 } else {
17145 self.fold_recursive(&Default::default(), window, cx)
17146 }
17147 }
17148
17149 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17150 if self.is_singleton(cx) {
17151 let mut to_fold = Vec::new();
17152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17153 let selections = self.selections.all_adjusted(cx);
17154
17155 for selection in selections {
17156 let range = selection.range().sorted();
17157 let buffer_start_row = range.start.row;
17158
17159 if range.start.row != range.end.row {
17160 let mut found = false;
17161 let mut row = range.start.row;
17162 while row <= range.end.row {
17163 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17164 {
17165 found = true;
17166 row = crease.range().end.row + 1;
17167 to_fold.push(crease);
17168 } else {
17169 row += 1
17170 }
17171 }
17172 if found {
17173 continue;
17174 }
17175 }
17176
17177 for row in (0..=range.start.row).rev() {
17178 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17179 if crease.range().end.row >= buffer_start_row {
17180 to_fold.push(crease);
17181 if row <= range.start.row {
17182 break;
17183 }
17184 }
17185 }
17186 }
17187 }
17188
17189 self.fold_creases(to_fold, true, window, cx);
17190 } else {
17191 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17192 let buffer_ids = self
17193 .selections
17194 .disjoint_anchor_ranges()
17195 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17196 .collect::<HashSet<_>>();
17197 for buffer_id in buffer_ids {
17198 self.fold_buffer(buffer_id, cx);
17199 }
17200 }
17201 }
17202
17203 pub fn toggle_fold_all(
17204 &mut self,
17205 _: &actions::ToggleFoldAll,
17206 window: &mut Window,
17207 cx: &mut Context<Self>,
17208 ) {
17209 if self.buffer.read(cx).is_singleton() {
17210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17211 let has_folds = display_map
17212 .folds_in_range(0..display_map.buffer_snapshot.len())
17213 .next()
17214 .is_some();
17215
17216 if has_folds {
17217 self.unfold_all(&actions::UnfoldAll, window, cx);
17218 } else {
17219 self.fold_all(&actions::FoldAll, window, cx);
17220 }
17221 } else {
17222 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17223 let should_unfold = buffer_ids
17224 .iter()
17225 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17226
17227 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17228 editor
17229 .update_in(cx, |editor, _, cx| {
17230 for buffer_id in buffer_ids {
17231 if should_unfold {
17232 editor.unfold_buffer(buffer_id, cx);
17233 } else {
17234 editor.fold_buffer(buffer_id, cx);
17235 }
17236 }
17237 })
17238 .ok();
17239 });
17240 }
17241 }
17242
17243 fn fold_at_level(
17244 &mut self,
17245 fold_at: &FoldAtLevel,
17246 window: &mut Window,
17247 cx: &mut Context<Self>,
17248 ) {
17249 if !self.buffer.read(cx).is_singleton() {
17250 return;
17251 }
17252
17253 let fold_at_level = fold_at.0;
17254 let snapshot = self.buffer.read(cx).snapshot(cx);
17255 let mut to_fold = Vec::new();
17256 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17257
17258 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17259 while start_row < end_row {
17260 match self
17261 .snapshot(window, cx)
17262 .crease_for_buffer_row(MultiBufferRow(start_row))
17263 {
17264 Some(crease) => {
17265 let nested_start_row = crease.range().start.row + 1;
17266 let nested_end_row = crease.range().end.row;
17267
17268 if current_level < fold_at_level {
17269 stack.push((nested_start_row, nested_end_row, current_level + 1));
17270 } else if current_level == fold_at_level {
17271 to_fold.push(crease);
17272 }
17273
17274 start_row = nested_end_row + 1;
17275 }
17276 None => start_row += 1,
17277 }
17278 }
17279 }
17280
17281 self.fold_creases(to_fold, true, window, cx);
17282 }
17283
17284 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17285 if self.buffer.read(cx).is_singleton() {
17286 let mut fold_ranges = Vec::new();
17287 let snapshot = self.buffer.read(cx).snapshot(cx);
17288
17289 for row in 0..snapshot.max_row().0 {
17290 if let Some(foldable_range) = self
17291 .snapshot(window, cx)
17292 .crease_for_buffer_row(MultiBufferRow(row))
17293 {
17294 fold_ranges.push(foldable_range);
17295 }
17296 }
17297
17298 self.fold_creases(fold_ranges, true, window, cx);
17299 } else {
17300 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17301 editor
17302 .update_in(cx, |editor, _, cx| {
17303 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17304 editor.fold_buffer(buffer_id, cx);
17305 }
17306 })
17307 .ok();
17308 });
17309 }
17310 }
17311
17312 pub fn fold_function_bodies(
17313 &mut self,
17314 _: &actions::FoldFunctionBodies,
17315 window: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) {
17318 let snapshot = self.buffer.read(cx).snapshot(cx);
17319
17320 let ranges = snapshot
17321 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17322 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17323 .collect::<Vec<_>>();
17324
17325 let creases = ranges
17326 .into_iter()
17327 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17328 .collect();
17329
17330 self.fold_creases(creases, true, window, cx);
17331 }
17332
17333 pub fn fold_recursive(
17334 &mut self,
17335 _: &actions::FoldRecursive,
17336 window: &mut Window,
17337 cx: &mut Context<Self>,
17338 ) {
17339 let mut to_fold = Vec::new();
17340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17341 let selections = self.selections.all_adjusted(cx);
17342
17343 for selection in selections {
17344 let range = selection.range().sorted();
17345 let buffer_start_row = range.start.row;
17346
17347 if range.start.row != range.end.row {
17348 let mut found = false;
17349 for row in range.start.row..=range.end.row {
17350 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17351 found = true;
17352 to_fold.push(crease);
17353 }
17354 }
17355 if found {
17356 continue;
17357 }
17358 }
17359
17360 for row in (0..=range.start.row).rev() {
17361 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17362 if crease.range().end.row >= buffer_start_row {
17363 to_fold.push(crease);
17364 } else {
17365 break;
17366 }
17367 }
17368 }
17369 }
17370
17371 self.fold_creases(to_fold, true, window, cx);
17372 }
17373
17374 pub fn fold_at(
17375 &mut self,
17376 buffer_row: MultiBufferRow,
17377 window: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17381
17382 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17383 let autoscroll = self
17384 .selections
17385 .all::<Point>(cx)
17386 .iter()
17387 .any(|selection| crease.range().overlaps(&selection.range()));
17388
17389 self.fold_creases(vec![crease], autoscroll, window, cx);
17390 }
17391 }
17392
17393 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17394 if self.is_singleton(cx) {
17395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17396 let buffer = &display_map.buffer_snapshot;
17397 let selections = self.selections.all::<Point>(cx);
17398 let ranges = selections
17399 .iter()
17400 .map(|s| {
17401 let range = s.display_range(&display_map).sorted();
17402 let mut start = range.start.to_point(&display_map);
17403 let mut end = range.end.to_point(&display_map);
17404 start.column = 0;
17405 end.column = buffer.line_len(MultiBufferRow(end.row));
17406 start..end
17407 })
17408 .collect::<Vec<_>>();
17409
17410 self.unfold_ranges(&ranges, true, true, cx);
17411 } else {
17412 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17413 let buffer_ids = self
17414 .selections
17415 .disjoint_anchor_ranges()
17416 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17417 .collect::<HashSet<_>>();
17418 for buffer_id in buffer_ids {
17419 self.unfold_buffer(buffer_id, cx);
17420 }
17421 }
17422 }
17423
17424 pub fn unfold_recursive(
17425 &mut self,
17426 _: &UnfoldRecursive,
17427 _window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17431 let selections = self.selections.all::<Point>(cx);
17432 let ranges = selections
17433 .iter()
17434 .map(|s| {
17435 let mut range = s.display_range(&display_map).sorted();
17436 *range.start.column_mut() = 0;
17437 *range.end.column_mut() = display_map.line_len(range.end.row());
17438 let start = range.start.to_point(&display_map);
17439 let end = range.end.to_point(&display_map);
17440 start..end
17441 })
17442 .collect::<Vec<_>>();
17443
17444 self.unfold_ranges(&ranges, true, true, cx);
17445 }
17446
17447 pub fn unfold_at(
17448 &mut self,
17449 buffer_row: MultiBufferRow,
17450 _window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) {
17453 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17454
17455 let intersection_range = Point::new(buffer_row.0, 0)
17456 ..Point::new(
17457 buffer_row.0,
17458 display_map.buffer_snapshot.line_len(buffer_row),
17459 );
17460
17461 let autoscroll = self
17462 .selections
17463 .all::<Point>(cx)
17464 .iter()
17465 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17466
17467 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17468 }
17469
17470 pub fn unfold_all(
17471 &mut self,
17472 _: &actions::UnfoldAll,
17473 _window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 if self.buffer.read(cx).is_singleton() {
17477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17478 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17479 } else {
17480 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17481 editor
17482 .update(cx, |editor, cx| {
17483 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17484 editor.unfold_buffer(buffer_id, cx);
17485 }
17486 })
17487 .ok();
17488 });
17489 }
17490 }
17491
17492 pub fn fold_selected_ranges(
17493 &mut self,
17494 _: &FoldSelectedRanges,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 let selections = self.selections.all_adjusted(cx);
17499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17500 let ranges = selections
17501 .into_iter()
17502 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17503 .collect::<Vec<_>>();
17504 self.fold_creases(ranges, true, window, cx);
17505 }
17506
17507 pub fn fold_ranges<T: ToOffset + Clone>(
17508 &mut self,
17509 ranges: Vec<Range<T>>,
17510 auto_scroll: bool,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17515 let ranges = ranges
17516 .into_iter()
17517 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17518 .collect::<Vec<_>>();
17519 self.fold_creases(ranges, auto_scroll, window, cx);
17520 }
17521
17522 pub fn fold_creases<T: ToOffset + Clone>(
17523 &mut self,
17524 creases: Vec<Crease<T>>,
17525 auto_scroll: bool,
17526 _window: &mut Window,
17527 cx: &mut Context<Self>,
17528 ) {
17529 if creases.is_empty() {
17530 return;
17531 }
17532
17533 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17534
17535 if auto_scroll {
17536 self.request_autoscroll(Autoscroll::fit(), cx);
17537 }
17538
17539 cx.notify();
17540
17541 self.scrollbar_marker_state.dirty = true;
17542 self.folds_did_change(cx);
17543 }
17544
17545 /// Removes any folds whose ranges intersect any of the given ranges.
17546 pub fn unfold_ranges<T: ToOffset + Clone>(
17547 &mut self,
17548 ranges: &[Range<T>],
17549 inclusive: bool,
17550 auto_scroll: bool,
17551 cx: &mut Context<Self>,
17552 ) {
17553 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17554 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17555 });
17556 self.folds_did_change(cx);
17557 }
17558
17559 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17560 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17561 return;
17562 }
17563 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17564 self.display_map.update(cx, |display_map, cx| {
17565 display_map.fold_buffers([buffer_id], cx)
17566 });
17567 cx.emit(EditorEvent::BufferFoldToggled {
17568 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17569 folded: true,
17570 });
17571 cx.notify();
17572 }
17573
17574 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17575 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17576 return;
17577 }
17578 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17579 self.display_map.update(cx, |display_map, cx| {
17580 display_map.unfold_buffers([buffer_id], cx);
17581 });
17582 cx.emit(EditorEvent::BufferFoldToggled {
17583 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17584 folded: false,
17585 });
17586 cx.notify();
17587 }
17588
17589 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17590 self.display_map.read(cx).is_buffer_folded(buffer)
17591 }
17592
17593 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17594 self.display_map.read(cx).folded_buffers()
17595 }
17596
17597 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17598 self.display_map.update(cx, |display_map, cx| {
17599 display_map.disable_header_for_buffer(buffer_id, cx);
17600 });
17601 cx.notify();
17602 }
17603
17604 /// Removes any folds with the given ranges.
17605 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17606 &mut self,
17607 ranges: &[Range<T>],
17608 type_id: TypeId,
17609 auto_scroll: bool,
17610 cx: &mut Context<Self>,
17611 ) {
17612 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17613 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17614 });
17615 self.folds_did_change(cx);
17616 }
17617
17618 fn remove_folds_with<T: ToOffset + Clone>(
17619 &mut self,
17620 ranges: &[Range<T>],
17621 auto_scroll: bool,
17622 cx: &mut Context<Self>,
17623 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17624 ) {
17625 if ranges.is_empty() {
17626 return;
17627 }
17628
17629 let mut buffers_affected = HashSet::default();
17630 let multi_buffer = self.buffer().read(cx);
17631 for range in ranges {
17632 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17633 buffers_affected.insert(buffer.read(cx).remote_id());
17634 };
17635 }
17636
17637 self.display_map.update(cx, update);
17638
17639 if auto_scroll {
17640 self.request_autoscroll(Autoscroll::fit(), cx);
17641 }
17642
17643 cx.notify();
17644 self.scrollbar_marker_state.dirty = true;
17645 self.active_indent_guides_state.dirty = true;
17646 }
17647
17648 pub fn update_renderer_widths(
17649 &mut self,
17650 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17651 cx: &mut Context<Self>,
17652 ) -> bool {
17653 self.display_map
17654 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17655 }
17656
17657 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17658 self.display_map.read(cx).fold_placeholder.clone()
17659 }
17660
17661 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17662 self.buffer.update(cx, |buffer, cx| {
17663 buffer.set_all_diff_hunks_expanded(cx);
17664 });
17665 }
17666
17667 pub fn expand_all_diff_hunks(
17668 &mut self,
17669 _: &ExpandAllDiffHunks,
17670 _window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) {
17673 self.buffer.update(cx, |buffer, cx| {
17674 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17675 });
17676 }
17677
17678 pub fn toggle_selected_diff_hunks(
17679 &mut self,
17680 _: &ToggleSelectedDiffHunks,
17681 _window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) {
17684 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17685 self.toggle_diff_hunks_in_ranges(ranges, cx);
17686 }
17687
17688 pub fn diff_hunks_in_ranges<'a>(
17689 &'a self,
17690 ranges: &'a [Range<Anchor>],
17691 buffer: &'a MultiBufferSnapshot,
17692 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17693 ranges.iter().flat_map(move |range| {
17694 let end_excerpt_id = range.end.excerpt_id;
17695 let range = range.to_point(buffer);
17696 let mut peek_end = range.end;
17697 if range.end.row < buffer.max_row().0 {
17698 peek_end = Point::new(range.end.row + 1, 0);
17699 }
17700 buffer
17701 .diff_hunks_in_range(range.start..peek_end)
17702 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17703 })
17704 }
17705
17706 pub fn has_stageable_diff_hunks_in_ranges(
17707 &self,
17708 ranges: &[Range<Anchor>],
17709 snapshot: &MultiBufferSnapshot,
17710 ) -> bool {
17711 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17712 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17713 }
17714
17715 pub fn toggle_staged_selected_diff_hunks(
17716 &mut self,
17717 _: &::git::ToggleStaged,
17718 _: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) {
17721 let snapshot = self.buffer.read(cx).snapshot(cx);
17722 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17723 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17724 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17725 }
17726
17727 pub fn set_render_diff_hunk_controls(
17728 &mut self,
17729 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17730 cx: &mut Context<Self>,
17731 ) {
17732 self.render_diff_hunk_controls = render_diff_hunk_controls;
17733 cx.notify();
17734 }
17735
17736 pub fn stage_and_next(
17737 &mut self,
17738 _: &::git::StageAndNext,
17739 window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) {
17742 self.do_stage_or_unstage_and_next(true, window, cx);
17743 }
17744
17745 pub fn unstage_and_next(
17746 &mut self,
17747 _: &::git::UnstageAndNext,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) {
17751 self.do_stage_or_unstage_and_next(false, window, cx);
17752 }
17753
17754 pub fn stage_or_unstage_diff_hunks(
17755 &mut self,
17756 stage: bool,
17757 ranges: Vec<Range<Anchor>>,
17758 cx: &mut Context<Self>,
17759 ) {
17760 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17761 cx.spawn(async move |this, cx| {
17762 task.await?;
17763 this.update(cx, |this, cx| {
17764 let snapshot = this.buffer.read(cx).snapshot(cx);
17765 let chunk_by = this
17766 .diff_hunks_in_ranges(&ranges, &snapshot)
17767 .chunk_by(|hunk| hunk.buffer_id);
17768 for (buffer_id, hunks) in &chunk_by {
17769 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17770 }
17771 })
17772 })
17773 .detach_and_log_err(cx);
17774 }
17775
17776 fn save_buffers_for_ranges_if_needed(
17777 &mut self,
17778 ranges: &[Range<Anchor>],
17779 cx: &mut Context<Editor>,
17780 ) -> Task<Result<()>> {
17781 let multibuffer = self.buffer.read(cx);
17782 let snapshot = multibuffer.read(cx);
17783 let buffer_ids: HashSet<_> = ranges
17784 .iter()
17785 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17786 .collect();
17787 drop(snapshot);
17788
17789 let mut buffers = HashSet::default();
17790 for buffer_id in buffer_ids {
17791 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17792 let buffer = buffer_entity.read(cx);
17793 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17794 {
17795 buffers.insert(buffer_entity);
17796 }
17797 }
17798 }
17799
17800 if let Some(project) = &self.project {
17801 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17802 } else {
17803 Task::ready(Ok(()))
17804 }
17805 }
17806
17807 fn do_stage_or_unstage_and_next(
17808 &mut self,
17809 stage: bool,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17814
17815 if ranges.iter().any(|range| range.start != range.end) {
17816 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17817 return;
17818 }
17819
17820 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17821 let snapshot = self.snapshot(window, cx);
17822 let position = self.selections.newest::<Point>(cx).head();
17823 let mut row = snapshot
17824 .buffer_snapshot
17825 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17826 .find(|hunk| hunk.row_range.start.0 > position.row)
17827 .map(|hunk| hunk.row_range.start);
17828
17829 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17830 // Outside of the project diff editor, wrap around to the beginning.
17831 if !all_diff_hunks_expanded {
17832 row = row.or_else(|| {
17833 snapshot
17834 .buffer_snapshot
17835 .diff_hunks_in_range(Point::zero()..position)
17836 .find(|hunk| hunk.row_range.end.0 < position.row)
17837 .map(|hunk| hunk.row_range.start)
17838 });
17839 }
17840
17841 if let Some(row) = row {
17842 let destination = Point::new(row.0, 0);
17843 let autoscroll = Autoscroll::center();
17844
17845 self.unfold_ranges(&[destination..destination], false, false, cx);
17846 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17847 s.select_ranges([destination..destination]);
17848 });
17849 }
17850 }
17851
17852 fn do_stage_or_unstage(
17853 &self,
17854 stage: bool,
17855 buffer_id: BufferId,
17856 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17857 cx: &mut App,
17858 ) -> Option<()> {
17859 let project = self.project.as_ref()?;
17860 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17861 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17862 let buffer_snapshot = buffer.read(cx).snapshot();
17863 let file_exists = buffer_snapshot
17864 .file()
17865 .is_some_and(|file| file.disk_state().exists());
17866 diff.update(cx, |diff, cx| {
17867 diff.stage_or_unstage_hunks(
17868 stage,
17869 &hunks
17870 .map(|hunk| buffer_diff::DiffHunk {
17871 buffer_range: hunk.buffer_range,
17872 diff_base_byte_range: hunk.diff_base_byte_range,
17873 secondary_status: hunk.secondary_status,
17874 range: Point::zero()..Point::zero(), // unused
17875 })
17876 .collect::<Vec<_>>(),
17877 &buffer_snapshot,
17878 file_exists,
17879 cx,
17880 )
17881 });
17882 None
17883 }
17884
17885 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17886 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17887 self.buffer
17888 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17889 }
17890
17891 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17892 self.buffer.update(cx, |buffer, cx| {
17893 let ranges = vec![Anchor::min()..Anchor::max()];
17894 if !buffer.all_diff_hunks_expanded()
17895 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17896 {
17897 buffer.collapse_diff_hunks(ranges, cx);
17898 true
17899 } else {
17900 false
17901 }
17902 })
17903 }
17904
17905 fn toggle_diff_hunks_in_ranges(
17906 &mut self,
17907 ranges: Vec<Range<Anchor>>,
17908 cx: &mut Context<Editor>,
17909 ) {
17910 self.buffer.update(cx, |buffer, cx| {
17911 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17912 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17913 })
17914 }
17915
17916 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17917 self.buffer.update(cx, |buffer, cx| {
17918 let snapshot = buffer.snapshot(cx);
17919 let excerpt_id = range.end.excerpt_id;
17920 let point_range = range.to_point(&snapshot);
17921 let expand = !buffer.single_hunk_is_expanded(range, cx);
17922 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17923 })
17924 }
17925
17926 pub(crate) fn apply_all_diff_hunks(
17927 &mut self,
17928 _: &ApplyAllDiffHunks,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17933
17934 let buffers = self.buffer.read(cx).all_buffers();
17935 for branch_buffer in buffers {
17936 branch_buffer.update(cx, |branch_buffer, cx| {
17937 branch_buffer.merge_into_base(Vec::new(), cx);
17938 });
17939 }
17940
17941 if let Some(project) = self.project.clone() {
17942 self.save(
17943 SaveOptions {
17944 format: true,
17945 autosave: false,
17946 },
17947 project,
17948 window,
17949 cx,
17950 )
17951 .detach_and_log_err(cx);
17952 }
17953 }
17954
17955 pub(crate) fn apply_selected_diff_hunks(
17956 &mut self,
17957 _: &ApplyDiffHunk,
17958 window: &mut Window,
17959 cx: &mut Context<Self>,
17960 ) {
17961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17962 let snapshot = self.snapshot(window, cx);
17963 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17964 let mut ranges_by_buffer = HashMap::default();
17965 self.transact(window, cx, |editor, _window, cx| {
17966 for hunk in hunks {
17967 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17968 ranges_by_buffer
17969 .entry(buffer.clone())
17970 .or_insert_with(Vec::new)
17971 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17972 }
17973 }
17974
17975 for (buffer, ranges) in ranges_by_buffer {
17976 buffer.update(cx, |buffer, cx| {
17977 buffer.merge_into_base(ranges, cx);
17978 });
17979 }
17980 });
17981
17982 if let Some(project) = self.project.clone() {
17983 self.save(
17984 SaveOptions {
17985 format: true,
17986 autosave: false,
17987 },
17988 project,
17989 window,
17990 cx,
17991 )
17992 .detach_and_log_err(cx);
17993 }
17994 }
17995
17996 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17997 if hovered != self.gutter_hovered {
17998 self.gutter_hovered = hovered;
17999 cx.notify();
18000 }
18001 }
18002
18003 pub fn insert_blocks(
18004 &mut self,
18005 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18006 autoscroll: Option<Autoscroll>,
18007 cx: &mut Context<Self>,
18008 ) -> Vec<CustomBlockId> {
18009 let blocks = self
18010 .display_map
18011 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18012 if let Some(autoscroll) = autoscroll {
18013 self.request_autoscroll(autoscroll, cx);
18014 }
18015 cx.notify();
18016 blocks
18017 }
18018
18019 pub fn resize_blocks(
18020 &mut self,
18021 heights: HashMap<CustomBlockId, u32>,
18022 autoscroll: Option<Autoscroll>,
18023 cx: &mut Context<Self>,
18024 ) {
18025 self.display_map
18026 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18027 if let Some(autoscroll) = autoscroll {
18028 self.request_autoscroll(autoscroll, cx);
18029 }
18030 cx.notify();
18031 }
18032
18033 pub fn replace_blocks(
18034 &mut self,
18035 renderers: HashMap<CustomBlockId, RenderBlock>,
18036 autoscroll: Option<Autoscroll>,
18037 cx: &mut Context<Self>,
18038 ) {
18039 self.display_map
18040 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18041 if let Some(autoscroll) = autoscroll {
18042 self.request_autoscroll(autoscroll, cx);
18043 }
18044 cx.notify();
18045 }
18046
18047 pub fn remove_blocks(
18048 &mut self,
18049 block_ids: HashSet<CustomBlockId>,
18050 autoscroll: Option<Autoscroll>,
18051 cx: &mut Context<Self>,
18052 ) {
18053 self.display_map.update(cx, |display_map, cx| {
18054 display_map.remove_blocks(block_ids, cx)
18055 });
18056 if let Some(autoscroll) = autoscroll {
18057 self.request_autoscroll(autoscroll, cx);
18058 }
18059 cx.notify();
18060 }
18061
18062 pub fn row_for_block(
18063 &self,
18064 block_id: CustomBlockId,
18065 cx: &mut Context<Self>,
18066 ) -> Option<DisplayRow> {
18067 self.display_map
18068 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18069 }
18070
18071 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18072 self.focused_block = Some(focused_block);
18073 }
18074
18075 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18076 self.focused_block.take()
18077 }
18078
18079 pub fn insert_creases(
18080 &mut self,
18081 creases: impl IntoIterator<Item = Crease<Anchor>>,
18082 cx: &mut Context<Self>,
18083 ) -> Vec<CreaseId> {
18084 self.display_map
18085 .update(cx, |map, cx| map.insert_creases(creases, cx))
18086 }
18087
18088 pub fn remove_creases(
18089 &mut self,
18090 ids: impl IntoIterator<Item = CreaseId>,
18091 cx: &mut Context<Self>,
18092 ) -> Vec<(CreaseId, Range<Anchor>)> {
18093 self.display_map
18094 .update(cx, |map, cx| map.remove_creases(ids, cx))
18095 }
18096
18097 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18098 self.display_map
18099 .update(cx, |map, cx| map.snapshot(cx))
18100 .longest_row()
18101 }
18102
18103 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18104 self.display_map
18105 .update(cx, |map, cx| map.snapshot(cx))
18106 .max_point()
18107 }
18108
18109 pub fn text(&self, cx: &App) -> String {
18110 self.buffer.read(cx).read(cx).text()
18111 }
18112
18113 pub fn is_empty(&self, cx: &App) -> bool {
18114 self.buffer.read(cx).read(cx).is_empty()
18115 }
18116
18117 pub fn text_option(&self, cx: &App) -> Option<String> {
18118 let text = self.text(cx);
18119 let text = text.trim();
18120
18121 if text.is_empty() {
18122 return None;
18123 }
18124
18125 Some(text.to_string())
18126 }
18127
18128 pub fn set_text(
18129 &mut self,
18130 text: impl Into<Arc<str>>,
18131 window: &mut Window,
18132 cx: &mut Context<Self>,
18133 ) {
18134 self.transact(window, cx, |this, _, cx| {
18135 this.buffer
18136 .read(cx)
18137 .as_singleton()
18138 .expect("you can only call set_text on editors for singleton buffers")
18139 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18140 });
18141 }
18142
18143 pub fn display_text(&self, cx: &mut App) -> String {
18144 self.display_map
18145 .update(cx, |map, cx| map.snapshot(cx))
18146 .text()
18147 }
18148
18149 fn create_minimap(
18150 &self,
18151 minimap_settings: MinimapSettings,
18152 window: &mut Window,
18153 cx: &mut Context<Self>,
18154 ) -> Option<Entity<Self>> {
18155 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18156 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18157 }
18158
18159 fn initialize_new_minimap(
18160 &self,
18161 minimap_settings: MinimapSettings,
18162 window: &mut Window,
18163 cx: &mut Context<Self>,
18164 ) -> Entity<Self> {
18165 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18166
18167 let mut minimap = Editor::new_internal(
18168 EditorMode::Minimap {
18169 parent: cx.weak_entity(),
18170 },
18171 self.buffer.clone(),
18172 None,
18173 Some(self.display_map.clone()),
18174 window,
18175 cx,
18176 );
18177 minimap.scroll_manager.clone_state(&self.scroll_manager);
18178 minimap.set_text_style_refinement(TextStyleRefinement {
18179 font_size: Some(MINIMAP_FONT_SIZE),
18180 font_weight: Some(MINIMAP_FONT_WEIGHT),
18181 ..Default::default()
18182 });
18183 minimap.update_minimap_configuration(minimap_settings, cx);
18184 cx.new(|_| minimap)
18185 }
18186
18187 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18188 let current_line_highlight = minimap_settings
18189 .current_line_highlight
18190 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18191 self.set_current_line_highlight(Some(current_line_highlight));
18192 }
18193
18194 pub fn minimap(&self) -> Option<&Entity<Self>> {
18195 self.minimap
18196 .as_ref()
18197 .filter(|_| self.minimap_visibility.visible())
18198 }
18199
18200 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18201 let mut wrap_guides = smallvec![];
18202
18203 if self.show_wrap_guides == Some(false) {
18204 return wrap_guides;
18205 }
18206
18207 let settings = self.buffer.read(cx).language_settings(cx);
18208 if settings.show_wrap_guides {
18209 match self.soft_wrap_mode(cx) {
18210 SoftWrap::Column(soft_wrap) => {
18211 wrap_guides.push((soft_wrap as usize, true));
18212 }
18213 SoftWrap::Bounded(soft_wrap) => {
18214 wrap_guides.push((soft_wrap as usize, true));
18215 }
18216 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18217 }
18218 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18219 }
18220
18221 wrap_guides
18222 }
18223
18224 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18225 let settings = self.buffer.read(cx).language_settings(cx);
18226 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18227 match mode {
18228 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18229 SoftWrap::None
18230 }
18231 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18232 language_settings::SoftWrap::PreferredLineLength => {
18233 SoftWrap::Column(settings.preferred_line_length)
18234 }
18235 language_settings::SoftWrap::Bounded => {
18236 SoftWrap::Bounded(settings.preferred_line_length)
18237 }
18238 }
18239 }
18240
18241 pub fn set_soft_wrap_mode(
18242 &mut self,
18243 mode: language_settings::SoftWrap,
18244
18245 cx: &mut Context<Self>,
18246 ) {
18247 self.soft_wrap_mode_override = Some(mode);
18248 cx.notify();
18249 }
18250
18251 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18252 self.hard_wrap = hard_wrap;
18253 cx.notify();
18254 }
18255
18256 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18257 self.text_style_refinement = Some(style);
18258 }
18259
18260 /// called by the Element so we know what style we were most recently rendered with.
18261 pub(crate) fn set_style(
18262 &mut self,
18263 style: EditorStyle,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 // We intentionally do not inform the display map about the minimap style
18268 // so that wrapping is not recalculated and stays consistent for the editor
18269 // and its linked minimap.
18270 if !self.mode.is_minimap() {
18271 let rem_size = window.rem_size();
18272 self.display_map.update(cx, |map, cx| {
18273 map.set_font(
18274 style.text.font(),
18275 style.text.font_size.to_pixels(rem_size),
18276 cx,
18277 )
18278 });
18279 }
18280 self.style = Some(style);
18281 }
18282
18283 pub fn style(&self) -> Option<&EditorStyle> {
18284 self.style.as_ref()
18285 }
18286
18287 // Called by the element. This method is not designed to be called outside of the editor
18288 // element's layout code because it does not notify when rewrapping is computed synchronously.
18289 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18290 self.display_map
18291 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18292 }
18293
18294 pub fn set_soft_wrap(&mut self) {
18295 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18296 }
18297
18298 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18299 if self.soft_wrap_mode_override.is_some() {
18300 self.soft_wrap_mode_override.take();
18301 } else {
18302 let soft_wrap = match self.soft_wrap_mode(cx) {
18303 SoftWrap::GitDiff => return,
18304 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18305 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18306 language_settings::SoftWrap::None
18307 }
18308 };
18309 self.soft_wrap_mode_override = Some(soft_wrap);
18310 }
18311 cx.notify();
18312 }
18313
18314 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18315 let Some(workspace) = self.workspace() else {
18316 return;
18317 };
18318 let fs = workspace.read(cx).app_state().fs.clone();
18319 let current_show = TabBarSettings::get_global(cx).show;
18320 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18321 setting.show = Some(!current_show);
18322 });
18323 }
18324
18325 pub fn toggle_indent_guides(
18326 &mut self,
18327 _: &ToggleIndentGuides,
18328 _: &mut Window,
18329 cx: &mut Context<Self>,
18330 ) {
18331 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18332 self.buffer
18333 .read(cx)
18334 .language_settings(cx)
18335 .indent_guides
18336 .enabled
18337 });
18338 self.show_indent_guides = Some(!currently_enabled);
18339 cx.notify();
18340 }
18341
18342 fn should_show_indent_guides(&self) -> Option<bool> {
18343 self.show_indent_guides
18344 }
18345
18346 pub fn toggle_line_numbers(
18347 &mut self,
18348 _: &ToggleLineNumbers,
18349 _: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 let mut editor_settings = EditorSettings::get_global(cx).clone();
18353 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18354 EditorSettings::override_global(editor_settings, cx);
18355 }
18356
18357 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18358 if let Some(show_line_numbers) = self.show_line_numbers {
18359 return show_line_numbers;
18360 }
18361 EditorSettings::get_global(cx).gutter.line_numbers
18362 }
18363
18364 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18365 self.use_relative_line_numbers
18366 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18367 }
18368
18369 pub fn toggle_relative_line_numbers(
18370 &mut self,
18371 _: &ToggleRelativeLineNumbers,
18372 _: &mut Window,
18373 cx: &mut Context<Self>,
18374 ) {
18375 let is_relative = self.should_use_relative_line_numbers(cx);
18376 self.set_relative_line_number(Some(!is_relative), cx)
18377 }
18378
18379 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18380 self.use_relative_line_numbers = is_relative;
18381 cx.notify();
18382 }
18383
18384 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18385 self.show_gutter = show_gutter;
18386 cx.notify();
18387 }
18388
18389 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18390 self.show_scrollbars = ScrollbarAxes {
18391 horizontal: show,
18392 vertical: show,
18393 };
18394 cx.notify();
18395 }
18396
18397 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18398 self.show_scrollbars.vertical = show;
18399 cx.notify();
18400 }
18401
18402 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18403 self.show_scrollbars.horizontal = show;
18404 cx.notify();
18405 }
18406
18407 pub fn set_minimap_visibility(
18408 &mut self,
18409 minimap_visibility: MinimapVisibility,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 if self.minimap_visibility != minimap_visibility {
18414 if minimap_visibility.visible() && self.minimap.is_none() {
18415 let minimap_settings = EditorSettings::get_global(cx).minimap;
18416 self.minimap =
18417 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18418 }
18419 self.minimap_visibility = minimap_visibility;
18420 cx.notify();
18421 }
18422 }
18423
18424 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18425 self.set_show_scrollbars(false, cx);
18426 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18427 }
18428
18429 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18430 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18431 }
18432
18433 /// Normally the text in full mode and auto height editors is padded on the
18434 /// left side by roughly half a character width for improved hit testing.
18435 ///
18436 /// Use this method to disable this for cases where this is not wanted (e.g.
18437 /// if you want to align the editor text with some other text above or below)
18438 /// or if you want to add this padding to single-line editors.
18439 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18440 self.offset_content = offset_content;
18441 cx.notify();
18442 }
18443
18444 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18445 self.show_line_numbers = Some(show_line_numbers);
18446 cx.notify();
18447 }
18448
18449 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18450 self.disable_expand_excerpt_buttons = true;
18451 cx.notify();
18452 }
18453
18454 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18455 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18456 cx.notify();
18457 }
18458
18459 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18460 self.show_code_actions = Some(show_code_actions);
18461 cx.notify();
18462 }
18463
18464 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18465 self.show_runnables = Some(show_runnables);
18466 cx.notify();
18467 }
18468
18469 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18470 self.show_breakpoints = Some(show_breakpoints);
18471 cx.notify();
18472 }
18473
18474 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18475 if self.display_map.read(cx).masked != masked {
18476 self.display_map.update(cx, |map, _| map.masked = masked);
18477 }
18478 cx.notify()
18479 }
18480
18481 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18482 self.show_wrap_guides = Some(show_wrap_guides);
18483 cx.notify();
18484 }
18485
18486 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18487 self.show_indent_guides = Some(show_indent_guides);
18488 cx.notify();
18489 }
18490
18491 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18492 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18493 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18494 if let Some(dir) = file.abs_path(cx).parent() {
18495 return Some(dir.to_owned());
18496 }
18497 }
18498
18499 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18500 return Some(project_path.path.to_path_buf());
18501 }
18502 }
18503
18504 None
18505 }
18506
18507 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18508 self.active_excerpt(cx)?
18509 .1
18510 .read(cx)
18511 .file()
18512 .and_then(|f| f.as_local())
18513 }
18514
18515 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18516 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18517 let buffer = buffer.read(cx);
18518 if let Some(project_path) = buffer.project_path(cx) {
18519 let project = self.project.as_ref()?.read(cx);
18520 project.absolute_path(&project_path, cx)
18521 } else {
18522 buffer
18523 .file()
18524 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18525 }
18526 })
18527 }
18528
18529 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18530 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18531 let project_path = buffer.read(cx).project_path(cx)?;
18532 let project = self.project.as_ref()?.read(cx);
18533 let entry = project.entry_for_path(&project_path, cx)?;
18534 let path = entry.path.to_path_buf();
18535 Some(path)
18536 })
18537 }
18538
18539 pub fn reveal_in_finder(
18540 &mut self,
18541 _: &RevealInFileManager,
18542 _window: &mut Window,
18543 cx: &mut Context<Self>,
18544 ) {
18545 if let Some(target) = self.target_file(cx) {
18546 cx.reveal_path(&target.abs_path(cx));
18547 }
18548 }
18549
18550 pub fn copy_path(
18551 &mut self,
18552 _: &zed_actions::workspace::CopyPath,
18553 _window: &mut Window,
18554 cx: &mut Context<Self>,
18555 ) {
18556 if let Some(path) = self.target_file_abs_path(cx) {
18557 if let Some(path) = path.to_str() {
18558 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18559 }
18560 }
18561 }
18562
18563 pub fn copy_relative_path(
18564 &mut self,
18565 _: &zed_actions::workspace::CopyRelativePath,
18566 _window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 if let Some(path) = self.target_file_path(cx) {
18570 if let Some(path) = path.to_str() {
18571 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18572 }
18573 }
18574 }
18575
18576 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18577 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18578 buffer.read(cx).project_path(cx)
18579 } else {
18580 None
18581 }
18582 }
18583
18584 // Returns true if the editor handled a go-to-line request
18585 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18586 maybe!({
18587 let breakpoint_store = self.breakpoint_store.as_ref()?;
18588
18589 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18590 else {
18591 self.clear_row_highlights::<ActiveDebugLine>();
18592 return None;
18593 };
18594
18595 let position = active_stack_frame.position;
18596 let buffer_id = position.buffer_id?;
18597 let snapshot = self
18598 .project
18599 .as_ref()?
18600 .read(cx)
18601 .buffer_for_id(buffer_id, cx)?
18602 .read(cx)
18603 .snapshot();
18604
18605 let mut handled = false;
18606 for (id, ExcerptRange { context, .. }) in
18607 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18608 {
18609 if context.start.cmp(&position, &snapshot).is_ge()
18610 || context.end.cmp(&position, &snapshot).is_lt()
18611 {
18612 continue;
18613 }
18614 let snapshot = self.buffer.read(cx).snapshot(cx);
18615 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18616
18617 handled = true;
18618 self.clear_row_highlights::<ActiveDebugLine>();
18619
18620 self.go_to_line::<ActiveDebugLine>(
18621 multibuffer_anchor,
18622 Some(cx.theme().colors().editor_debugger_active_line_background),
18623 window,
18624 cx,
18625 );
18626
18627 cx.notify();
18628 }
18629
18630 handled.then_some(())
18631 })
18632 .is_some()
18633 }
18634
18635 pub fn copy_file_name_without_extension(
18636 &mut self,
18637 _: &CopyFileNameWithoutExtension,
18638 _: &mut Window,
18639 cx: &mut Context<Self>,
18640 ) {
18641 if let Some(file) = self.target_file(cx) {
18642 if let Some(file_stem) = file.path().file_stem() {
18643 if let Some(name) = file_stem.to_str() {
18644 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18645 }
18646 }
18647 }
18648 }
18649
18650 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18651 if let Some(file) = self.target_file(cx) {
18652 if let Some(file_name) = file.path().file_name() {
18653 if let Some(name) = file_name.to_str() {
18654 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18655 }
18656 }
18657 }
18658 }
18659
18660 pub fn toggle_git_blame(
18661 &mut self,
18662 _: &::git::Blame,
18663 window: &mut Window,
18664 cx: &mut Context<Self>,
18665 ) {
18666 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18667
18668 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18669 self.start_git_blame(true, window, cx);
18670 }
18671
18672 cx.notify();
18673 }
18674
18675 pub fn toggle_git_blame_inline(
18676 &mut self,
18677 _: &ToggleGitBlameInline,
18678 window: &mut Window,
18679 cx: &mut Context<Self>,
18680 ) {
18681 self.toggle_git_blame_inline_internal(true, window, cx);
18682 cx.notify();
18683 }
18684
18685 pub fn open_git_blame_commit(
18686 &mut self,
18687 _: &OpenGitBlameCommit,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) {
18691 self.open_git_blame_commit_internal(window, cx);
18692 }
18693
18694 fn open_git_blame_commit_internal(
18695 &mut self,
18696 window: &mut Window,
18697 cx: &mut Context<Self>,
18698 ) -> Option<()> {
18699 let blame = self.blame.as_ref()?;
18700 let snapshot = self.snapshot(window, cx);
18701 let cursor = self.selections.newest::<Point>(cx).head();
18702 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18703 let blame_entry = blame
18704 .update(cx, |blame, cx| {
18705 blame
18706 .blame_for_rows(
18707 &[RowInfo {
18708 buffer_id: Some(buffer.remote_id()),
18709 buffer_row: Some(point.row),
18710 ..Default::default()
18711 }],
18712 cx,
18713 )
18714 .next()
18715 })
18716 .flatten()?;
18717 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18718 let repo = blame.read(cx).repository(cx)?;
18719 let workspace = self.workspace()?.downgrade();
18720 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18721 None
18722 }
18723
18724 pub fn git_blame_inline_enabled(&self) -> bool {
18725 self.git_blame_inline_enabled
18726 }
18727
18728 pub fn toggle_selection_menu(
18729 &mut self,
18730 _: &ToggleSelectionMenu,
18731 _: &mut Window,
18732 cx: &mut Context<Self>,
18733 ) {
18734 self.show_selection_menu = self
18735 .show_selection_menu
18736 .map(|show_selections_menu| !show_selections_menu)
18737 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18738
18739 cx.notify();
18740 }
18741
18742 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18743 self.show_selection_menu
18744 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18745 }
18746
18747 fn start_git_blame(
18748 &mut self,
18749 user_triggered: bool,
18750 window: &mut Window,
18751 cx: &mut Context<Self>,
18752 ) {
18753 if let Some(project) = self.project.as_ref() {
18754 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18755 return;
18756 };
18757
18758 if buffer.read(cx).file().is_none() {
18759 return;
18760 }
18761
18762 let focused = self.focus_handle(cx).contains_focused(window, cx);
18763
18764 let project = project.clone();
18765 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18766 self.blame_subscription =
18767 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18768 self.blame = Some(blame);
18769 }
18770 }
18771
18772 fn toggle_git_blame_inline_internal(
18773 &mut self,
18774 user_triggered: bool,
18775 window: &mut Window,
18776 cx: &mut Context<Self>,
18777 ) {
18778 if self.git_blame_inline_enabled {
18779 self.git_blame_inline_enabled = false;
18780 self.show_git_blame_inline = false;
18781 self.show_git_blame_inline_delay_task.take();
18782 } else {
18783 self.git_blame_inline_enabled = true;
18784 self.start_git_blame_inline(user_triggered, window, cx);
18785 }
18786
18787 cx.notify();
18788 }
18789
18790 fn start_git_blame_inline(
18791 &mut self,
18792 user_triggered: bool,
18793 window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) {
18796 self.start_git_blame(user_triggered, window, cx);
18797
18798 if ProjectSettings::get_global(cx)
18799 .git
18800 .inline_blame_delay()
18801 .is_some()
18802 {
18803 self.start_inline_blame_timer(window, cx);
18804 } else {
18805 self.show_git_blame_inline = true
18806 }
18807 }
18808
18809 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18810 self.blame.as_ref()
18811 }
18812
18813 pub fn show_git_blame_gutter(&self) -> bool {
18814 self.show_git_blame_gutter
18815 }
18816
18817 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18818 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18819 }
18820
18821 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18822 self.show_git_blame_inline
18823 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18824 && !self.newest_selection_head_on_empty_line(cx)
18825 && self.has_blame_entries(cx)
18826 }
18827
18828 fn has_blame_entries(&self, cx: &App) -> bool {
18829 self.blame()
18830 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18831 }
18832
18833 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18834 let cursor_anchor = self.selections.newest_anchor().head();
18835
18836 let snapshot = self.buffer.read(cx).snapshot(cx);
18837 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18838
18839 snapshot.line_len(buffer_row) == 0
18840 }
18841
18842 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18843 let buffer_and_selection = maybe!({
18844 let selection = self.selections.newest::<Point>(cx);
18845 let selection_range = selection.range();
18846
18847 let multi_buffer = self.buffer().read(cx);
18848 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18849 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18850
18851 let (buffer, range, _) = if selection.reversed {
18852 buffer_ranges.first()
18853 } else {
18854 buffer_ranges.last()
18855 }?;
18856
18857 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18858 ..text::ToPoint::to_point(&range.end, &buffer).row;
18859 Some((
18860 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18861 selection,
18862 ))
18863 });
18864
18865 let Some((buffer, selection)) = buffer_and_selection else {
18866 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18867 };
18868
18869 let Some(project) = self.project.as_ref() else {
18870 return Task::ready(Err(anyhow!("editor does not have project")));
18871 };
18872
18873 project.update(cx, |project, cx| {
18874 project.get_permalink_to_line(&buffer, selection, cx)
18875 })
18876 }
18877
18878 pub fn copy_permalink_to_line(
18879 &mut self,
18880 _: &CopyPermalinkToLine,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) {
18884 let permalink_task = self.get_permalink_to_line(cx);
18885 let workspace = self.workspace();
18886
18887 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18888 Ok(permalink) => {
18889 cx.update(|_, cx| {
18890 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18891 })
18892 .ok();
18893 }
18894 Err(err) => {
18895 let message = format!("Failed to copy permalink: {err}");
18896
18897 anyhow::Result::<()>::Err(err).log_err();
18898
18899 if let Some(workspace) = workspace {
18900 workspace
18901 .update_in(cx, |workspace, _, cx| {
18902 struct CopyPermalinkToLine;
18903
18904 workspace.show_toast(
18905 Toast::new(
18906 NotificationId::unique::<CopyPermalinkToLine>(),
18907 message,
18908 ),
18909 cx,
18910 )
18911 })
18912 .ok();
18913 }
18914 }
18915 })
18916 .detach();
18917 }
18918
18919 pub fn copy_file_location(
18920 &mut self,
18921 _: &CopyFileLocation,
18922 _: &mut Window,
18923 cx: &mut Context<Self>,
18924 ) {
18925 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18926 if let Some(file) = self.target_file(cx) {
18927 if let Some(path) = file.path().to_str() {
18928 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18929 }
18930 }
18931 }
18932
18933 pub fn open_permalink_to_line(
18934 &mut self,
18935 _: &OpenPermalinkToLine,
18936 window: &mut Window,
18937 cx: &mut Context<Self>,
18938 ) {
18939 let permalink_task = self.get_permalink_to_line(cx);
18940 let workspace = self.workspace();
18941
18942 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18943 Ok(permalink) => {
18944 cx.update(|_, cx| {
18945 cx.open_url(permalink.as_ref());
18946 })
18947 .ok();
18948 }
18949 Err(err) => {
18950 let message = format!("Failed to open permalink: {err}");
18951
18952 anyhow::Result::<()>::Err(err).log_err();
18953
18954 if let Some(workspace) = workspace {
18955 workspace
18956 .update(cx, |workspace, cx| {
18957 struct OpenPermalinkToLine;
18958
18959 workspace.show_toast(
18960 Toast::new(
18961 NotificationId::unique::<OpenPermalinkToLine>(),
18962 message,
18963 ),
18964 cx,
18965 )
18966 })
18967 .ok();
18968 }
18969 }
18970 })
18971 .detach();
18972 }
18973
18974 pub fn insert_uuid_v4(
18975 &mut self,
18976 _: &InsertUuidV4,
18977 window: &mut Window,
18978 cx: &mut Context<Self>,
18979 ) {
18980 self.insert_uuid(UuidVersion::V4, window, cx);
18981 }
18982
18983 pub fn insert_uuid_v7(
18984 &mut self,
18985 _: &InsertUuidV7,
18986 window: &mut Window,
18987 cx: &mut Context<Self>,
18988 ) {
18989 self.insert_uuid(UuidVersion::V7, window, cx);
18990 }
18991
18992 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18994 self.transact(window, cx, |this, window, cx| {
18995 let edits = this
18996 .selections
18997 .all::<Point>(cx)
18998 .into_iter()
18999 .map(|selection| {
19000 let uuid = match version {
19001 UuidVersion::V4 => uuid::Uuid::new_v4(),
19002 UuidVersion::V7 => uuid::Uuid::now_v7(),
19003 };
19004
19005 (selection.range(), uuid.to_string())
19006 });
19007 this.edit(edits, cx);
19008 this.refresh_inline_completion(true, false, window, cx);
19009 });
19010 }
19011
19012 pub fn open_selections_in_multibuffer(
19013 &mut self,
19014 _: &OpenSelectionsInMultibuffer,
19015 window: &mut Window,
19016 cx: &mut Context<Self>,
19017 ) {
19018 let multibuffer = self.buffer.read(cx);
19019
19020 let Some(buffer) = multibuffer.as_singleton() else {
19021 return;
19022 };
19023
19024 let Some(workspace) = self.workspace() else {
19025 return;
19026 };
19027
19028 let title = multibuffer.title(cx).to_string();
19029
19030 let locations = self
19031 .selections
19032 .all_anchors(cx)
19033 .into_iter()
19034 .map(|selection| Location {
19035 buffer: buffer.clone(),
19036 range: selection.start.text_anchor..selection.end.text_anchor,
19037 })
19038 .collect::<Vec<_>>();
19039
19040 cx.spawn_in(window, async move |_, cx| {
19041 workspace.update_in(cx, |workspace, window, cx| {
19042 Self::open_locations_in_multibuffer(
19043 workspace,
19044 locations,
19045 format!("Selections for '{title}'"),
19046 false,
19047 MultibufferSelectionMode::All,
19048 window,
19049 cx,
19050 );
19051 })
19052 })
19053 .detach();
19054 }
19055
19056 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19057 /// last highlight added will be used.
19058 ///
19059 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19060 pub fn highlight_rows<T: 'static>(
19061 &mut self,
19062 range: Range<Anchor>,
19063 color: Hsla,
19064 options: RowHighlightOptions,
19065 cx: &mut Context<Self>,
19066 ) {
19067 let snapshot = self.buffer().read(cx).snapshot(cx);
19068 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19069 let ix = row_highlights.binary_search_by(|highlight| {
19070 Ordering::Equal
19071 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19072 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19073 });
19074
19075 if let Err(mut ix) = ix {
19076 let index = post_inc(&mut self.highlight_order);
19077
19078 // If this range intersects with the preceding highlight, then merge it with
19079 // the preceding highlight. Otherwise insert a new highlight.
19080 let mut merged = false;
19081 if ix > 0 {
19082 let prev_highlight = &mut row_highlights[ix - 1];
19083 if prev_highlight
19084 .range
19085 .end
19086 .cmp(&range.start, &snapshot)
19087 .is_ge()
19088 {
19089 ix -= 1;
19090 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19091 prev_highlight.range.end = range.end;
19092 }
19093 merged = true;
19094 prev_highlight.index = index;
19095 prev_highlight.color = color;
19096 prev_highlight.options = options;
19097 }
19098 }
19099
19100 if !merged {
19101 row_highlights.insert(
19102 ix,
19103 RowHighlight {
19104 range: range.clone(),
19105 index,
19106 color,
19107 options,
19108 type_id: TypeId::of::<T>(),
19109 },
19110 );
19111 }
19112
19113 // If any of the following highlights intersect with this one, merge them.
19114 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19115 let highlight = &row_highlights[ix];
19116 if next_highlight
19117 .range
19118 .start
19119 .cmp(&highlight.range.end, &snapshot)
19120 .is_le()
19121 {
19122 if next_highlight
19123 .range
19124 .end
19125 .cmp(&highlight.range.end, &snapshot)
19126 .is_gt()
19127 {
19128 row_highlights[ix].range.end = next_highlight.range.end;
19129 }
19130 row_highlights.remove(ix + 1);
19131 } else {
19132 break;
19133 }
19134 }
19135 }
19136 }
19137
19138 /// Remove any highlighted row ranges of the given type that intersect the
19139 /// given ranges.
19140 pub fn remove_highlighted_rows<T: 'static>(
19141 &mut self,
19142 ranges_to_remove: Vec<Range<Anchor>>,
19143 cx: &mut Context<Self>,
19144 ) {
19145 let snapshot = self.buffer().read(cx).snapshot(cx);
19146 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19147 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19148 row_highlights.retain(|highlight| {
19149 while let Some(range_to_remove) = ranges_to_remove.peek() {
19150 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19151 Ordering::Less | Ordering::Equal => {
19152 ranges_to_remove.next();
19153 }
19154 Ordering::Greater => {
19155 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19156 Ordering::Less | Ordering::Equal => {
19157 return false;
19158 }
19159 Ordering::Greater => break,
19160 }
19161 }
19162 }
19163 }
19164
19165 true
19166 })
19167 }
19168
19169 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19170 pub fn clear_row_highlights<T: 'static>(&mut self) {
19171 self.highlighted_rows.remove(&TypeId::of::<T>());
19172 }
19173
19174 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19175 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19176 self.highlighted_rows
19177 .get(&TypeId::of::<T>())
19178 .map_or(&[] as &[_], |vec| vec.as_slice())
19179 .iter()
19180 .map(|highlight| (highlight.range.clone(), highlight.color))
19181 }
19182
19183 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19184 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19185 /// Allows to ignore certain kinds of highlights.
19186 pub fn highlighted_display_rows(
19187 &self,
19188 window: &mut Window,
19189 cx: &mut App,
19190 ) -> BTreeMap<DisplayRow, LineHighlight> {
19191 let snapshot = self.snapshot(window, cx);
19192 let mut used_highlight_orders = HashMap::default();
19193 self.highlighted_rows
19194 .iter()
19195 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19196 .fold(
19197 BTreeMap::<DisplayRow, LineHighlight>::new(),
19198 |mut unique_rows, highlight| {
19199 let start = highlight.range.start.to_display_point(&snapshot);
19200 let end = highlight.range.end.to_display_point(&snapshot);
19201 let start_row = start.row().0;
19202 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19203 && end.column() == 0
19204 {
19205 end.row().0.saturating_sub(1)
19206 } else {
19207 end.row().0
19208 };
19209 for row in start_row..=end_row {
19210 let used_index =
19211 used_highlight_orders.entry(row).or_insert(highlight.index);
19212 if highlight.index >= *used_index {
19213 *used_index = highlight.index;
19214 unique_rows.insert(
19215 DisplayRow(row),
19216 LineHighlight {
19217 include_gutter: highlight.options.include_gutter,
19218 border: None,
19219 background: highlight.color.into(),
19220 type_id: Some(highlight.type_id),
19221 },
19222 );
19223 }
19224 }
19225 unique_rows
19226 },
19227 )
19228 }
19229
19230 pub fn highlighted_display_row_for_autoscroll(
19231 &self,
19232 snapshot: &DisplaySnapshot,
19233 ) -> Option<DisplayRow> {
19234 self.highlighted_rows
19235 .values()
19236 .flat_map(|highlighted_rows| highlighted_rows.iter())
19237 .filter_map(|highlight| {
19238 if highlight.options.autoscroll {
19239 Some(highlight.range.start.to_display_point(snapshot).row())
19240 } else {
19241 None
19242 }
19243 })
19244 .min()
19245 }
19246
19247 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19248 self.highlight_background::<SearchWithinRange>(
19249 ranges,
19250 |colors| colors.colors().editor_document_highlight_read_background,
19251 cx,
19252 )
19253 }
19254
19255 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19256 self.breadcrumb_header = Some(new_header);
19257 }
19258
19259 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19260 self.clear_background_highlights::<SearchWithinRange>(cx);
19261 }
19262
19263 pub fn highlight_background<T: 'static>(
19264 &mut self,
19265 ranges: &[Range<Anchor>],
19266 color_fetcher: fn(&Theme) -> Hsla,
19267 cx: &mut Context<Self>,
19268 ) {
19269 self.background_highlights.insert(
19270 HighlightKey::Type(TypeId::of::<T>()),
19271 (color_fetcher, Arc::from(ranges)),
19272 );
19273 self.scrollbar_marker_state.dirty = true;
19274 cx.notify();
19275 }
19276
19277 pub fn highlight_background_key<T: 'static>(
19278 &mut self,
19279 key: usize,
19280 ranges: &[Range<Anchor>],
19281 color_fetcher: fn(&Theme) -> Hsla,
19282 cx: &mut Context<Self>,
19283 ) {
19284 self.background_highlights.insert(
19285 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19286 (color_fetcher, Arc::from(ranges)),
19287 );
19288 self.scrollbar_marker_state.dirty = true;
19289 cx.notify();
19290 }
19291
19292 pub fn clear_background_highlights<T: 'static>(
19293 &mut self,
19294 cx: &mut Context<Self>,
19295 ) -> Option<BackgroundHighlight> {
19296 let text_highlights = self
19297 .background_highlights
19298 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19299 if !text_highlights.1.is_empty() {
19300 self.scrollbar_marker_state.dirty = true;
19301 cx.notify();
19302 }
19303 Some(text_highlights)
19304 }
19305
19306 pub fn highlight_gutter<T: 'static>(
19307 &mut self,
19308 ranges: impl Into<Vec<Range<Anchor>>>,
19309 color_fetcher: fn(&App) -> Hsla,
19310 cx: &mut Context<Self>,
19311 ) {
19312 self.gutter_highlights
19313 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19314 cx.notify();
19315 }
19316
19317 pub fn clear_gutter_highlights<T: 'static>(
19318 &mut self,
19319 cx: &mut Context<Self>,
19320 ) -> Option<GutterHighlight> {
19321 cx.notify();
19322 self.gutter_highlights.remove(&TypeId::of::<T>())
19323 }
19324
19325 pub fn insert_gutter_highlight<T: 'static>(
19326 &mut self,
19327 range: Range<Anchor>,
19328 color_fetcher: fn(&App) -> Hsla,
19329 cx: &mut Context<Self>,
19330 ) {
19331 let snapshot = self.buffer().read(cx).snapshot(cx);
19332 let mut highlights = self
19333 .gutter_highlights
19334 .remove(&TypeId::of::<T>())
19335 .map(|(_, highlights)| highlights)
19336 .unwrap_or_default();
19337 let ix = highlights.binary_search_by(|highlight| {
19338 Ordering::Equal
19339 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19340 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19341 });
19342 if let Err(ix) = ix {
19343 highlights.insert(ix, range);
19344 }
19345 self.gutter_highlights
19346 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19347 }
19348
19349 pub fn remove_gutter_highlights<T: 'static>(
19350 &mut self,
19351 ranges_to_remove: Vec<Range<Anchor>>,
19352 cx: &mut Context<Self>,
19353 ) {
19354 let snapshot = self.buffer().read(cx).snapshot(cx);
19355 let Some((color_fetcher, mut gutter_highlights)) =
19356 self.gutter_highlights.remove(&TypeId::of::<T>())
19357 else {
19358 return;
19359 };
19360 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19361 gutter_highlights.retain(|highlight| {
19362 while let Some(range_to_remove) = ranges_to_remove.peek() {
19363 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19364 Ordering::Less | Ordering::Equal => {
19365 ranges_to_remove.next();
19366 }
19367 Ordering::Greater => {
19368 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19369 Ordering::Less | Ordering::Equal => {
19370 return false;
19371 }
19372 Ordering::Greater => break,
19373 }
19374 }
19375 }
19376 }
19377
19378 true
19379 });
19380 self.gutter_highlights
19381 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19382 }
19383
19384 #[cfg(feature = "test-support")]
19385 pub fn all_text_highlights(
19386 &self,
19387 window: &mut Window,
19388 cx: &mut Context<Self>,
19389 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19390 let snapshot = self.snapshot(window, cx);
19391 self.display_map.update(cx, |display_map, _| {
19392 display_map
19393 .all_text_highlights()
19394 .map(|highlight| {
19395 let (style, ranges) = highlight.as_ref();
19396 (
19397 *style,
19398 ranges
19399 .iter()
19400 .map(|range| range.clone().to_display_points(&snapshot))
19401 .collect(),
19402 )
19403 })
19404 .collect()
19405 })
19406 }
19407
19408 #[cfg(feature = "test-support")]
19409 pub fn all_text_background_highlights(
19410 &self,
19411 window: &mut Window,
19412 cx: &mut Context<Self>,
19413 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19414 let snapshot = self.snapshot(window, cx);
19415 let buffer = &snapshot.buffer_snapshot;
19416 let start = buffer.anchor_before(0);
19417 let end = buffer.anchor_after(buffer.len());
19418 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19419 }
19420
19421 #[cfg(feature = "test-support")]
19422 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19423 let snapshot = self.buffer().read(cx).snapshot(cx);
19424
19425 let highlights = self
19426 .background_highlights
19427 .get(&HighlightKey::Type(TypeId::of::<
19428 items::BufferSearchHighlights,
19429 >()));
19430
19431 if let Some((_color, ranges)) = highlights {
19432 ranges
19433 .iter()
19434 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19435 .collect_vec()
19436 } else {
19437 vec![]
19438 }
19439 }
19440
19441 fn document_highlights_for_position<'a>(
19442 &'a self,
19443 position: Anchor,
19444 buffer: &'a MultiBufferSnapshot,
19445 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19446 let read_highlights = self
19447 .background_highlights
19448 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19449 .map(|h| &h.1);
19450 let write_highlights = self
19451 .background_highlights
19452 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19453 .map(|h| &h.1);
19454 let left_position = position.bias_left(buffer);
19455 let right_position = position.bias_right(buffer);
19456 read_highlights
19457 .into_iter()
19458 .chain(write_highlights)
19459 .flat_map(move |ranges| {
19460 let start_ix = match ranges.binary_search_by(|probe| {
19461 let cmp = probe.end.cmp(&left_position, buffer);
19462 if cmp.is_ge() {
19463 Ordering::Greater
19464 } else {
19465 Ordering::Less
19466 }
19467 }) {
19468 Ok(i) | Err(i) => i,
19469 };
19470
19471 ranges[start_ix..]
19472 .iter()
19473 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19474 })
19475 }
19476
19477 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19478 self.background_highlights
19479 .get(&HighlightKey::Type(TypeId::of::<T>()))
19480 .map_or(false, |(_, highlights)| !highlights.is_empty())
19481 }
19482
19483 pub fn background_highlights_in_range(
19484 &self,
19485 search_range: Range<Anchor>,
19486 display_snapshot: &DisplaySnapshot,
19487 theme: &Theme,
19488 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19489 let mut results = Vec::new();
19490 for (color_fetcher, ranges) in self.background_highlights.values() {
19491 let color = color_fetcher(theme);
19492 let start_ix = match ranges.binary_search_by(|probe| {
19493 let cmp = probe
19494 .end
19495 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19496 if cmp.is_gt() {
19497 Ordering::Greater
19498 } else {
19499 Ordering::Less
19500 }
19501 }) {
19502 Ok(i) | Err(i) => i,
19503 };
19504 for range in &ranges[start_ix..] {
19505 if range
19506 .start
19507 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19508 .is_ge()
19509 {
19510 break;
19511 }
19512
19513 let start = range.start.to_display_point(display_snapshot);
19514 let end = range.end.to_display_point(display_snapshot);
19515 results.push((start..end, color))
19516 }
19517 }
19518 results
19519 }
19520
19521 pub fn background_highlight_row_ranges<T: 'static>(
19522 &self,
19523 search_range: Range<Anchor>,
19524 display_snapshot: &DisplaySnapshot,
19525 count: usize,
19526 ) -> Vec<RangeInclusive<DisplayPoint>> {
19527 let mut results = Vec::new();
19528 let Some((_, ranges)) = self
19529 .background_highlights
19530 .get(&HighlightKey::Type(TypeId::of::<T>()))
19531 else {
19532 return vec![];
19533 };
19534
19535 let start_ix = match ranges.binary_search_by(|probe| {
19536 let cmp = probe
19537 .end
19538 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19539 if cmp.is_gt() {
19540 Ordering::Greater
19541 } else {
19542 Ordering::Less
19543 }
19544 }) {
19545 Ok(i) | Err(i) => i,
19546 };
19547 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19548 if let (Some(start_display), Some(end_display)) = (start, end) {
19549 results.push(
19550 start_display.to_display_point(display_snapshot)
19551 ..=end_display.to_display_point(display_snapshot),
19552 );
19553 }
19554 };
19555 let mut start_row: Option<Point> = None;
19556 let mut end_row: Option<Point> = None;
19557 if ranges.len() > count {
19558 return Vec::new();
19559 }
19560 for range in &ranges[start_ix..] {
19561 if range
19562 .start
19563 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19564 .is_ge()
19565 {
19566 break;
19567 }
19568 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19569 if let Some(current_row) = &end_row {
19570 if end.row == current_row.row {
19571 continue;
19572 }
19573 }
19574 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19575 if start_row.is_none() {
19576 assert_eq!(end_row, None);
19577 start_row = Some(start);
19578 end_row = Some(end);
19579 continue;
19580 }
19581 if let Some(current_end) = end_row.as_mut() {
19582 if start.row > current_end.row + 1 {
19583 push_region(start_row, end_row);
19584 start_row = Some(start);
19585 end_row = Some(end);
19586 } else {
19587 // Merge two hunks.
19588 *current_end = end;
19589 }
19590 } else {
19591 unreachable!();
19592 }
19593 }
19594 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19595 push_region(start_row, end_row);
19596 results
19597 }
19598
19599 pub fn gutter_highlights_in_range(
19600 &self,
19601 search_range: Range<Anchor>,
19602 display_snapshot: &DisplaySnapshot,
19603 cx: &App,
19604 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19605 let mut results = Vec::new();
19606 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19607 let color = color_fetcher(cx);
19608 let start_ix = match ranges.binary_search_by(|probe| {
19609 let cmp = probe
19610 .end
19611 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19612 if cmp.is_gt() {
19613 Ordering::Greater
19614 } else {
19615 Ordering::Less
19616 }
19617 }) {
19618 Ok(i) | Err(i) => i,
19619 };
19620 for range in &ranges[start_ix..] {
19621 if range
19622 .start
19623 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19624 .is_ge()
19625 {
19626 break;
19627 }
19628
19629 let start = range.start.to_display_point(display_snapshot);
19630 let end = range.end.to_display_point(display_snapshot);
19631 results.push((start..end, color))
19632 }
19633 }
19634 results
19635 }
19636
19637 /// Get the text ranges corresponding to the redaction query
19638 pub fn redacted_ranges(
19639 &self,
19640 search_range: Range<Anchor>,
19641 display_snapshot: &DisplaySnapshot,
19642 cx: &App,
19643 ) -> Vec<Range<DisplayPoint>> {
19644 display_snapshot
19645 .buffer_snapshot
19646 .redacted_ranges(search_range, |file| {
19647 if let Some(file) = file {
19648 file.is_private()
19649 && EditorSettings::get(
19650 Some(SettingsLocation {
19651 worktree_id: file.worktree_id(cx),
19652 path: file.path().as_ref(),
19653 }),
19654 cx,
19655 )
19656 .redact_private_values
19657 } else {
19658 false
19659 }
19660 })
19661 .map(|range| {
19662 range.start.to_display_point(display_snapshot)
19663 ..range.end.to_display_point(display_snapshot)
19664 })
19665 .collect()
19666 }
19667
19668 pub fn highlight_text_key<T: 'static>(
19669 &mut self,
19670 key: usize,
19671 ranges: Vec<Range<Anchor>>,
19672 style: HighlightStyle,
19673 cx: &mut Context<Self>,
19674 ) {
19675 self.display_map.update(cx, |map, _| {
19676 map.highlight_text(
19677 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19678 ranges,
19679 style,
19680 );
19681 });
19682 cx.notify();
19683 }
19684
19685 pub fn highlight_text<T: 'static>(
19686 &mut self,
19687 ranges: Vec<Range<Anchor>>,
19688 style: HighlightStyle,
19689 cx: &mut Context<Self>,
19690 ) {
19691 self.display_map.update(cx, |map, _| {
19692 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19693 });
19694 cx.notify();
19695 }
19696
19697 pub(crate) fn highlight_inlays<T: 'static>(
19698 &mut self,
19699 highlights: Vec<InlayHighlight>,
19700 style: HighlightStyle,
19701 cx: &mut Context<Self>,
19702 ) {
19703 self.display_map.update(cx, |map, _| {
19704 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19705 });
19706 cx.notify();
19707 }
19708
19709 pub fn text_highlights<'a, T: 'static>(
19710 &'a self,
19711 cx: &'a App,
19712 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19713 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19714 }
19715
19716 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19717 let cleared = self
19718 .display_map
19719 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19720 if cleared {
19721 cx.notify();
19722 }
19723 }
19724
19725 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19726 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19727 && self.focus_handle.is_focused(window)
19728 }
19729
19730 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19731 self.show_cursor_when_unfocused = is_enabled;
19732 cx.notify();
19733 }
19734
19735 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19736 cx.notify();
19737 }
19738
19739 fn on_debug_session_event(
19740 &mut self,
19741 _session: Entity<Session>,
19742 event: &SessionEvent,
19743 cx: &mut Context<Self>,
19744 ) {
19745 match event {
19746 SessionEvent::InvalidateInlineValue => {
19747 self.refresh_inline_values(cx);
19748 }
19749 _ => {}
19750 }
19751 }
19752
19753 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19754 let Some(project) = self.project.clone() else {
19755 return;
19756 };
19757
19758 if !self.inline_value_cache.enabled {
19759 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19760 self.splice_inlays(&inlays, Vec::new(), cx);
19761 return;
19762 }
19763
19764 let current_execution_position = self
19765 .highlighted_rows
19766 .get(&TypeId::of::<ActiveDebugLine>())
19767 .and_then(|lines| lines.last().map(|line| line.range.end));
19768
19769 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19770 let inline_values = editor
19771 .update(cx, |editor, cx| {
19772 let Some(current_execution_position) = current_execution_position else {
19773 return Some(Task::ready(Ok(Vec::new())));
19774 };
19775
19776 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19777 let snapshot = buffer.snapshot(cx);
19778
19779 let excerpt = snapshot.excerpt_containing(
19780 current_execution_position..current_execution_position,
19781 )?;
19782
19783 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19784 })?;
19785
19786 let range =
19787 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19788
19789 project.inline_values(buffer, range, cx)
19790 })
19791 .ok()
19792 .flatten()?
19793 .await
19794 .context("refreshing debugger inlays")
19795 .log_err()?;
19796
19797 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19798
19799 for (buffer_id, inline_value) in inline_values
19800 .into_iter()
19801 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19802 {
19803 buffer_inline_values
19804 .entry(buffer_id)
19805 .or_default()
19806 .push(inline_value);
19807 }
19808
19809 editor
19810 .update(cx, |editor, cx| {
19811 let snapshot = editor.buffer.read(cx).snapshot(cx);
19812 let mut new_inlays = Vec::default();
19813
19814 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19815 let buffer_id = buffer_snapshot.remote_id();
19816 buffer_inline_values
19817 .get(&buffer_id)
19818 .into_iter()
19819 .flatten()
19820 .for_each(|hint| {
19821 let inlay = Inlay::debugger(
19822 post_inc(&mut editor.next_inlay_id),
19823 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19824 hint.text(),
19825 );
19826 if !inlay.text.chars().contains(&'\n') {
19827 new_inlays.push(inlay);
19828 }
19829 });
19830 }
19831
19832 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19833 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19834
19835 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19836 })
19837 .ok()?;
19838 Some(())
19839 });
19840 }
19841
19842 fn on_buffer_event(
19843 &mut self,
19844 multibuffer: &Entity<MultiBuffer>,
19845 event: &multi_buffer::Event,
19846 window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 match event {
19850 multi_buffer::Event::Edited {
19851 singleton_buffer_edited,
19852 edited_buffer,
19853 } => {
19854 self.scrollbar_marker_state.dirty = true;
19855 self.active_indent_guides_state.dirty = true;
19856 self.refresh_active_diagnostics(cx);
19857 self.refresh_code_actions(window, cx);
19858 self.refresh_selected_text_highlights(true, window, cx);
19859 self.refresh_single_line_folds(window, cx);
19860 refresh_matching_bracket_highlights(self, window, cx);
19861 if self.has_active_inline_completion() {
19862 self.update_visible_inline_completion(window, cx);
19863 }
19864 if let Some(project) = self.project.as_ref() {
19865 if let Some(edited_buffer) = edited_buffer {
19866 project.update(cx, |project, cx| {
19867 self.registered_buffers
19868 .entry(edited_buffer.read(cx).remote_id())
19869 .or_insert_with(|| {
19870 project
19871 .register_buffer_with_language_servers(&edited_buffer, cx)
19872 });
19873 });
19874 }
19875 }
19876 cx.emit(EditorEvent::BufferEdited);
19877 cx.emit(SearchEvent::MatchesInvalidated);
19878
19879 if let Some(buffer) = edited_buffer {
19880 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19881 }
19882
19883 if *singleton_buffer_edited {
19884 if let Some(buffer) = edited_buffer {
19885 if buffer.read(cx).file().is_none() {
19886 cx.emit(EditorEvent::TitleChanged);
19887 }
19888 }
19889 if let Some(project) = &self.project {
19890 #[allow(clippy::mutable_key_type)]
19891 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19892 multibuffer
19893 .all_buffers()
19894 .into_iter()
19895 .filter_map(|buffer| {
19896 buffer.update(cx, |buffer, cx| {
19897 let language = buffer.language()?;
19898 let should_discard = project.update(cx, |project, cx| {
19899 project.is_local()
19900 && !project.has_language_servers_for(buffer, cx)
19901 });
19902 should_discard.not().then_some(language.clone())
19903 })
19904 })
19905 .collect::<HashSet<_>>()
19906 });
19907 if !languages_affected.is_empty() {
19908 self.refresh_inlay_hints(
19909 InlayHintRefreshReason::BufferEdited(languages_affected),
19910 cx,
19911 );
19912 }
19913 }
19914 }
19915
19916 let Some(project) = &self.project else { return };
19917 let (telemetry, is_via_ssh) = {
19918 let project = project.read(cx);
19919 let telemetry = project.client().telemetry().clone();
19920 let is_via_ssh = project.is_via_ssh();
19921 (telemetry, is_via_ssh)
19922 };
19923 refresh_linked_ranges(self, window, cx);
19924 telemetry.log_edit_event("editor", is_via_ssh);
19925 }
19926 multi_buffer::Event::ExcerptsAdded {
19927 buffer,
19928 predecessor,
19929 excerpts,
19930 } => {
19931 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19932 let buffer_id = buffer.read(cx).remote_id();
19933 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19934 if let Some(project) = &self.project {
19935 update_uncommitted_diff_for_buffer(
19936 cx.entity(),
19937 project,
19938 [buffer.clone()],
19939 self.buffer.clone(),
19940 cx,
19941 )
19942 .detach();
19943 }
19944 }
19945 self.update_lsp_data(false, Some(buffer_id), window, cx);
19946 cx.emit(EditorEvent::ExcerptsAdded {
19947 buffer: buffer.clone(),
19948 predecessor: *predecessor,
19949 excerpts: excerpts.clone(),
19950 });
19951 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19952 }
19953 multi_buffer::Event::ExcerptsRemoved {
19954 ids,
19955 removed_buffer_ids,
19956 } => {
19957 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19958 let buffer = self.buffer.read(cx);
19959 self.registered_buffers
19960 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19961 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19962 cx.emit(EditorEvent::ExcerptsRemoved {
19963 ids: ids.clone(),
19964 removed_buffer_ids: removed_buffer_ids.clone(),
19965 });
19966 }
19967 multi_buffer::Event::ExcerptsEdited {
19968 excerpt_ids,
19969 buffer_ids,
19970 } => {
19971 self.display_map.update(cx, |map, cx| {
19972 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19973 });
19974 cx.emit(EditorEvent::ExcerptsEdited {
19975 ids: excerpt_ids.clone(),
19976 });
19977 }
19978 multi_buffer::Event::ExcerptsExpanded { ids } => {
19979 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19980 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19981 }
19982 multi_buffer::Event::Reparsed(buffer_id) => {
19983 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19984 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19985
19986 cx.emit(EditorEvent::Reparsed(*buffer_id));
19987 }
19988 multi_buffer::Event::DiffHunksToggled => {
19989 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19990 }
19991 multi_buffer::Event::LanguageChanged(buffer_id) => {
19992 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19993 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19994 cx.emit(EditorEvent::Reparsed(*buffer_id));
19995 cx.notify();
19996 }
19997 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19998 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19999 multi_buffer::Event::FileHandleChanged
20000 | multi_buffer::Event::Reloaded
20001 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20002 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20003 multi_buffer::Event::DiagnosticsUpdated => {
20004 self.update_diagnostics_state(window, cx);
20005 }
20006 _ => {}
20007 };
20008 }
20009
20010 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20011 if !self.diagnostics_enabled() {
20012 return;
20013 }
20014 self.refresh_active_diagnostics(cx);
20015 self.refresh_inline_diagnostics(true, window, cx);
20016 self.scrollbar_marker_state.dirty = true;
20017 cx.notify();
20018 }
20019
20020 pub fn start_temporary_diff_override(&mut self) {
20021 self.load_diff_task.take();
20022 self.temporary_diff_override = true;
20023 }
20024
20025 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20026 self.temporary_diff_override = false;
20027 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20028 self.buffer.update(cx, |buffer, cx| {
20029 buffer.set_all_diff_hunks_collapsed(cx);
20030 });
20031
20032 if let Some(project) = self.project.clone() {
20033 self.load_diff_task = Some(
20034 update_uncommitted_diff_for_buffer(
20035 cx.entity(),
20036 &project,
20037 self.buffer.read(cx).all_buffers(),
20038 self.buffer.clone(),
20039 cx,
20040 )
20041 .shared(),
20042 );
20043 }
20044 }
20045
20046 fn on_display_map_changed(
20047 &mut self,
20048 _: Entity<DisplayMap>,
20049 _: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 cx.notify();
20053 }
20054
20055 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20056 if self.diagnostics_enabled() {
20057 let new_severity = EditorSettings::get_global(cx)
20058 .diagnostics_max_severity
20059 .unwrap_or(DiagnosticSeverity::Hint);
20060 self.set_max_diagnostics_severity(new_severity, cx);
20061 }
20062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20063 self.update_edit_prediction_settings(cx);
20064 self.refresh_inline_completion(true, false, window, cx);
20065 self.refresh_inline_values(cx);
20066 self.refresh_inlay_hints(
20067 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20068 self.selections.newest_anchor().head(),
20069 &self.buffer.read(cx).snapshot(cx),
20070 cx,
20071 )),
20072 cx,
20073 );
20074
20075 let old_cursor_shape = self.cursor_shape;
20076
20077 {
20078 let editor_settings = EditorSettings::get_global(cx);
20079 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20080 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20081 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20082 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20083 }
20084
20085 if old_cursor_shape != self.cursor_shape {
20086 cx.emit(EditorEvent::CursorShapeChanged);
20087 }
20088
20089 let project_settings = ProjectSettings::get_global(cx);
20090 self.serialize_dirty_buffers =
20091 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20092
20093 if self.mode.is_full() {
20094 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20095 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20096 if self.show_inline_diagnostics != show_inline_diagnostics {
20097 self.show_inline_diagnostics = show_inline_diagnostics;
20098 self.refresh_inline_diagnostics(false, window, cx);
20099 }
20100
20101 if self.git_blame_inline_enabled != inline_blame_enabled {
20102 self.toggle_git_blame_inline_internal(false, window, cx);
20103 }
20104
20105 let minimap_settings = EditorSettings::get_global(cx).minimap;
20106 if self.minimap_visibility != MinimapVisibility::Disabled {
20107 if self.minimap_visibility.settings_visibility()
20108 != minimap_settings.minimap_enabled()
20109 {
20110 self.set_minimap_visibility(
20111 MinimapVisibility::for_mode(self.mode(), cx),
20112 window,
20113 cx,
20114 );
20115 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20116 minimap_entity.update(cx, |minimap_editor, cx| {
20117 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20118 })
20119 }
20120 }
20121 }
20122
20123 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20124 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20125 }) {
20126 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20127 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20128 }
20129 self.refresh_colors(false, None, window, cx);
20130 }
20131
20132 cx.notify();
20133 }
20134
20135 pub fn set_searchable(&mut self, searchable: bool) {
20136 self.searchable = searchable;
20137 }
20138
20139 pub fn searchable(&self) -> bool {
20140 self.searchable
20141 }
20142
20143 fn open_proposed_changes_editor(
20144 &mut self,
20145 _: &OpenProposedChangesEditor,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 let Some(workspace) = self.workspace() else {
20150 cx.propagate();
20151 return;
20152 };
20153
20154 let selections = self.selections.all::<usize>(cx);
20155 let multi_buffer = self.buffer.read(cx);
20156 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20157 let mut new_selections_by_buffer = HashMap::default();
20158 for selection in selections {
20159 for (buffer, range, _) in
20160 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20161 {
20162 let mut range = range.to_point(buffer);
20163 range.start.column = 0;
20164 range.end.column = buffer.line_len(range.end.row);
20165 new_selections_by_buffer
20166 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20167 .or_insert(Vec::new())
20168 .push(range)
20169 }
20170 }
20171
20172 let proposed_changes_buffers = new_selections_by_buffer
20173 .into_iter()
20174 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20175 .collect::<Vec<_>>();
20176 let proposed_changes_editor = cx.new(|cx| {
20177 ProposedChangesEditor::new(
20178 "Proposed changes",
20179 proposed_changes_buffers,
20180 self.project.clone(),
20181 window,
20182 cx,
20183 )
20184 });
20185
20186 window.defer(cx, move |window, cx| {
20187 workspace.update(cx, |workspace, cx| {
20188 workspace.active_pane().update(cx, |pane, cx| {
20189 pane.add_item(
20190 Box::new(proposed_changes_editor),
20191 true,
20192 true,
20193 None,
20194 window,
20195 cx,
20196 );
20197 });
20198 });
20199 });
20200 }
20201
20202 pub fn open_excerpts_in_split(
20203 &mut self,
20204 _: &OpenExcerptsSplit,
20205 window: &mut Window,
20206 cx: &mut Context<Self>,
20207 ) {
20208 self.open_excerpts_common(None, true, window, cx)
20209 }
20210
20211 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20212 self.open_excerpts_common(None, false, window, cx)
20213 }
20214
20215 fn open_excerpts_common(
20216 &mut self,
20217 jump_data: Option<JumpData>,
20218 split: bool,
20219 window: &mut Window,
20220 cx: &mut Context<Self>,
20221 ) {
20222 let Some(workspace) = self.workspace() else {
20223 cx.propagate();
20224 return;
20225 };
20226
20227 if self.buffer.read(cx).is_singleton() {
20228 cx.propagate();
20229 return;
20230 }
20231
20232 let mut new_selections_by_buffer = HashMap::default();
20233 match &jump_data {
20234 Some(JumpData::MultiBufferPoint {
20235 excerpt_id,
20236 position,
20237 anchor,
20238 line_offset_from_top,
20239 }) => {
20240 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20241 if let Some(buffer) = multi_buffer_snapshot
20242 .buffer_id_for_excerpt(*excerpt_id)
20243 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20244 {
20245 let buffer_snapshot = buffer.read(cx).snapshot();
20246 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20247 language::ToPoint::to_point(anchor, &buffer_snapshot)
20248 } else {
20249 buffer_snapshot.clip_point(*position, Bias::Left)
20250 };
20251 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20252 new_selections_by_buffer.insert(
20253 buffer,
20254 (
20255 vec![jump_to_offset..jump_to_offset],
20256 Some(*line_offset_from_top),
20257 ),
20258 );
20259 }
20260 }
20261 Some(JumpData::MultiBufferRow {
20262 row,
20263 line_offset_from_top,
20264 }) => {
20265 let point = MultiBufferPoint::new(row.0, 0);
20266 if let Some((buffer, buffer_point, _)) =
20267 self.buffer.read(cx).point_to_buffer_point(point, cx)
20268 {
20269 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20270 new_selections_by_buffer
20271 .entry(buffer)
20272 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20273 .0
20274 .push(buffer_offset..buffer_offset)
20275 }
20276 }
20277 None => {
20278 let selections = self.selections.all::<usize>(cx);
20279 let multi_buffer = self.buffer.read(cx);
20280 for selection in selections {
20281 for (snapshot, range, _, anchor) in multi_buffer
20282 .snapshot(cx)
20283 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20284 {
20285 if let Some(anchor) = anchor {
20286 // selection is in a deleted hunk
20287 let Some(buffer_id) = anchor.buffer_id else {
20288 continue;
20289 };
20290 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20291 continue;
20292 };
20293 let offset = text::ToOffset::to_offset(
20294 &anchor.text_anchor,
20295 &buffer_handle.read(cx).snapshot(),
20296 );
20297 let range = offset..offset;
20298 new_selections_by_buffer
20299 .entry(buffer_handle)
20300 .or_insert((Vec::new(), None))
20301 .0
20302 .push(range)
20303 } else {
20304 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20305 else {
20306 continue;
20307 };
20308 new_selections_by_buffer
20309 .entry(buffer_handle)
20310 .or_insert((Vec::new(), None))
20311 .0
20312 .push(range)
20313 }
20314 }
20315 }
20316 }
20317 }
20318
20319 new_selections_by_buffer
20320 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20321
20322 if new_selections_by_buffer.is_empty() {
20323 return;
20324 }
20325
20326 // We defer the pane interaction because we ourselves are a workspace item
20327 // and activating a new item causes the pane to call a method on us reentrantly,
20328 // which panics if we're on the stack.
20329 window.defer(cx, move |window, cx| {
20330 workspace.update(cx, |workspace, cx| {
20331 let pane = if split {
20332 workspace.adjacent_pane(window, cx)
20333 } else {
20334 workspace.active_pane().clone()
20335 };
20336
20337 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20338 let editor = buffer
20339 .read(cx)
20340 .file()
20341 .is_none()
20342 .then(|| {
20343 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20344 // so `workspace.open_project_item` will never find them, always opening a new editor.
20345 // Instead, we try to activate the existing editor in the pane first.
20346 let (editor, pane_item_index) =
20347 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20348 let editor = item.downcast::<Editor>()?;
20349 let singleton_buffer =
20350 editor.read(cx).buffer().read(cx).as_singleton()?;
20351 if singleton_buffer == buffer {
20352 Some((editor, i))
20353 } else {
20354 None
20355 }
20356 })?;
20357 pane.update(cx, |pane, cx| {
20358 pane.activate_item(pane_item_index, true, true, window, cx)
20359 });
20360 Some(editor)
20361 })
20362 .flatten()
20363 .unwrap_or_else(|| {
20364 workspace.open_project_item::<Self>(
20365 pane.clone(),
20366 buffer,
20367 true,
20368 true,
20369 window,
20370 cx,
20371 )
20372 });
20373
20374 editor.update(cx, |editor, cx| {
20375 let autoscroll = match scroll_offset {
20376 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20377 None => Autoscroll::newest(),
20378 };
20379 let nav_history = editor.nav_history.take();
20380 editor.change_selections(
20381 SelectionEffects::scroll(autoscroll),
20382 window,
20383 cx,
20384 |s| {
20385 s.select_ranges(ranges);
20386 },
20387 );
20388 editor.nav_history = nav_history;
20389 });
20390 }
20391 })
20392 });
20393 }
20394
20395 // For now, don't allow opening excerpts in buffers that aren't backed by
20396 // regular project files.
20397 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20398 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20399 }
20400
20401 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20402 let snapshot = self.buffer.read(cx).read(cx);
20403 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20404 Some(
20405 ranges
20406 .iter()
20407 .map(move |range| {
20408 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20409 })
20410 .collect(),
20411 )
20412 }
20413
20414 fn selection_replacement_ranges(
20415 &self,
20416 range: Range<OffsetUtf16>,
20417 cx: &mut App,
20418 ) -> Vec<Range<OffsetUtf16>> {
20419 let selections = self.selections.all::<OffsetUtf16>(cx);
20420 let newest_selection = selections
20421 .iter()
20422 .max_by_key(|selection| selection.id)
20423 .unwrap();
20424 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20425 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20426 let snapshot = self.buffer.read(cx).read(cx);
20427 selections
20428 .into_iter()
20429 .map(|mut selection| {
20430 selection.start.0 =
20431 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20432 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20433 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20434 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20435 })
20436 .collect()
20437 }
20438
20439 fn report_editor_event(
20440 &self,
20441 event_type: &'static str,
20442 file_extension: Option<String>,
20443 cx: &App,
20444 ) {
20445 if cfg!(any(test, feature = "test-support")) {
20446 return;
20447 }
20448
20449 let Some(project) = &self.project else { return };
20450
20451 // If None, we are in a file without an extension
20452 let file = self
20453 .buffer
20454 .read(cx)
20455 .as_singleton()
20456 .and_then(|b| b.read(cx).file());
20457 let file_extension = file_extension.or(file
20458 .as_ref()
20459 .and_then(|file| Path::new(file.file_name(cx)).extension())
20460 .and_then(|e| e.to_str())
20461 .map(|a| a.to_string()));
20462
20463 let vim_mode = vim_enabled(cx);
20464
20465 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20466 let copilot_enabled = edit_predictions_provider
20467 == language::language_settings::EditPredictionProvider::Copilot;
20468 let copilot_enabled_for_language = self
20469 .buffer
20470 .read(cx)
20471 .language_settings(cx)
20472 .show_edit_predictions;
20473
20474 let project = project.read(cx);
20475 telemetry::event!(
20476 event_type,
20477 file_extension,
20478 vim_mode,
20479 copilot_enabled,
20480 copilot_enabled_for_language,
20481 edit_predictions_provider,
20482 is_via_ssh = project.is_via_ssh(),
20483 );
20484 }
20485
20486 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20487 /// with each line being an array of {text, highlight} objects.
20488 fn copy_highlight_json(
20489 &mut self,
20490 _: &CopyHighlightJson,
20491 window: &mut Window,
20492 cx: &mut Context<Self>,
20493 ) {
20494 #[derive(Serialize)]
20495 struct Chunk<'a> {
20496 text: String,
20497 highlight: Option<&'a str>,
20498 }
20499
20500 let snapshot = self.buffer.read(cx).snapshot(cx);
20501 let range = self
20502 .selected_text_range(false, window, cx)
20503 .and_then(|selection| {
20504 if selection.range.is_empty() {
20505 None
20506 } else {
20507 Some(selection.range)
20508 }
20509 })
20510 .unwrap_or_else(|| 0..snapshot.len());
20511
20512 let chunks = snapshot.chunks(range, true);
20513 let mut lines = Vec::new();
20514 let mut line: VecDeque<Chunk> = VecDeque::new();
20515
20516 let Some(style) = self.style.as_ref() else {
20517 return;
20518 };
20519
20520 for chunk in chunks {
20521 let highlight = chunk
20522 .syntax_highlight_id
20523 .and_then(|id| id.name(&style.syntax));
20524 let mut chunk_lines = chunk.text.split('\n').peekable();
20525 while let Some(text) = chunk_lines.next() {
20526 let mut merged_with_last_token = false;
20527 if let Some(last_token) = line.back_mut() {
20528 if last_token.highlight == highlight {
20529 last_token.text.push_str(text);
20530 merged_with_last_token = true;
20531 }
20532 }
20533
20534 if !merged_with_last_token {
20535 line.push_back(Chunk {
20536 text: text.into(),
20537 highlight,
20538 });
20539 }
20540
20541 if chunk_lines.peek().is_some() {
20542 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20543 line.pop_front();
20544 }
20545 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20546 line.pop_back();
20547 }
20548
20549 lines.push(mem::take(&mut line));
20550 }
20551 }
20552 }
20553
20554 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20555 return;
20556 };
20557 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20558 }
20559
20560 pub fn open_context_menu(
20561 &mut self,
20562 _: &OpenContextMenu,
20563 window: &mut Window,
20564 cx: &mut Context<Self>,
20565 ) {
20566 self.request_autoscroll(Autoscroll::newest(), cx);
20567 let position = self.selections.newest_display(cx).start;
20568 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20569 }
20570
20571 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20572 &self.inlay_hint_cache
20573 }
20574
20575 pub fn replay_insert_event(
20576 &mut self,
20577 text: &str,
20578 relative_utf16_range: Option<Range<isize>>,
20579 window: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) {
20582 if !self.input_enabled {
20583 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20584 return;
20585 }
20586 if let Some(relative_utf16_range) = relative_utf16_range {
20587 let selections = self.selections.all::<OffsetUtf16>(cx);
20588 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20589 let new_ranges = selections.into_iter().map(|range| {
20590 let start = OffsetUtf16(
20591 range
20592 .head()
20593 .0
20594 .saturating_add_signed(relative_utf16_range.start),
20595 );
20596 let end = OffsetUtf16(
20597 range
20598 .head()
20599 .0
20600 .saturating_add_signed(relative_utf16_range.end),
20601 );
20602 start..end
20603 });
20604 s.select_ranges(new_ranges);
20605 });
20606 }
20607
20608 self.handle_input(text, window, cx);
20609 }
20610
20611 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20612 let Some(provider) = self.semantics_provider.as_ref() else {
20613 return false;
20614 };
20615
20616 let mut supports = false;
20617 self.buffer().update(cx, |this, cx| {
20618 this.for_each_buffer(|buffer| {
20619 supports |= provider.supports_inlay_hints(buffer, cx);
20620 });
20621 });
20622
20623 supports
20624 }
20625
20626 pub fn is_focused(&self, window: &Window) -> bool {
20627 self.focus_handle.is_focused(window)
20628 }
20629
20630 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20631 cx.emit(EditorEvent::Focused);
20632
20633 if let Some(descendant) = self
20634 .last_focused_descendant
20635 .take()
20636 .and_then(|descendant| descendant.upgrade())
20637 {
20638 window.focus(&descendant);
20639 } else {
20640 if let Some(blame) = self.blame.as_ref() {
20641 blame.update(cx, GitBlame::focus)
20642 }
20643
20644 self.blink_manager.update(cx, BlinkManager::enable);
20645 self.show_cursor_names(window, cx);
20646 self.buffer.update(cx, |buffer, cx| {
20647 buffer.finalize_last_transaction(cx);
20648 if self.leader_id.is_none() {
20649 buffer.set_active_selections(
20650 &self.selections.disjoint_anchors(),
20651 self.selections.line_mode,
20652 self.cursor_shape,
20653 cx,
20654 );
20655 }
20656 });
20657 }
20658 }
20659
20660 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20661 cx.emit(EditorEvent::FocusedIn)
20662 }
20663
20664 fn handle_focus_out(
20665 &mut self,
20666 event: FocusOutEvent,
20667 _window: &mut Window,
20668 cx: &mut Context<Self>,
20669 ) {
20670 if event.blurred != self.focus_handle {
20671 self.last_focused_descendant = Some(event.blurred);
20672 }
20673 self.selection_drag_state = SelectionDragState::None;
20674 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20675 }
20676
20677 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20678 self.blink_manager.update(cx, BlinkManager::disable);
20679 self.buffer
20680 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20681
20682 if let Some(blame) = self.blame.as_ref() {
20683 blame.update(cx, GitBlame::blur)
20684 }
20685 if !self.hover_state.focused(window, cx) {
20686 hide_hover(self, cx);
20687 }
20688 if !self
20689 .context_menu
20690 .borrow()
20691 .as_ref()
20692 .is_some_and(|context_menu| context_menu.focused(window, cx))
20693 {
20694 self.hide_context_menu(window, cx);
20695 }
20696 self.discard_inline_completion(false, cx);
20697 cx.emit(EditorEvent::Blurred);
20698 cx.notify();
20699 }
20700
20701 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20702 let mut pending: String = window
20703 .pending_input_keystrokes()
20704 .into_iter()
20705 .flatten()
20706 .filter_map(|keystroke| {
20707 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20708 keystroke.key_char.clone()
20709 } else {
20710 None
20711 }
20712 })
20713 .collect();
20714
20715 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20716 pending = "".to_string();
20717 }
20718
20719 let existing_pending = self
20720 .text_highlights::<PendingInput>(cx)
20721 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20722 if existing_pending.is_none() && pending.is_empty() {
20723 return;
20724 }
20725 let transaction =
20726 self.transact(window, cx, |this, window, cx| {
20727 let selections = this.selections.all::<usize>(cx);
20728 let edits = selections
20729 .iter()
20730 .map(|selection| (selection.end..selection.end, pending.clone()));
20731 this.edit(edits, cx);
20732 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20733 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20734 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20735 }));
20736 });
20737 if let Some(existing_ranges) = existing_pending {
20738 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20739 this.edit(edits, cx);
20740 }
20741 });
20742
20743 let snapshot = self.snapshot(window, cx);
20744 let ranges = self
20745 .selections
20746 .all::<usize>(cx)
20747 .into_iter()
20748 .map(|selection| {
20749 snapshot.buffer_snapshot.anchor_after(selection.end)
20750 ..snapshot
20751 .buffer_snapshot
20752 .anchor_before(selection.end + pending.len())
20753 })
20754 .collect();
20755
20756 if pending.is_empty() {
20757 self.clear_highlights::<PendingInput>(cx);
20758 } else {
20759 self.highlight_text::<PendingInput>(
20760 ranges,
20761 HighlightStyle {
20762 underline: Some(UnderlineStyle {
20763 thickness: px(1.),
20764 color: None,
20765 wavy: false,
20766 }),
20767 ..Default::default()
20768 },
20769 cx,
20770 );
20771 }
20772
20773 self.ime_transaction = self.ime_transaction.or(transaction);
20774 if let Some(transaction) = self.ime_transaction {
20775 self.buffer.update(cx, |buffer, cx| {
20776 buffer.group_until_transaction(transaction, cx);
20777 });
20778 }
20779
20780 if self.text_highlights::<PendingInput>(cx).is_none() {
20781 self.ime_transaction.take();
20782 }
20783 }
20784
20785 pub fn register_action_renderer(
20786 &mut self,
20787 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20788 ) -> Subscription {
20789 let id = self.next_editor_action_id.post_inc();
20790 self.editor_actions
20791 .borrow_mut()
20792 .insert(id, Box::new(listener));
20793
20794 let editor_actions = self.editor_actions.clone();
20795 Subscription::new(move || {
20796 editor_actions.borrow_mut().remove(&id);
20797 })
20798 }
20799
20800 pub fn register_action<A: Action>(
20801 &mut self,
20802 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20803 ) -> Subscription {
20804 let id = self.next_editor_action_id.post_inc();
20805 let listener = Arc::new(listener);
20806 self.editor_actions.borrow_mut().insert(
20807 id,
20808 Box::new(move |_, window, _| {
20809 let listener = listener.clone();
20810 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20811 let action = action.downcast_ref().unwrap();
20812 if phase == DispatchPhase::Bubble {
20813 listener(action, window, cx)
20814 }
20815 })
20816 }),
20817 );
20818
20819 let editor_actions = self.editor_actions.clone();
20820 Subscription::new(move || {
20821 editor_actions.borrow_mut().remove(&id);
20822 })
20823 }
20824
20825 pub fn file_header_size(&self) -> u32 {
20826 FILE_HEADER_HEIGHT
20827 }
20828
20829 pub fn restore(
20830 &mut self,
20831 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 let workspace = self.workspace();
20836 let project = self.project.as_ref();
20837 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20838 let mut tasks = Vec::new();
20839 for (buffer_id, changes) in revert_changes {
20840 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20841 buffer.update(cx, |buffer, cx| {
20842 buffer.edit(
20843 changes
20844 .into_iter()
20845 .map(|(range, text)| (range, text.to_string())),
20846 None,
20847 cx,
20848 );
20849 });
20850
20851 if let Some(project) =
20852 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20853 {
20854 project.update(cx, |project, cx| {
20855 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20856 })
20857 }
20858 }
20859 }
20860 tasks
20861 });
20862 cx.spawn_in(window, async move |_, cx| {
20863 for (buffer, task) in save_tasks {
20864 let result = task.await;
20865 if result.is_err() {
20866 let Some(path) = buffer
20867 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20868 .ok()
20869 else {
20870 continue;
20871 };
20872 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20873 let Some(task) = cx
20874 .update_window_entity(&workspace, |workspace, window, cx| {
20875 workspace
20876 .open_path_preview(path, None, false, false, false, window, cx)
20877 })
20878 .ok()
20879 else {
20880 continue;
20881 };
20882 task.await.log_err();
20883 }
20884 }
20885 }
20886 })
20887 .detach();
20888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20889 selections.refresh()
20890 });
20891 }
20892
20893 pub fn to_pixel_point(
20894 &self,
20895 source: multi_buffer::Anchor,
20896 editor_snapshot: &EditorSnapshot,
20897 window: &mut Window,
20898 ) -> Option<gpui::Point<Pixels>> {
20899 let source_point = source.to_display_point(editor_snapshot);
20900 self.display_to_pixel_point(source_point, editor_snapshot, window)
20901 }
20902
20903 pub fn display_to_pixel_point(
20904 &self,
20905 source: DisplayPoint,
20906 editor_snapshot: &EditorSnapshot,
20907 window: &mut Window,
20908 ) -> Option<gpui::Point<Pixels>> {
20909 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20910 let text_layout_details = self.text_layout_details(window);
20911 let scroll_top = text_layout_details
20912 .scroll_anchor
20913 .scroll_position(editor_snapshot)
20914 .y;
20915
20916 if source.row().as_f32() < scroll_top.floor() {
20917 return None;
20918 }
20919 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20920 let source_y = line_height * (source.row().as_f32() - scroll_top);
20921 Some(gpui::Point::new(source_x, source_y))
20922 }
20923
20924 pub fn has_visible_completions_menu(&self) -> bool {
20925 !self.edit_prediction_preview_is_active()
20926 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20927 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20928 })
20929 }
20930
20931 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20932 if self.mode.is_minimap() {
20933 return;
20934 }
20935 self.addons
20936 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20937 }
20938
20939 pub fn unregister_addon<T: Addon>(&mut self) {
20940 self.addons.remove(&std::any::TypeId::of::<T>());
20941 }
20942
20943 pub fn addon<T: Addon>(&self) -> Option<&T> {
20944 let type_id = std::any::TypeId::of::<T>();
20945 self.addons
20946 .get(&type_id)
20947 .and_then(|item| item.to_any().downcast_ref::<T>())
20948 }
20949
20950 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20951 let type_id = std::any::TypeId::of::<T>();
20952 self.addons
20953 .get_mut(&type_id)
20954 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20955 }
20956
20957 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20958 let text_layout_details = self.text_layout_details(window);
20959 let style = &text_layout_details.editor_style;
20960 let font_id = window.text_system().resolve_font(&style.text.font());
20961 let font_size = style.text.font_size.to_pixels(window.rem_size());
20962 let line_height = style.text.line_height_in_pixels(window.rem_size());
20963 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20964 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20965
20966 CharacterDimensions {
20967 em_width,
20968 em_advance,
20969 line_height,
20970 }
20971 }
20972
20973 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20974 self.load_diff_task.clone()
20975 }
20976
20977 fn read_metadata_from_db(
20978 &mut self,
20979 item_id: u64,
20980 workspace_id: WorkspaceId,
20981 window: &mut Window,
20982 cx: &mut Context<Editor>,
20983 ) {
20984 if self.is_singleton(cx)
20985 && !self.mode.is_minimap()
20986 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20987 {
20988 let buffer_snapshot = OnceCell::new();
20989
20990 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20991 if !folds.is_empty() {
20992 let snapshot =
20993 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20994 self.fold_ranges(
20995 folds
20996 .into_iter()
20997 .map(|(start, end)| {
20998 snapshot.clip_offset(start, Bias::Left)
20999 ..snapshot.clip_offset(end, Bias::Right)
21000 })
21001 .collect(),
21002 false,
21003 window,
21004 cx,
21005 );
21006 }
21007 }
21008
21009 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21010 if !selections.is_empty() {
21011 let snapshot =
21012 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21013 // skip adding the initial selection to selection history
21014 self.selection_history.mode = SelectionHistoryMode::Skipping;
21015 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21016 s.select_ranges(selections.into_iter().map(|(start, end)| {
21017 snapshot.clip_offset(start, Bias::Left)
21018 ..snapshot.clip_offset(end, Bias::Right)
21019 }));
21020 });
21021 self.selection_history.mode = SelectionHistoryMode::Normal;
21022 }
21023 };
21024 }
21025
21026 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21027 }
21028
21029 fn update_lsp_data(
21030 &mut self,
21031 ignore_cache: bool,
21032 for_buffer: Option<BufferId>,
21033 window: &mut Window,
21034 cx: &mut Context<'_, Self>,
21035 ) {
21036 self.pull_diagnostics(for_buffer, window, cx);
21037 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21038 }
21039}
21040
21041fn vim_enabled(cx: &App) -> bool {
21042 cx.global::<SettingsStore>()
21043 .raw_user_settings()
21044 .get("vim_mode")
21045 == Some(&serde_json::Value::Bool(true))
21046}
21047
21048fn process_completion_for_edit(
21049 completion: &Completion,
21050 intent: CompletionIntent,
21051 buffer: &Entity<Buffer>,
21052 cursor_position: &text::Anchor,
21053 cx: &mut Context<Editor>,
21054) -> CompletionEdit {
21055 let buffer = buffer.read(cx);
21056 let buffer_snapshot = buffer.snapshot();
21057 let (snippet, new_text) = if completion.is_snippet() {
21058 // Workaround for typescript language server issues so that methods don't expand within
21059 // strings and functions with type expressions. The previous point is used because the query
21060 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21061 let mut snippet_source = completion.new_text.clone();
21062 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21063 previous_point.column = previous_point.column.saturating_sub(1);
21064 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21065 if scope.prefers_label_for_snippet_in_completion() {
21066 if let Some(label) = completion.label() {
21067 if matches!(
21068 completion.kind(),
21069 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21070 ) {
21071 snippet_source = label;
21072 }
21073 }
21074 }
21075 }
21076 match Snippet::parse(&snippet_source).log_err() {
21077 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21078 None => (None, completion.new_text.clone()),
21079 }
21080 } else {
21081 (None, completion.new_text.clone())
21082 };
21083
21084 let mut range_to_replace = {
21085 let replace_range = &completion.replace_range;
21086 if let CompletionSource::Lsp {
21087 insert_range: Some(insert_range),
21088 ..
21089 } = &completion.source
21090 {
21091 debug_assert_eq!(
21092 insert_range.start, replace_range.start,
21093 "insert_range and replace_range should start at the same position"
21094 );
21095 debug_assert!(
21096 insert_range
21097 .start
21098 .cmp(&cursor_position, &buffer_snapshot)
21099 .is_le(),
21100 "insert_range should start before or at cursor position"
21101 );
21102 debug_assert!(
21103 replace_range
21104 .start
21105 .cmp(&cursor_position, &buffer_snapshot)
21106 .is_le(),
21107 "replace_range should start before or at cursor position"
21108 );
21109 debug_assert!(
21110 insert_range
21111 .end
21112 .cmp(&cursor_position, &buffer_snapshot)
21113 .is_le(),
21114 "insert_range should end before or at cursor position"
21115 );
21116
21117 let should_replace = match intent {
21118 CompletionIntent::CompleteWithInsert => false,
21119 CompletionIntent::CompleteWithReplace => true,
21120 CompletionIntent::Complete | CompletionIntent::Compose => {
21121 let insert_mode =
21122 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21123 .completions
21124 .lsp_insert_mode;
21125 match insert_mode {
21126 LspInsertMode::Insert => false,
21127 LspInsertMode::Replace => true,
21128 LspInsertMode::ReplaceSubsequence => {
21129 let mut text_to_replace = buffer.chars_for_range(
21130 buffer.anchor_before(replace_range.start)
21131 ..buffer.anchor_after(replace_range.end),
21132 );
21133 let mut current_needle = text_to_replace.next();
21134 for haystack_ch in completion.label.text.chars() {
21135 if let Some(needle_ch) = current_needle {
21136 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21137 current_needle = text_to_replace.next();
21138 }
21139 }
21140 }
21141 current_needle.is_none()
21142 }
21143 LspInsertMode::ReplaceSuffix => {
21144 if replace_range
21145 .end
21146 .cmp(&cursor_position, &buffer_snapshot)
21147 .is_gt()
21148 {
21149 let range_after_cursor = *cursor_position..replace_range.end;
21150 let text_after_cursor = buffer
21151 .text_for_range(
21152 buffer.anchor_before(range_after_cursor.start)
21153 ..buffer.anchor_after(range_after_cursor.end),
21154 )
21155 .collect::<String>()
21156 .to_ascii_lowercase();
21157 completion
21158 .label
21159 .text
21160 .to_ascii_lowercase()
21161 .ends_with(&text_after_cursor)
21162 } else {
21163 true
21164 }
21165 }
21166 }
21167 }
21168 };
21169
21170 if should_replace {
21171 replace_range.clone()
21172 } else {
21173 insert_range.clone()
21174 }
21175 } else {
21176 replace_range.clone()
21177 }
21178 };
21179
21180 if range_to_replace
21181 .end
21182 .cmp(&cursor_position, &buffer_snapshot)
21183 .is_lt()
21184 {
21185 range_to_replace.end = *cursor_position;
21186 }
21187
21188 CompletionEdit {
21189 new_text,
21190 replace_range: range_to_replace.to_offset(&buffer),
21191 snippet,
21192 }
21193}
21194
21195struct CompletionEdit {
21196 new_text: String,
21197 replace_range: Range<usize>,
21198 snippet: Option<Snippet>,
21199}
21200
21201fn insert_extra_newline_brackets(
21202 buffer: &MultiBufferSnapshot,
21203 range: Range<usize>,
21204 language: &language::LanguageScope,
21205) -> bool {
21206 let leading_whitespace_len = buffer
21207 .reversed_chars_at(range.start)
21208 .take_while(|c| c.is_whitespace() && *c != '\n')
21209 .map(|c| c.len_utf8())
21210 .sum::<usize>();
21211 let trailing_whitespace_len = buffer
21212 .chars_at(range.end)
21213 .take_while(|c| c.is_whitespace() && *c != '\n')
21214 .map(|c| c.len_utf8())
21215 .sum::<usize>();
21216 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21217
21218 language.brackets().any(|(pair, enabled)| {
21219 let pair_start = pair.start.trim_end();
21220 let pair_end = pair.end.trim_start();
21221
21222 enabled
21223 && pair.newline
21224 && buffer.contains_str_at(range.end, pair_end)
21225 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21226 })
21227}
21228
21229fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21230 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21231 [(buffer, range, _)] => (*buffer, range.clone()),
21232 _ => return false,
21233 };
21234 let pair = {
21235 let mut result: Option<BracketMatch> = None;
21236
21237 for pair in buffer
21238 .all_bracket_ranges(range.clone())
21239 .filter(move |pair| {
21240 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21241 })
21242 {
21243 let len = pair.close_range.end - pair.open_range.start;
21244
21245 if let Some(existing) = &result {
21246 let existing_len = existing.close_range.end - existing.open_range.start;
21247 if len > existing_len {
21248 continue;
21249 }
21250 }
21251
21252 result = Some(pair);
21253 }
21254
21255 result
21256 };
21257 let Some(pair) = pair else {
21258 return false;
21259 };
21260 pair.newline_only
21261 && buffer
21262 .chars_for_range(pair.open_range.end..range.start)
21263 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21264 .all(|c| c.is_whitespace() && c != '\n')
21265}
21266
21267fn update_uncommitted_diff_for_buffer(
21268 editor: Entity<Editor>,
21269 project: &Entity<Project>,
21270 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21271 buffer: Entity<MultiBuffer>,
21272 cx: &mut App,
21273) -> Task<()> {
21274 let mut tasks = Vec::new();
21275 project.update(cx, |project, cx| {
21276 for buffer in buffers {
21277 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21278 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21279 }
21280 }
21281 });
21282 cx.spawn(async move |cx| {
21283 let diffs = future::join_all(tasks).await;
21284 if editor
21285 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21286 .unwrap_or(false)
21287 {
21288 return;
21289 }
21290
21291 buffer
21292 .update(cx, |buffer, cx| {
21293 for diff in diffs.into_iter().flatten() {
21294 buffer.add_diff(diff, cx);
21295 }
21296 })
21297 .ok();
21298 })
21299}
21300
21301fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21302 let tab_size = tab_size.get() as usize;
21303 let mut width = offset;
21304
21305 for ch in text.chars() {
21306 width += if ch == '\t' {
21307 tab_size - (width % tab_size)
21308 } else {
21309 1
21310 };
21311 }
21312
21313 width - offset
21314}
21315
21316#[cfg(test)]
21317mod tests {
21318 use super::*;
21319
21320 #[test]
21321 fn test_string_size_with_expanded_tabs() {
21322 let nz = |val| NonZeroU32::new(val).unwrap();
21323 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21324 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21325 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21326 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21327 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21328 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21329 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21330 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21331 }
21332}
21333
21334/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21335struct WordBreakingTokenizer<'a> {
21336 input: &'a str,
21337}
21338
21339impl<'a> WordBreakingTokenizer<'a> {
21340 fn new(input: &'a str) -> Self {
21341 Self { input }
21342 }
21343}
21344
21345fn is_char_ideographic(ch: char) -> bool {
21346 use unicode_script::Script::*;
21347 use unicode_script::UnicodeScript;
21348 matches!(ch.script(), Han | Tangut | Yi)
21349}
21350
21351fn is_grapheme_ideographic(text: &str) -> bool {
21352 text.chars().any(is_char_ideographic)
21353}
21354
21355fn is_grapheme_whitespace(text: &str) -> bool {
21356 text.chars().any(|x| x.is_whitespace())
21357}
21358
21359fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21360 text.chars().next().map_or(false, |ch| {
21361 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21362 })
21363}
21364
21365#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21366enum WordBreakToken<'a> {
21367 Word { token: &'a str, grapheme_len: usize },
21368 InlineWhitespace { token: &'a str, grapheme_len: usize },
21369 Newline,
21370}
21371
21372impl<'a> Iterator for WordBreakingTokenizer<'a> {
21373 /// Yields a span, the count of graphemes in the token, and whether it was
21374 /// whitespace. Note that it also breaks at word boundaries.
21375 type Item = WordBreakToken<'a>;
21376
21377 fn next(&mut self) -> Option<Self::Item> {
21378 use unicode_segmentation::UnicodeSegmentation;
21379 if self.input.is_empty() {
21380 return None;
21381 }
21382
21383 let mut iter = self.input.graphemes(true).peekable();
21384 let mut offset = 0;
21385 let mut grapheme_len = 0;
21386 if let Some(first_grapheme) = iter.next() {
21387 let is_newline = first_grapheme == "\n";
21388 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21389 offset += first_grapheme.len();
21390 grapheme_len += 1;
21391 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21392 if let Some(grapheme) = iter.peek().copied() {
21393 if should_stay_with_preceding_ideograph(grapheme) {
21394 offset += grapheme.len();
21395 grapheme_len += 1;
21396 }
21397 }
21398 } else {
21399 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21400 let mut next_word_bound = words.peek().copied();
21401 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21402 next_word_bound = words.next();
21403 }
21404 while let Some(grapheme) = iter.peek().copied() {
21405 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21406 break;
21407 };
21408 if is_grapheme_whitespace(grapheme) != is_whitespace
21409 || (grapheme == "\n") != is_newline
21410 {
21411 break;
21412 };
21413 offset += grapheme.len();
21414 grapheme_len += 1;
21415 iter.next();
21416 }
21417 }
21418 let token = &self.input[..offset];
21419 self.input = &self.input[offset..];
21420 if token == "\n" {
21421 Some(WordBreakToken::Newline)
21422 } else if is_whitespace {
21423 Some(WordBreakToken::InlineWhitespace {
21424 token,
21425 grapheme_len,
21426 })
21427 } else {
21428 Some(WordBreakToken::Word {
21429 token,
21430 grapheme_len,
21431 })
21432 }
21433 } else {
21434 None
21435 }
21436 }
21437}
21438
21439#[test]
21440fn test_word_breaking_tokenizer() {
21441 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21442 ("", &[]),
21443 (" ", &[whitespace(" ", 2)]),
21444 ("Ʒ", &[word("Ʒ", 1)]),
21445 ("Ǽ", &[word("Ǽ", 1)]),
21446 ("⋑", &[word("⋑", 1)]),
21447 ("⋑⋑", &[word("⋑⋑", 2)]),
21448 (
21449 "原理,进而",
21450 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21451 ),
21452 (
21453 "hello world",
21454 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21455 ),
21456 (
21457 "hello, world",
21458 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21459 ),
21460 (
21461 " hello world",
21462 &[
21463 whitespace(" ", 2),
21464 word("hello", 5),
21465 whitespace(" ", 1),
21466 word("world", 5),
21467 ],
21468 ),
21469 (
21470 "这是什么 \n 钢笔",
21471 &[
21472 word("这", 1),
21473 word("是", 1),
21474 word("什", 1),
21475 word("么", 1),
21476 whitespace(" ", 1),
21477 newline(),
21478 whitespace(" ", 1),
21479 word("钢", 1),
21480 word("笔", 1),
21481 ],
21482 ),
21483 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21484 ];
21485
21486 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21487 WordBreakToken::Word {
21488 token,
21489 grapheme_len,
21490 }
21491 }
21492
21493 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21494 WordBreakToken::InlineWhitespace {
21495 token,
21496 grapheme_len,
21497 }
21498 }
21499
21500 fn newline() -> WordBreakToken<'static> {
21501 WordBreakToken::Newline
21502 }
21503
21504 for (input, result) in tests {
21505 assert_eq!(
21506 WordBreakingTokenizer::new(input)
21507 .collect::<Vec<_>>()
21508 .as_slice(),
21509 *result,
21510 );
21511 }
21512}
21513
21514fn wrap_with_prefix(
21515 first_line_prefix: String,
21516 subsequent_lines_prefix: String,
21517 unwrapped_text: String,
21518 wrap_column: usize,
21519 tab_size: NonZeroU32,
21520 preserve_existing_whitespace: bool,
21521) -> String {
21522 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21523 let subsequent_lines_prefix_len =
21524 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21525 let mut wrapped_text = String::new();
21526 let mut current_line = first_line_prefix.clone();
21527 let mut is_first_line = true;
21528
21529 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21530 let mut current_line_len = first_line_prefix_len;
21531 let mut in_whitespace = false;
21532 for token in tokenizer {
21533 let have_preceding_whitespace = in_whitespace;
21534 match token {
21535 WordBreakToken::Word {
21536 token,
21537 grapheme_len,
21538 } => {
21539 in_whitespace = false;
21540 let current_prefix_len = if is_first_line {
21541 first_line_prefix_len
21542 } else {
21543 subsequent_lines_prefix_len
21544 };
21545 if current_line_len + grapheme_len > wrap_column
21546 && current_line_len != current_prefix_len
21547 {
21548 wrapped_text.push_str(current_line.trim_end());
21549 wrapped_text.push('\n');
21550 is_first_line = false;
21551 current_line = subsequent_lines_prefix.clone();
21552 current_line_len = subsequent_lines_prefix_len;
21553 }
21554 current_line.push_str(token);
21555 current_line_len += grapheme_len;
21556 }
21557 WordBreakToken::InlineWhitespace {
21558 mut token,
21559 mut grapheme_len,
21560 } => {
21561 in_whitespace = true;
21562 if have_preceding_whitespace && !preserve_existing_whitespace {
21563 continue;
21564 }
21565 if !preserve_existing_whitespace {
21566 token = " ";
21567 grapheme_len = 1;
21568 }
21569 let current_prefix_len = if is_first_line {
21570 first_line_prefix_len
21571 } else {
21572 subsequent_lines_prefix_len
21573 };
21574 if current_line_len + grapheme_len > wrap_column {
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 || preserve_existing_whitespace {
21581 current_line.push_str(token);
21582 current_line_len += grapheme_len;
21583 }
21584 }
21585 WordBreakToken::Newline => {
21586 in_whitespace = true;
21587 let current_prefix_len = if is_first_line {
21588 first_line_prefix_len
21589 } else {
21590 subsequent_lines_prefix_len
21591 };
21592 if preserve_existing_whitespace {
21593 wrapped_text.push_str(current_line.trim_end());
21594 wrapped_text.push('\n');
21595 is_first_line = false;
21596 current_line = subsequent_lines_prefix.clone();
21597 current_line_len = subsequent_lines_prefix_len;
21598 } else if have_preceding_whitespace {
21599 continue;
21600 } else if current_line_len + 1 > wrap_column
21601 && current_line_len != current_prefix_len
21602 {
21603 wrapped_text.push_str(current_line.trim_end());
21604 wrapped_text.push('\n');
21605 is_first_line = false;
21606 current_line = subsequent_lines_prefix.clone();
21607 current_line_len = subsequent_lines_prefix_len;
21608 } else if current_line_len != current_prefix_len {
21609 current_line.push(' ');
21610 current_line_len += 1;
21611 }
21612 }
21613 }
21614 }
21615
21616 if !current_line.is_empty() {
21617 wrapped_text.push_str(¤t_line);
21618 }
21619 wrapped_text
21620}
21621
21622#[test]
21623fn test_wrap_with_prefix() {
21624 assert_eq!(
21625 wrap_with_prefix(
21626 "# ".to_string(),
21627 "# ".to_string(),
21628 "abcdefg".to_string(),
21629 4,
21630 NonZeroU32::new(4).unwrap(),
21631 false,
21632 ),
21633 "# abcdefg"
21634 );
21635 assert_eq!(
21636 wrap_with_prefix(
21637 "".to_string(),
21638 "".to_string(),
21639 "\thello world".to_string(),
21640 8,
21641 NonZeroU32::new(4).unwrap(),
21642 false,
21643 ),
21644 "hello\nworld"
21645 );
21646 assert_eq!(
21647 wrap_with_prefix(
21648 "// ".to_string(),
21649 "// ".to_string(),
21650 "xx \nyy zz aa bb cc".to_string(),
21651 12,
21652 NonZeroU32::new(4).unwrap(),
21653 false,
21654 ),
21655 "// xx yy zz\n// aa bb cc"
21656 );
21657 assert_eq!(
21658 wrap_with_prefix(
21659 String::new(),
21660 String::new(),
21661 "这是什么 \n 钢笔".to_string(),
21662 3,
21663 NonZeroU32::new(4).unwrap(),
21664 false,
21665 ),
21666 "这是什\n么 钢\n笔"
21667 );
21668}
21669
21670pub trait CollaborationHub {
21671 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21672 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21673 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21674}
21675
21676impl CollaborationHub for Entity<Project> {
21677 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21678 self.read(cx).collaborators()
21679 }
21680
21681 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21682 self.read(cx).user_store().read(cx).participant_indices()
21683 }
21684
21685 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21686 let this = self.read(cx);
21687 let user_ids = this.collaborators().values().map(|c| c.user_id);
21688 this.user_store().read(cx).participant_names(user_ids, cx)
21689 }
21690}
21691
21692pub trait SemanticsProvider {
21693 fn hover(
21694 &self,
21695 buffer: &Entity<Buffer>,
21696 position: text::Anchor,
21697 cx: &mut App,
21698 ) -> Option<Task<Vec<project::Hover>>>;
21699
21700 fn inline_values(
21701 &self,
21702 buffer_handle: Entity<Buffer>,
21703 range: Range<text::Anchor>,
21704 cx: &mut App,
21705 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21706
21707 fn inlay_hints(
21708 &self,
21709 buffer_handle: Entity<Buffer>,
21710 range: Range<text::Anchor>,
21711 cx: &mut App,
21712 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21713
21714 fn resolve_inlay_hint(
21715 &self,
21716 hint: InlayHint,
21717 buffer_handle: Entity<Buffer>,
21718 server_id: LanguageServerId,
21719 cx: &mut App,
21720 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21721
21722 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21723
21724 fn document_highlights(
21725 &self,
21726 buffer: &Entity<Buffer>,
21727 position: text::Anchor,
21728 cx: &mut App,
21729 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21730
21731 fn definitions(
21732 &self,
21733 buffer: &Entity<Buffer>,
21734 position: text::Anchor,
21735 kind: GotoDefinitionKind,
21736 cx: &mut App,
21737 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21738
21739 fn range_for_rename(
21740 &self,
21741 buffer: &Entity<Buffer>,
21742 position: text::Anchor,
21743 cx: &mut App,
21744 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21745
21746 fn perform_rename(
21747 &self,
21748 buffer: &Entity<Buffer>,
21749 position: text::Anchor,
21750 new_name: String,
21751 cx: &mut App,
21752 ) -> Option<Task<Result<ProjectTransaction>>>;
21753}
21754
21755pub trait CompletionProvider {
21756 fn completions(
21757 &self,
21758 excerpt_id: ExcerptId,
21759 buffer: &Entity<Buffer>,
21760 buffer_position: text::Anchor,
21761 trigger: CompletionContext,
21762 window: &mut Window,
21763 cx: &mut Context<Editor>,
21764 ) -> Task<Result<Vec<CompletionResponse>>>;
21765
21766 fn resolve_completions(
21767 &self,
21768 _buffer: Entity<Buffer>,
21769 _completion_indices: Vec<usize>,
21770 _completions: Rc<RefCell<Box<[Completion]>>>,
21771 _cx: &mut Context<Editor>,
21772 ) -> Task<Result<bool>> {
21773 Task::ready(Ok(false))
21774 }
21775
21776 fn apply_additional_edits_for_completion(
21777 &self,
21778 _buffer: Entity<Buffer>,
21779 _completions: Rc<RefCell<Box<[Completion]>>>,
21780 _completion_index: usize,
21781 _push_to_history: bool,
21782 _cx: &mut Context<Editor>,
21783 ) -> Task<Result<Option<language::Transaction>>> {
21784 Task::ready(Ok(None))
21785 }
21786
21787 fn is_completion_trigger(
21788 &self,
21789 buffer: &Entity<Buffer>,
21790 position: language::Anchor,
21791 text: &str,
21792 trigger_in_words: bool,
21793 menu_is_open: bool,
21794 cx: &mut Context<Editor>,
21795 ) -> bool;
21796
21797 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21798
21799 fn sort_completions(&self) -> bool {
21800 true
21801 }
21802
21803 fn filter_completions(&self) -> bool {
21804 true
21805 }
21806}
21807
21808pub trait CodeActionProvider {
21809 fn id(&self) -> Arc<str>;
21810
21811 fn code_actions(
21812 &self,
21813 buffer: &Entity<Buffer>,
21814 range: Range<text::Anchor>,
21815 window: &mut Window,
21816 cx: &mut App,
21817 ) -> Task<Result<Vec<CodeAction>>>;
21818
21819 fn apply_code_action(
21820 &self,
21821 buffer_handle: Entity<Buffer>,
21822 action: CodeAction,
21823 excerpt_id: ExcerptId,
21824 push_to_history: bool,
21825 window: &mut Window,
21826 cx: &mut App,
21827 ) -> Task<Result<ProjectTransaction>>;
21828}
21829
21830impl CodeActionProvider for Entity<Project> {
21831 fn id(&self) -> Arc<str> {
21832 "project".into()
21833 }
21834
21835 fn code_actions(
21836 &self,
21837 buffer: &Entity<Buffer>,
21838 range: Range<text::Anchor>,
21839 _window: &mut Window,
21840 cx: &mut App,
21841 ) -> Task<Result<Vec<CodeAction>>> {
21842 self.update(cx, |project, cx| {
21843 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21844 let code_actions = project.code_actions(buffer, range, None, cx);
21845 cx.background_spawn(async move {
21846 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21847 Ok(code_lens_actions
21848 .context("code lens fetch")?
21849 .into_iter()
21850 .chain(code_actions.context("code action fetch")?)
21851 .collect())
21852 })
21853 })
21854 }
21855
21856 fn apply_code_action(
21857 &self,
21858 buffer_handle: Entity<Buffer>,
21859 action: CodeAction,
21860 _excerpt_id: ExcerptId,
21861 push_to_history: bool,
21862 _window: &mut Window,
21863 cx: &mut App,
21864 ) -> Task<Result<ProjectTransaction>> {
21865 self.update(cx, |project, cx| {
21866 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21867 })
21868 }
21869}
21870
21871fn snippet_completions(
21872 project: &Project,
21873 buffer: &Entity<Buffer>,
21874 buffer_position: text::Anchor,
21875 cx: &mut App,
21876) -> Task<Result<CompletionResponse>> {
21877 let languages = buffer.read(cx).languages_at(buffer_position);
21878 let snippet_store = project.snippets().read(cx);
21879
21880 let scopes: Vec<_> = languages
21881 .iter()
21882 .filter_map(|language| {
21883 let language_name = language.lsp_id();
21884 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21885
21886 if snippets.is_empty() {
21887 None
21888 } else {
21889 Some((language.default_scope(), snippets))
21890 }
21891 })
21892 .collect();
21893
21894 if scopes.is_empty() {
21895 return Task::ready(Ok(CompletionResponse {
21896 completions: vec![],
21897 is_incomplete: false,
21898 }));
21899 }
21900
21901 let snapshot = buffer.read(cx).text_snapshot();
21902 let chars: String = snapshot
21903 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21904 .collect();
21905 let executor = cx.background_executor().clone();
21906
21907 cx.background_spawn(async move {
21908 let mut is_incomplete = false;
21909 let mut completions: Vec<Completion> = Vec::new();
21910 for (scope, snippets) in scopes.into_iter() {
21911 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21912 let mut last_word = chars
21913 .chars()
21914 .take_while(|c| classifier.is_word(*c))
21915 .collect::<String>();
21916 last_word = last_word.chars().rev().collect();
21917
21918 if last_word.is_empty() {
21919 return Ok(CompletionResponse {
21920 completions: vec![],
21921 is_incomplete: true,
21922 });
21923 }
21924
21925 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21926 let to_lsp = |point: &text::Anchor| {
21927 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21928 point_to_lsp(end)
21929 };
21930 let lsp_end = to_lsp(&buffer_position);
21931
21932 let candidates = snippets
21933 .iter()
21934 .enumerate()
21935 .flat_map(|(ix, snippet)| {
21936 snippet
21937 .prefix
21938 .iter()
21939 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21940 })
21941 .collect::<Vec<StringMatchCandidate>>();
21942
21943 const MAX_RESULTS: usize = 100;
21944 let mut matches = fuzzy::match_strings(
21945 &candidates,
21946 &last_word,
21947 last_word.chars().any(|c| c.is_uppercase()),
21948 true,
21949 MAX_RESULTS,
21950 &Default::default(),
21951 executor.clone(),
21952 )
21953 .await;
21954
21955 if matches.len() >= MAX_RESULTS {
21956 is_incomplete = true;
21957 }
21958
21959 // Remove all candidates where the query's start does not match the start of any word in the candidate
21960 if let Some(query_start) = last_word.chars().next() {
21961 matches.retain(|string_match| {
21962 split_words(&string_match.string).any(|word| {
21963 // Check that the first codepoint of the word as lowercase matches the first
21964 // codepoint of the query as lowercase
21965 word.chars()
21966 .flat_map(|codepoint| codepoint.to_lowercase())
21967 .zip(query_start.to_lowercase())
21968 .all(|(word_cp, query_cp)| word_cp == query_cp)
21969 })
21970 });
21971 }
21972
21973 let matched_strings = matches
21974 .into_iter()
21975 .map(|m| m.string)
21976 .collect::<HashSet<_>>();
21977
21978 completions.extend(snippets.iter().filter_map(|snippet| {
21979 let matching_prefix = snippet
21980 .prefix
21981 .iter()
21982 .find(|prefix| matched_strings.contains(*prefix))?;
21983 let start = as_offset - last_word.len();
21984 let start = snapshot.anchor_before(start);
21985 let range = start..buffer_position;
21986 let lsp_start = to_lsp(&start);
21987 let lsp_range = lsp::Range {
21988 start: lsp_start,
21989 end: lsp_end,
21990 };
21991 Some(Completion {
21992 replace_range: range,
21993 new_text: snippet.body.clone(),
21994 source: CompletionSource::Lsp {
21995 insert_range: None,
21996 server_id: LanguageServerId(usize::MAX),
21997 resolved: true,
21998 lsp_completion: Box::new(lsp::CompletionItem {
21999 label: snippet.prefix.first().unwrap().clone(),
22000 kind: Some(CompletionItemKind::SNIPPET),
22001 label_details: snippet.description.as_ref().map(|description| {
22002 lsp::CompletionItemLabelDetails {
22003 detail: Some(description.clone()),
22004 description: None,
22005 }
22006 }),
22007 insert_text_format: Some(InsertTextFormat::SNIPPET),
22008 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22009 lsp::InsertReplaceEdit {
22010 new_text: snippet.body.clone(),
22011 insert: lsp_range,
22012 replace: lsp_range,
22013 },
22014 )),
22015 filter_text: Some(snippet.body.clone()),
22016 sort_text: Some(char::MAX.to_string()),
22017 ..lsp::CompletionItem::default()
22018 }),
22019 lsp_defaults: None,
22020 },
22021 label: CodeLabel {
22022 text: matching_prefix.clone(),
22023 runs: Vec::new(),
22024 filter_range: 0..matching_prefix.len(),
22025 },
22026 icon_path: None,
22027 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22028 single_line: snippet.name.clone().into(),
22029 plain_text: snippet
22030 .description
22031 .clone()
22032 .map(|description| description.into()),
22033 }),
22034 insert_text_mode: None,
22035 confirm: None,
22036 })
22037 }))
22038 }
22039
22040 Ok(CompletionResponse {
22041 completions,
22042 is_incomplete,
22043 })
22044 })
22045}
22046
22047impl CompletionProvider for Entity<Project> {
22048 fn completions(
22049 &self,
22050 _excerpt_id: ExcerptId,
22051 buffer: &Entity<Buffer>,
22052 buffer_position: text::Anchor,
22053 options: CompletionContext,
22054 _window: &mut Window,
22055 cx: &mut Context<Editor>,
22056 ) -> Task<Result<Vec<CompletionResponse>>> {
22057 self.update(cx, |project, cx| {
22058 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22059 let project_completions = project.completions(buffer, buffer_position, options, cx);
22060 cx.background_spawn(async move {
22061 let mut responses = project_completions.await?;
22062 let snippets = snippets.await?;
22063 if !snippets.completions.is_empty() {
22064 responses.push(snippets);
22065 }
22066 Ok(responses)
22067 })
22068 })
22069 }
22070
22071 fn resolve_completions(
22072 &self,
22073 buffer: Entity<Buffer>,
22074 completion_indices: Vec<usize>,
22075 completions: Rc<RefCell<Box<[Completion]>>>,
22076 cx: &mut Context<Editor>,
22077 ) -> Task<Result<bool>> {
22078 self.update(cx, |project, cx| {
22079 project.lsp_store().update(cx, |lsp_store, cx| {
22080 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22081 })
22082 })
22083 }
22084
22085 fn apply_additional_edits_for_completion(
22086 &self,
22087 buffer: Entity<Buffer>,
22088 completions: Rc<RefCell<Box<[Completion]>>>,
22089 completion_index: usize,
22090 push_to_history: bool,
22091 cx: &mut Context<Editor>,
22092 ) -> Task<Result<Option<language::Transaction>>> {
22093 self.update(cx, |project, cx| {
22094 project.lsp_store().update(cx, |lsp_store, cx| {
22095 lsp_store.apply_additional_edits_for_completion(
22096 buffer,
22097 completions,
22098 completion_index,
22099 push_to_history,
22100 cx,
22101 )
22102 })
22103 })
22104 }
22105
22106 fn is_completion_trigger(
22107 &self,
22108 buffer: &Entity<Buffer>,
22109 position: language::Anchor,
22110 text: &str,
22111 trigger_in_words: bool,
22112 menu_is_open: bool,
22113 cx: &mut Context<Editor>,
22114 ) -> bool {
22115 let mut chars = text.chars();
22116 let char = if let Some(char) = chars.next() {
22117 char
22118 } else {
22119 return false;
22120 };
22121 if chars.next().is_some() {
22122 return false;
22123 }
22124
22125 let buffer = buffer.read(cx);
22126 let snapshot = buffer.snapshot();
22127 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22128 return false;
22129 }
22130 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22131 if trigger_in_words && classifier.is_word(char) {
22132 return true;
22133 }
22134
22135 buffer.completion_triggers().contains(text)
22136 }
22137}
22138
22139impl SemanticsProvider for Entity<Project> {
22140 fn hover(
22141 &self,
22142 buffer: &Entity<Buffer>,
22143 position: text::Anchor,
22144 cx: &mut App,
22145 ) -> Option<Task<Vec<project::Hover>>> {
22146 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22147 }
22148
22149 fn document_highlights(
22150 &self,
22151 buffer: &Entity<Buffer>,
22152 position: text::Anchor,
22153 cx: &mut App,
22154 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22155 Some(self.update(cx, |project, cx| {
22156 project.document_highlights(buffer, position, cx)
22157 }))
22158 }
22159
22160 fn definitions(
22161 &self,
22162 buffer: &Entity<Buffer>,
22163 position: text::Anchor,
22164 kind: GotoDefinitionKind,
22165 cx: &mut App,
22166 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22167 Some(self.update(cx, |project, cx| match kind {
22168 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22169 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22170 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22171 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22172 }))
22173 }
22174
22175 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22176 // TODO: make this work for remote projects
22177 self.update(cx, |project, cx| {
22178 if project
22179 .active_debug_session(cx)
22180 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22181 {
22182 return true;
22183 }
22184
22185 buffer.update(cx, |buffer, cx| {
22186 project.any_language_server_supports_inlay_hints(buffer, cx)
22187 })
22188 })
22189 }
22190
22191 fn inline_values(
22192 &self,
22193 buffer_handle: Entity<Buffer>,
22194 range: Range<text::Anchor>,
22195 cx: &mut App,
22196 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22197 self.update(cx, |project, cx| {
22198 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22199
22200 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22201 })
22202 }
22203
22204 fn inlay_hints(
22205 &self,
22206 buffer_handle: Entity<Buffer>,
22207 range: Range<text::Anchor>,
22208 cx: &mut App,
22209 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22210 Some(self.update(cx, |project, cx| {
22211 project.inlay_hints(buffer_handle, range, cx)
22212 }))
22213 }
22214
22215 fn resolve_inlay_hint(
22216 &self,
22217 hint: InlayHint,
22218 buffer_handle: Entity<Buffer>,
22219 server_id: LanguageServerId,
22220 cx: &mut App,
22221 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22222 Some(self.update(cx, |project, cx| {
22223 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22224 }))
22225 }
22226
22227 fn range_for_rename(
22228 &self,
22229 buffer: &Entity<Buffer>,
22230 position: text::Anchor,
22231 cx: &mut App,
22232 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22233 Some(self.update(cx, |project, cx| {
22234 let buffer = buffer.clone();
22235 let task = project.prepare_rename(buffer.clone(), position, cx);
22236 cx.spawn(async move |_, cx| {
22237 Ok(match task.await? {
22238 PrepareRenameResponse::Success(range) => Some(range),
22239 PrepareRenameResponse::InvalidPosition => None,
22240 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22241 // Fallback on using TreeSitter info to determine identifier range
22242 buffer.read_with(cx, |buffer, _| {
22243 let snapshot = buffer.snapshot();
22244 let (range, kind) = snapshot.surrounding_word(position, false);
22245 if kind != Some(CharKind::Word) {
22246 return None;
22247 }
22248 Some(
22249 snapshot.anchor_before(range.start)
22250 ..snapshot.anchor_after(range.end),
22251 )
22252 })?
22253 }
22254 })
22255 })
22256 }))
22257 }
22258
22259 fn perform_rename(
22260 &self,
22261 buffer: &Entity<Buffer>,
22262 position: text::Anchor,
22263 new_name: String,
22264 cx: &mut App,
22265 ) -> Option<Task<Result<ProjectTransaction>>> {
22266 Some(self.update(cx, |project, cx| {
22267 project.perform_rename(buffer.clone(), position, new_name, cx)
22268 }))
22269 }
22270}
22271
22272fn inlay_hint_settings(
22273 location: Anchor,
22274 snapshot: &MultiBufferSnapshot,
22275 cx: &mut Context<Editor>,
22276) -> InlayHintSettings {
22277 let file = snapshot.file_at(location);
22278 let language = snapshot.language_at(location).map(|l| l.name());
22279 language_settings(language, file, cx).inlay_hints
22280}
22281
22282fn consume_contiguous_rows(
22283 contiguous_row_selections: &mut Vec<Selection<Point>>,
22284 selection: &Selection<Point>,
22285 display_map: &DisplaySnapshot,
22286 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22287) -> (MultiBufferRow, MultiBufferRow) {
22288 contiguous_row_selections.push(selection.clone());
22289 let start_row = MultiBufferRow(selection.start.row);
22290 let mut end_row = ending_row(selection, display_map);
22291
22292 while let Some(next_selection) = selections.peek() {
22293 if next_selection.start.row <= end_row.0 {
22294 end_row = ending_row(next_selection, display_map);
22295 contiguous_row_selections.push(selections.next().unwrap().clone());
22296 } else {
22297 break;
22298 }
22299 }
22300 (start_row, end_row)
22301}
22302
22303fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22304 if next_selection.end.column > 0 || next_selection.is_empty() {
22305 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22306 } else {
22307 MultiBufferRow(next_selection.end.row)
22308 }
22309}
22310
22311impl EditorSnapshot {
22312 pub fn remote_selections_in_range<'a>(
22313 &'a self,
22314 range: &'a Range<Anchor>,
22315 collaboration_hub: &dyn CollaborationHub,
22316 cx: &'a App,
22317 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22318 let participant_names = collaboration_hub.user_names(cx);
22319 let participant_indices = collaboration_hub.user_participant_indices(cx);
22320 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22321 let collaborators_by_replica_id = collaborators_by_peer_id
22322 .values()
22323 .map(|collaborator| (collaborator.replica_id, collaborator))
22324 .collect::<HashMap<_, _>>();
22325 self.buffer_snapshot
22326 .selections_in_range(range, false)
22327 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22328 if replica_id == AGENT_REPLICA_ID {
22329 Some(RemoteSelection {
22330 replica_id,
22331 selection,
22332 cursor_shape,
22333 line_mode,
22334 collaborator_id: CollaboratorId::Agent,
22335 user_name: Some("Agent".into()),
22336 color: cx.theme().players().agent(),
22337 })
22338 } else {
22339 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22340 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22341 let user_name = participant_names.get(&collaborator.user_id).cloned();
22342 Some(RemoteSelection {
22343 replica_id,
22344 selection,
22345 cursor_shape,
22346 line_mode,
22347 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22348 user_name,
22349 color: if let Some(index) = participant_index {
22350 cx.theme().players().color_for_participant(index.0)
22351 } else {
22352 cx.theme().players().absent()
22353 },
22354 })
22355 }
22356 })
22357 }
22358
22359 pub fn hunks_for_ranges(
22360 &self,
22361 ranges: impl IntoIterator<Item = Range<Point>>,
22362 ) -> Vec<MultiBufferDiffHunk> {
22363 let mut hunks = Vec::new();
22364 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22365 HashMap::default();
22366 for query_range in ranges {
22367 let query_rows =
22368 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22369 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22370 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22371 ) {
22372 // Include deleted hunks that are adjacent to the query range, because
22373 // otherwise they would be missed.
22374 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22375 if hunk.status().is_deleted() {
22376 intersects_range |= hunk.row_range.start == query_rows.end;
22377 intersects_range |= hunk.row_range.end == query_rows.start;
22378 }
22379 if intersects_range {
22380 if !processed_buffer_rows
22381 .entry(hunk.buffer_id)
22382 .or_default()
22383 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22384 {
22385 continue;
22386 }
22387 hunks.push(hunk);
22388 }
22389 }
22390 }
22391
22392 hunks
22393 }
22394
22395 fn display_diff_hunks_for_rows<'a>(
22396 &'a self,
22397 display_rows: Range<DisplayRow>,
22398 folded_buffers: &'a HashSet<BufferId>,
22399 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22400 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22401 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22402
22403 self.buffer_snapshot
22404 .diff_hunks_in_range(buffer_start..buffer_end)
22405 .filter_map(|hunk| {
22406 if folded_buffers.contains(&hunk.buffer_id) {
22407 return None;
22408 }
22409
22410 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22411 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22412
22413 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22414 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22415
22416 let display_hunk = if hunk_display_start.column() != 0 {
22417 DisplayDiffHunk::Folded {
22418 display_row: hunk_display_start.row(),
22419 }
22420 } else {
22421 let mut end_row = hunk_display_end.row();
22422 if hunk_display_end.column() > 0 {
22423 end_row.0 += 1;
22424 }
22425 let is_created_file = hunk.is_created_file();
22426 DisplayDiffHunk::Unfolded {
22427 status: hunk.status(),
22428 diff_base_byte_range: hunk.diff_base_byte_range,
22429 display_row_range: hunk_display_start.row()..end_row,
22430 multi_buffer_range: Anchor::range_in_buffer(
22431 hunk.excerpt_id,
22432 hunk.buffer_id,
22433 hunk.buffer_range,
22434 ),
22435 is_created_file,
22436 }
22437 };
22438
22439 Some(display_hunk)
22440 })
22441 }
22442
22443 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22444 self.display_snapshot.buffer_snapshot.language_at(position)
22445 }
22446
22447 pub fn is_focused(&self) -> bool {
22448 self.is_focused
22449 }
22450
22451 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22452 self.placeholder_text.as_ref()
22453 }
22454
22455 pub fn scroll_position(&self) -> gpui::Point<f32> {
22456 self.scroll_anchor.scroll_position(&self.display_snapshot)
22457 }
22458
22459 fn gutter_dimensions(
22460 &self,
22461 font_id: FontId,
22462 font_size: Pixels,
22463 max_line_number_width: Pixels,
22464 cx: &App,
22465 ) -> Option<GutterDimensions> {
22466 if !self.show_gutter {
22467 return None;
22468 }
22469
22470 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22471 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22472
22473 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22474 matches!(
22475 ProjectSettings::get_global(cx).git.git_gutter,
22476 Some(GitGutterSetting::TrackedFiles)
22477 )
22478 });
22479 let gutter_settings = EditorSettings::get_global(cx).gutter;
22480 let show_line_numbers = self
22481 .show_line_numbers
22482 .unwrap_or(gutter_settings.line_numbers);
22483 let line_gutter_width = if show_line_numbers {
22484 // Avoid flicker-like gutter resizes when the line number gains another digit by
22485 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22486 let min_width_for_number_on_gutter =
22487 ch_advance * gutter_settings.min_line_number_digits as f32;
22488 max_line_number_width.max(min_width_for_number_on_gutter)
22489 } else {
22490 0.0.into()
22491 };
22492
22493 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22494 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22495
22496 let git_blame_entries_width =
22497 self.git_blame_gutter_max_author_length
22498 .map(|max_author_length| {
22499 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22500 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22501
22502 /// The number of characters to dedicate to gaps and margins.
22503 const SPACING_WIDTH: usize = 4;
22504
22505 let max_char_count = max_author_length.min(renderer.max_author_length())
22506 + ::git::SHORT_SHA_LENGTH
22507 + MAX_RELATIVE_TIMESTAMP.len()
22508 + SPACING_WIDTH;
22509
22510 ch_advance * max_char_count
22511 });
22512
22513 let is_singleton = self.buffer_snapshot.is_singleton();
22514
22515 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22516 left_padding += if !is_singleton {
22517 ch_width * 4.0
22518 } else if show_runnables || show_breakpoints {
22519 ch_width * 3.0
22520 } else if show_git_gutter && show_line_numbers {
22521 ch_width * 2.0
22522 } else if show_git_gutter || show_line_numbers {
22523 ch_width
22524 } else {
22525 px(0.)
22526 };
22527
22528 let shows_folds = is_singleton && gutter_settings.folds;
22529
22530 let right_padding = if shows_folds && show_line_numbers {
22531 ch_width * 4.0
22532 } else if shows_folds || (!is_singleton && show_line_numbers) {
22533 ch_width * 3.0
22534 } else if show_line_numbers {
22535 ch_width
22536 } else {
22537 px(0.)
22538 };
22539
22540 Some(GutterDimensions {
22541 left_padding,
22542 right_padding,
22543 width: line_gutter_width + left_padding + right_padding,
22544 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22545 git_blame_entries_width,
22546 })
22547 }
22548
22549 pub fn render_crease_toggle(
22550 &self,
22551 buffer_row: MultiBufferRow,
22552 row_contains_cursor: bool,
22553 editor: Entity<Editor>,
22554 window: &mut Window,
22555 cx: &mut App,
22556 ) -> Option<AnyElement> {
22557 let folded = self.is_line_folded(buffer_row);
22558 let mut is_foldable = false;
22559
22560 if let Some(crease) = self
22561 .crease_snapshot
22562 .query_row(buffer_row, &self.buffer_snapshot)
22563 {
22564 is_foldable = true;
22565 match crease {
22566 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22567 if let Some(render_toggle) = render_toggle {
22568 let toggle_callback =
22569 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22570 if folded {
22571 editor.update(cx, |editor, cx| {
22572 editor.fold_at(buffer_row, window, cx)
22573 });
22574 } else {
22575 editor.update(cx, |editor, cx| {
22576 editor.unfold_at(buffer_row, window, cx)
22577 });
22578 }
22579 });
22580 return Some((render_toggle)(
22581 buffer_row,
22582 folded,
22583 toggle_callback,
22584 window,
22585 cx,
22586 ));
22587 }
22588 }
22589 }
22590 }
22591
22592 is_foldable |= self.starts_indent(buffer_row);
22593
22594 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22595 Some(
22596 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22597 .toggle_state(folded)
22598 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22599 if folded {
22600 this.unfold_at(buffer_row, window, cx);
22601 } else {
22602 this.fold_at(buffer_row, window, cx);
22603 }
22604 }))
22605 .into_any_element(),
22606 )
22607 } else {
22608 None
22609 }
22610 }
22611
22612 pub fn render_crease_trailer(
22613 &self,
22614 buffer_row: MultiBufferRow,
22615 window: &mut Window,
22616 cx: &mut App,
22617 ) -> Option<AnyElement> {
22618 let folded = self.is_line_folded(buffer_row);
22619 if let Crease::Inline { render_trailer, .. } = self
22620 .crease_snapshot
22621 .query_row(buffer_row, &self.buffer_snapshot)?
22622 {
22623 let render_trailer = render_trailer.as_ref()?;
22624 Some(render_trailer(buffer_row, folded, window, cx))
22625 } else {
22626 None
22627 }
22628 }
22629}
22630
22631impl Deref for EditorSnapshot {
22632 type Target = DisplaySnapshot;
22633
22634 fn deref(&self) -> &Self::Target {
22635 &self.display_snapshot
22636 }
22637}
22638
22639#[derive(Clone, Debug, PartialEq, Eq)]
22640pub enum EditorEvent {
22641 InputIgnored {
22642 text: Arc<str>,
22643 },
22644 InputHandled {
22645 utf16_range_to_replace: Option<Range<isize>>,
22646 text: Arc<str>,
22647 },
22648 ExcerptsAdded {
22649 buffer: Entity<Buffer>,
22650 predecessor: ExcerptId,
22651 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22652 },
22653 ExcerptsRemoved {
22654 ids: Vec<ExcerptId>,
22655 removed_buffer_ids: Vec<BufferId>,
22656 },
22657 BufferFoldToggled {
22658 ids: Vec<ExcerptId>,
22659 folded: bool,
22660 },
22661 ExcerptsEdited {
22662 ids: Vec<ExcerptId>,
22663 },
22664 ExcerptsExpanded {
22665 ids: Vec<ExcerptId>,
22666 },
22667 BufferEdited,
22668 Edited {
22669 transaction_id: clock::Lamport,
22670 },
22671 Reparsed(BufferId),
22672 Focused,
22673 FocusedIn,
22674 Blurred,
22675 DirtyChanged,
22676 Saved,
22677 TitleChanged,
22678 DiffBaseChanged,
22679 SelectionsChanged {
22680 local: bool,
22681 },
22682 ScrollPositionChanged {
22683 local: bool,
22684 autoscroll: bool,
22685 },
22686 Closed,
22687 TransactionUndone {
22688 transaction_id: clock::Lamport,
22689 },
22690 TransactionBegun {
22691 transaction_id: clock::Lamport,
22692 },
22693 Reloaded,
22694 CursorShapeChanged,
22695 PushedToNavHistory {
22696 anchor: Anchor,
22697 is_deactivate: bool,
22698 },
22699}
22700
22701impl EventEmitter<EditorEvent> for Editor {}
22702
22703impl Focusable for Editor {
22704 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22705 self.focus_handle.clone()
22706 }
22707}
22708
22709impl Render for Editor {
22710 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22711 let settings = ThemeSettings::get_global(cx);
22712
22713 let mut text_style = match self.mode {
22714 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22715 color: cx.theme().colors().editor_foreground,
22716 font_family: settings.ui_font.family.clone(),
22717 font_features: settings.ui_font.features.clone(),
22718 font_fallbacks: settings.ui_font.fallbacks.clone(),
22719 font_size: rems(0.875).into(),
22720 font_weight: settings.ui_font.weight,
22721 line_height: relative(settings.buffer_line_height.value()),
22722 ..Default::default()
22723 },
22724 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22725 color: cx.theme().colors().editor_foreground,
22726 font_family: settings.buffer_font.family.clone(),
22727 font_features: settings.buffer_font.features.clone(),
22728 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22729 font_size: settings.buffer_font_size(cx).into(),
22730 font_weight: settings.buffer_font.weight,
22731 line_height: relative(settings.buffer_line_height.value()),
22732 ..Default::default()
22733 },
22734 };
22735 if let Some(text_style_refinement) = &self.text_style_refinement {
22736 text_style.refine(text_style_refinement)
22737 }
22738
22739 let background = match self.mode {
22740 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22741 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22742 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22743 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22744 };
22745
22746 EditorElement::new(
22747 &cx.entity(),
22748 EditorStyle {
22749 background,
22750 border: cx.theme().colors().border,
22751 local_player: cx.theme().players().local(),
22752 text: text_style,
22753 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22754 syntax: cx.theme().syntax().clone(),
22755 status: cx.theme().status().clone(),
22756 inlay_hints_style: make_inlay_hints_style(cx),
22757 inline_completion_styles: make_suggestion_styles(cx),
22758 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22759 show_underlines: self.diagnostics_enabled(),
22760 },
22761 )
22762 }
22763}
22764
22765impl EntityInputHandler for Editor {
22766 fn text_for_range(
22767 &mut self,
22768 range_utf16: Range<usize>,
22769 adjusted_range: &mut Option<Range<usize>>,
22770 _: &mut Window,
22771 cx: &mut Context<Self>,
22772 ) -> Option<String> {
22773 let snapshot = self.buffer.read(cx).read(cx);
22774 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22775 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22776 if (start.0..end.0) != range_utf16 {
22777 adjusted_range.replace(start.0..end.0);
22778 }
22779 Some(snapshot.text_for_range(start..end).collect())
22780 }
22781
22782 fn selected_text_range(
22783 &mut self,
22784 ignore_disabled_input: bool,
22785 _: &mut Window,
22786 cx: &mut Context<Self>,
22787 ) -> Option<UTF16Selection> {
22788 // Prevent the IME menu from appearing when holding down an alphabetic key
22789 // while input is disabled.
22790 if !ignore_disabled_input && !self.input_enabled {
22791 return None;
22792 }
22793
22794 let selection = self.selections.newest::<OffsetUtf16>(cx);
22795 let range = selection.range();
22796
22797 Some(UTF16Selection {
22798 range: range.start.0..range.end.0,
22799 reversed: selection.reversed,
22800 })
22801 }
22802
22803 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22804 let snapshot = self.buffer.read(cx).read(cx);
22805 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22806 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22807 }
22808
22809 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22810 self.clear_highlights::<InputComposition>(cx);
22811 self.ime_transaction.take();
22812 }
22813
22814 fn replace_text_in_range(
22815 &mut self,
22816 range_utf16: Option<Range<usize>>,
22817 text: &str,
22818 window: &mut Window,
22819 cx: &mut Context<Self>,
22820 ) {
22821 if !self.input_enabled {
22822 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22823 return;
22824 }
22825
22826 self.transact(window, cx, |this, window, cx| {
22827 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22828 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22829 Some(this.selection_replacement_ranges(range_utf16, cx))
22830 } else {
22831 this.marked_text_ranges(cx)
22832 };
22833
22834 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22835 let newest_selection_id = this.selections.newest_anchor().id;
22836 this.selections
22837 .all::<OffsetUtf16>(cx)
22838 .iter()
22839 .zip(ranges_to_replace.iter())
22840 .find_map(|(selection, range)| {
22841 if selection.id == newest_selection_id {
22842 Some(
22843 (range.start.0 as isize - selection.head().0 as isize)
22844 ..(range.end.0 as isize - selection.head().0 as isize),
22845 )
22846 } else {
22847 None
22848 }
22849 })
22850 });
22851
22852 cx.emit(EditorEvent::InputHandled {
22853 utf16_range_to_replace: range_to_replace,
22854 text: text.into(),
22855 });
22856
22857 if let Some(new_selected_ranges) = new_selected_ranges {
22858 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22859 selections.select_ranges(new_selected_ranges)
22860 });
22861 this.backspace(&Default::default(), window, cx);
22862 }
22863
22864 this.handle_input(text, window, cx);
22865 });
22866
22867 if let Some(transaction) = self.ime_transaction {
22868 self.buffer.update(cx, |buffer, cx| {
22869 buffer.group_until_transaction(transaction, cx);
22870 });
22871 }
22872
22873 self.unmark_text(window, cx);
22874 }
22875
22876 fn replace_and_mark_text_in_range(
22877 &mut self,
22878 range_utf16: Option<Range<usize>>,
22879 text: &str,
22880 new_selected_range_utf16: Option<Range<usize>>,
22881 window: &mut Window,
22882 cx: &mut Context<Self>,
22883 ) {
22884 if !self.input_enabled {
22885 return;
22886 }
22887
22888 let transaction = self.transact(window, cx, |this, window, cx| {
22889 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22890 let snapshot = this.buffer.read(cx).read(cx);
22891 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22892 for marked_range in &mut marked_ranges {
22893 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22894 marked_range.start.0 += relative_range_utf16.start;
22895 marked_range.start =
22896 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22897 marked_range.end =
22898 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22899 }
22900 }
22901 Some(marked_ranges)
22902 } else if let Some(range_utf16) = range_utf16 {
22903 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22904 Some(this.selection_replacement_ranges(range_utf16, cx))
22905 } else {
22906 None
22907 };
22908
22909 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22910 let newest_selection_id = this.selections.newest_anchor().id;
22911 this.selections
22912 .all::<OffsetUtf16>(cx)
22913 .iter()
22914 .zip(ranges_to_replace.iter())
22915 .find_map(|(selection, range)| {
22916 if selection.id == newest_selection_id {
22917 Some(
22918 (range.start.0 as isize - selection.head().0 as isize)
22919 ..(range.end.0 as isize - selection.head().0 as isize),
22920 )
22921 } else {
22922 None
22923 }
22924 })
22925 });
22926
22927 cx.emit(EditorEvent::InputHandled {
22928 utf16_range_to_replace: range_to_replace,
22929 text: text.into(),
22930 });
22931
22932 if let Some(ranges) = ranges_to_replace {
22933 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22934 s.select_ranges(ranges)
22935 });
22936 }
22937
22938 let marked_ranges = {
22939 let snapshot = this.buffer.read(cx).read(cx);
22940 this.selections
22941 .disjoint_anchors()
22942 .iter()
22943 .map(|selection| {
22944 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22945 })
22946 .collect::<Vec<_>>()
22947 };
22948
22949 if text.is_empty() {
22950 this.unmark_text(window, cx);
22951 } else {
22952 this.highlight_text::<InputComposition>(
22953 marked_ranges.clone(),
22954 HighlightStyle {
22955 underline: Some(UnderlineStyle {
22956 thickness: px(1.),
22957 color: None,
22958 wavy: false,
22959 }),
22960 ..Default::default()
22961 },
22962 cx,
22963 );
22964 }
22965
22966 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22967 let use_autoclose = this.use_autoclose;
22968 let use_auto_surround = this.use_auto_surround;
22969 this.set_use_autoclose(false);
22970 this.set_use_auto_surround(false);
22971 this.handle_input(text, window, cx);
22972 this.set_use_autoclose(use_autoclose);
22973 this.set_use_auto_surround(use_auto_surround);
22974
22975 if let Some(new_selected_range) = new_selected_range_utf16 {
22976 let snapshot = this.buffer.read(cx).read(cx);
22977 let new_selected_ranges = marked_ranges
22978 .into_iter()
22979 .map(|marked_range| {
22980 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22981 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22982 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22983 snapshot.clip_offset_utf16(new_start, Bias::Left)
22984 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22985 })
22986 .collect::<Vec<_>>();
22987
22988 drop(snapshot);
22989 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22990 selections.select_ranges(new_selected_ranges)
22991 });
22992 }
22993 });
22994
22995 self.ime_transaction = self.ime_transaction.or(transaction);
22996 if let Some(transaction) = self.ime_transaction {
22997 self.buffer.update(cx, |buffer, cx| {
22998 buffer.group_until_transaction(transaction, cx);
22999 });
23000 }
23001
23002 if self.text_highlights::<InputComposition>(cx).is_none() {
23003 self.ime_transaction.take();
23004 }
23005 }
23006
23007 fn bounds_for_range(
23008 &mut self,
23009 range_utf16: Range<usize>,
23010 element_bounds: gpui::Bounds<Pixels>,
23011 window: &mut Window,
23012 cx: &mut Context<Self>,
23013 ) -> Option<gpui::Bounds<Pixels>> {
23014 let text_layout_details = self.text_layout_details(window);
23015 let CharacterDimensions {
23016 em_width,
23017 em_advance,
23018 line_height,
23019 } = self.character_dimensions(window);
23020
23021 let snapshot = self.snapshot(window, cx);
23022 let scroll_position = snapshot.scroll_position();
23023 let scroll_left = scroll_position.x * em_advance;
23024
23025 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23026 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23027 + self.gutter_dimensions.full_width();
23028 let y = line_height * (start.row().as_f32() - scroll_position.y);
23029
23030 Some(Bounds {
23031 origin: element_bounds.origin + point(x, y),
23032 size: size(em_width, line_height),
23033 })
23034 }
23035
23036 fn character_index_for_point(
23037 &mut self,
23038 point: gpui::Point<Pixels>,
23039 _window: &mut Window,
23040 _cx: &mut Context<Self>,
23041 ) -> Option<usize> {
23042 let position_map = self.last_position_map.as_ref()?;
23043 if !position_map.text_hitbox.contains(&point) {
23044 return None;
23045 }
23046 let display_point = position_map.point_for_position(point).previous_valid;
23047 let anchor = position_map
23048 .snapshot
23049 .display_point_to_anchor(display_point, Bias::Left);
23050 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23051 Some(utf16_offset.0)
23052 }
23053}
23054
23055trait SelectionExt {
23056 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23057 fn spanned_rows(
23058 &self,
23059 include_end_if_at_line_start: bool,
23060 map: &DisplaySnapshot,
23061 ) -> Range<MultiBufferRow>;
23062}
23063
23064impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23065 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23066 let start = self
23067 .start
23068 .to_point(&map.buffer_snapshot)
23069 .to_display_point(map);
23070 let end = self
23071 .end
23072 .to_point(&map.buffer_snapshot)
23073 .to_display_point(map);
23074 if self.reversed {
23075 end..start
23076 } else {
23077 start..end
23078 }
23079 }
23080
23081 fn spanned_rows(
23082 &self,
23083 include_end_if_at_line_start: bool,
23084 map: &DisplaySnapshot,
23085 ) -> Range<MultiBufferRow> {
23086 let start = self.start.to_point(&map.buffer_snapshot);
23087 let mut end = self.end.to_point(&map.buffer_snapshot);
23088 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23089 end.row -= 1;
23090 }
23091
23092 let buffer_start = map.prev_line_boundary(start).0;
23093 let buffer_end = map.next_line_boundary(end).0;
23094 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23095 }
23096}
23097
23098impl<T: InvalidationRegion> InvalidationStack<T> {
23099 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23100 where
23101 S: Clone + ToOffset,
23102 {
23103 while let Some(region) = self.last() {
23104 let all_selections_inside_invalidation_ranges =
23105 if selections.len() == region.ranges().len() {
23106 selections
23107 .iter()
23108 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23109 .all(|(selection, invalidation_range)| {
23110 let head = selection.head().to_offset(buffer);
23111 invalidation_range.start <= head && invalidation_range.end >= head
23112 })
23113 } else {
23114 false
23115 };
23116
23117 if all_selections_inside_invalidation_ranges {
23118 break;
23119 } else {
23120 self.pop();
23121 }
23122 }
23123 }
23124}
23125
23126impl<T> Default for InvalidationStack<T> {
23127 fn default() -> Self {
23128 Self(Default::default())
23129 }
23130}
23131
23132impl<T> Deref for InvalidationStack<T> {
23133 type Target = Vec<T>;
23134
23135 fn deref(&self) -> &Self::Target {
23136 &self.0
23137 }
23138}
23139
23140impl<T> DerefMut for InvalidationStack<T> {
23141 fn deref_mut(&mut self) -> &mut Self::Target {
23142 &mut self.0
23143 }
23144}
23145
23146impl InvalidationRegion for SnippetState {
23147 fn ranges(&self) -> &[Range<Anchor>] {
23148 &self.ranges[self.active_index]
23149 }
23150}
23151
23152fn inline_completion_edit_text(
23153 current_snapshot: &BufferSnapshot,
23154 edits: &[(Range<Anchor>, String)],
23155 edit_preview: &EditPreview,
23156 include_deletions: bool,
23157 cx: &App,
23158) -> HighlightedText {
23159 let edits = edits
23160 .iter()
23161 .map(|(anchor, text)| {
23162 (
23163 anchor.start.text_anchor..anchor.end.text_anchor,
23164 text.clone(),
23165 )
23166 })
23167 .collect::<Vec<_>>();
23168
23169 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23170}
23171
23172pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23173 match severity {
23174 lsp::DiagnosticSeverity::ERROR => colors.error,
23175 lsp::DiagnosticSeverity::WARNING => colors.warning,
23176 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23177 lsp::DiagnosticSeverity::HINT => colors.info,
23178 _ => colors.ignored,
23179 }
23180}
23181
23182pub fn styled_runs_for_code_label<'a>(
23183 label: &'a CodeLabel,
23184 syntax_theme: &'a theme::SyntaxTheme,
23185) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23186 let fade_out = HighlightStyle {
23187 fade_out: Some(0.35),
23188 ..Default::default()
23189 };
23190
23191 let mut prev_end = label.filter_range.end;
23192 label
23193 .runs
23194 .iter()
23195 .enumerate()
23196 .flat_map(move |(ix, (range, highlight_id))| {
23197 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23198 style
23199 } else {
23200 return Default::default();
23201 };
23202 let mut muted_style = style;
23203 muted_style.highlight(fade_out);
23204
23205 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23206 if range.start >= label.filter_range.end {
23207 if range.start > prev_end {
23208 runs.push((prev_end..range.start, fade_out));
23209 }
23210 runs.push((range.clone(), muted_style));
23211 } else if range.end <= label.filter_range.end {
23212 runs.push((range.clone(), style));
23213 } else {
23214 runs.push((range.start..label.filter_range.end, style));
23215 runs.push((label.filter_range.end..range.end, muted_style));
23216 }
23217 prev_end = cmp::max(prev_end, range.end);
23218
23219 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23220 runs.push((prev_end..label.text.len(), fade_out));
23221 }
23222
23223 runs
23224 })
23225}
23226
23227pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23228 let mut prev_index = 0;
23229 let mut prev_codepoint: Option<char> = None;
23230 text.char_indices()
23231 .chain([(text.len(), '\0')])
23232 .filter_map(move |(index, codepoint)| {
23233 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23234 let is_boundary = index == text.len()
23235 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23236 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23237 if is_boundary {
23238 let chunk = &text[prev_index..index];
23239 prev_index = index;
23240 Some(chunk)
23241 } else {
23242 None
23243 }
23244 })
23245}
23246
23247pub trait RangeToAnchorExt: Sized {
23248 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23249
23250 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23251 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23252 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23253 }
23254}
23255
23256impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23257 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23258 let start_offset = self.start.to_offset(snapshot);
23259 let end_offset = self.end.to_offset(snapshot);
23260 if start_offset == end_offset {
23261 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23262 } else {
23263 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23264 }
23265 }
23266}
23267
23268pub trait RowExt {
23269 fn as_f32(&self) -> f32;
23270
23271 fn next_row(&self) -> Self;
23272
23273 fn previous_row(&self) -> Self;
23274
23275 fn minus(&self, other: Self) -> u32;
23276}
23277
23278impl RowExt for DisplayRow {
23279 fn as_f32(&self) -> f32 {
23280 self.0 as f32
23281 }
23282
23283 fn next_row(&self) -> Self {
23284 Self(self.0 + 1)
23285 }
23286
23287 fn previous_row(&self) -> Self {
23288 Self(self.0.saturating_sub(1))
23289 }
23290
23291 fn minus(&self, other: Self) -> u32 {
23292 self.0 - other.0
23293 }
23294}
23295
23296impl RowExt for MultiBufferRow {
23297 fn as_f32(&self) -> f32 {
23298 self.0 as f32
23299 }
23300
23301 fn next_row(&self) -> Self {
23302 Self(self.0 + 1)
23303 }
23304
23305 fn previous_row(&self) -> Self {
23306 Self(self.0.saturating_sub(1))
23307 }
23308
23309 fn minus(&self, other: Self) -> u32 {
23310 self.0 - other.0
23311 }
23312}
23313
23314trait RowRangeExt {
23315 type Row;
23316
23317 fn len(&self) -> usize;
23318
23319 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23320}
23321
23322impl RowRangeExt for Range<MultiBufferRow> {
23323 type Row = MultiBufferRow;
23324
23325 fn len(&self) -> usize {
23326 (self.end.0 - self.start.0) as usize
23327 }
23328
23329 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23330 (self.start.0..self.end.0).map(MultiBufferRow)
23331 }
23332}
23333
23334impl RowRangeExt for Range<DisplayRow> {
23335 type Row = DisplayRow;
23336
23337 fn len(&self) -> usize {
23338 (self.end.0 - self.start.0) as usize
23339 }
23340
23341 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23342 (self.start.0..self.end.0).map(DisplayRow)
23343 }
23344}
23345
23346/// If select range has more than one line, we
23347/// just point the cursor to range.start.
23348fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23349 if range.start.row == range.end.row {
23350 range
23351 } else {
23352 range.start..range.start
23353 }
23354}
23355pub struct KillRing(ClipboardItem);
23356impl Global for KillRing {}
23357
23358const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23359
23360enum BreakpointPromptEditAction {
23361 Log,
23362 Condition,
23363 HitCondition,
23364}
23365
23366struct BreakpointPromptEditor {
23367 pub(crate) prompt: Entity<Editor>,
23368 editor: WeakEntity<Editor>,
23369 breakpoint_anchor: Anchor,
23370 breakpoint: Breakpoint,
23371 edit_action: BreakpointPromptEditAction,
23372 block_ids: HashSet<CustomBlockId>,
23373 editor_margins: Arc<Mutex<EditorMargins>>,
23374 _subscriptions: Vec<Subscription>,
23375}
23376
23377impl BreakpointPromptEditor {
23378 const MAX_LINES: u8 = 4;
23379
23380 fn new(
23381 editor: WeakEntity<Editor>,
23382 breakpoint_anchor: Anchor,
23383 breakpoint: Breakpoint,
23384 edit_action: BreakpointPromptEditAction,
23385 window: &mut Window,
23386 cx: &mut Context<Self>,
23387 ) -> Self {
23388 let base_text = match edit_action {
23389 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23390 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23391 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23392 }
23393 .map(|msg| msg.to_string())
23394 .unwrap_or_default();
23395
23396 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23397 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23398
23399 let prompt = cx.new(|cx| {
23400 let mut prompt = Editor::new(
23401 EditorMode::AutoHeight {
23402 min_lines: 1,
23403 max_lines: Some(Self::MAX_LINES as usize),
23404 },
23405 buffer,
23406 None,
23407 window,
23408 cx,
23409 );
23410 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23411 prompt.set_show_cursor_when_unfocused(false, cx);
23412 prompt.set_placeholder_text(
23413 match edit_action {
23414 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23415 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23416 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23417 },
23418 cx,
23419 );
23420
23421 prompt
23422 });
23423
23424 Self {
23425 prompt,
23426 editor,
23427 breakpoint_anchor,
23428 breakpoint,
23429 edit_action,
23430 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23431 block_ids: Default::default(),
23432 _subscriptions: vec![],
23433 }
23434 }
23435
23436 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23437 self.block_ids.extend(block_ids)
23438 }
23439
23440 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23441 if let Some(editor) = self.editor.upgrade() {
23442 let message = self
23443 .prompt
23444 .read(cx)
23445 .buffer
23446 .read(cx)
23447 .as_singleton()
23448 .expect("A multi buffer in breakpoint prompt isn't possible")
23449 .read(cx)
23450 .as_rope()
23451 .to_string();
23452
23453 editor.update(cx, |editor, cx| {
23454 editor.edit_breakpoint_at_anchor(
23455 self.breakpoint_anchor,
23456 self.breakpoint.clone(),
23457 match self.edit_action {
23458 BreakpointPromptEditAction::Log => {
23459 BreakpointEditAction::EditLogMessage(message.into())
23460 }
23461 BreakpointPromptEditAction::Condition => {
23462 BreakpointEditAction::EditCondition(message.into())
23463 }
23464 BreakpointPromptEditAction::HitCondition => {
23465 BreakpointEditAction::EditHitCondition(message.into())
23466 }
23467 },
23468 cx,
23469 );
23470
23471 editor.remove_blocks(self.block_ids.clone(), None, cx);
23472 cx.focus_self(window);
23473 });
23474 }
23475 }
23476
23477 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23478 self.editor
23479 .update(cx, |editor, cx| {
23480 editor.remove_blocks(self.block_ids.clone(), None, cx);
23481 window.focus(&editor.focus_handle);
23482 })
23483 .log_err();
23484 }
23485
23486 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23487 let settings = ThemeSettings::get_global(cx);
23488 let text_style = TextStyle {
23489 color: if self.prompt.read(cx).read_only(cx) {
23490 cx.theme().colors().text_disabled
23491 } else {
23492 cx.theme().colors().text
23493 },
23494 font_family: settings.buffer_font.family.clone(),
23495 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23496 font_size: settings.buffer_font_size(cx).into(),
23497 font_weight: settings.buffer_font.weight,
23498 line_height: relative(settings.buffer_line_height.value()),
23499 ..Default::default()
23500 };
23501 EditorElement::new(
23502 &self.prompt,
23503 EditorStyle {
23504 background: cx.theme().colors().editor_background,
23505 local_player: cx.theme().players().local(),
23506 text: text_style,
23507 ..Default::default()
23508 },
23509 )
23510 }
23511}
23512
23513impl Render for BreakpointPromptEditor {
23514 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23515 let editor_margins = *self.editor_margins.lock();
23516 let gutter_dimensions = editor_margins.gutter;
23517 h_flex()
23518 .key_context("Editor")
23519 .bg(cx.theme().colors().editor_background)
23520 .border_y_1()
23521 .border_color(cx.theme().status().info_border)
23522 .size_full()
23523 .py(window.line_height() / 2.5)
23524 .on_action(cx.listener(Self::confirm))
23525 .on_action(cx.listener(Self::cancel))
23526 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23527 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23528 }
23529}
23530
23531impl Focusable for BreakpointPromptEditor {
23532 fn focus_handle(&self, cx: &App) -> FocusHandle {
23533 self.prompt.focus_handle(cx)
23534 }
23535}
23536
23537fn all_edits_insertions_or_deletions(
23538 edits: &Vec<(Range<Anchor>, String)>,
23539 snapshot: &MultiBufferSnapshot,
23540) -> bool {
23541 let mut all_insertions = true;
23542 let mut all_deletions = true;
23543
23544 for (range, new_text) in edits.iter() {
23545 let range_is_empty = range.to_offset(&snapshot).is_empty();
23546 let text_is_empty = new_text.is_empty();
23547
23548 if range_is_empty != text_is_empty {
23549 if range_is_empty {
23550 all_deletions = false;
23551 } else {
23552 all_insertions = false;
23553 }
23554 } else {
23555 return false;
23556 }
23557
23558 if !all_insertions && !all_deletions {
23559 return false;
23560 }
23561 }
23562 all_insertions || all_deletions
23563}
23564
23565struct MissingEditPredictionKeybindingTooltip;
23566
23567impl Render for MissingEditPredictionKeybindingTooltip {
23568 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23569 ui::tooltip_container(window, cx, |container, _, cx| {
23570 container
23571 .flex_shrink_0()
23572 .max_w_80()
23573 .min_h(rems_from_px(124.))
23574 .justify_between()
23575 .child(
23576 v_flex()
23577 .flex_1()
23578 .text_ui_sm(cx)
23579 .child(Label::new("Conflict with Accept Keybinding"))
23580 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23581 )
23582 .child(
23583 h_flex()
23584 .pb_1()
23585 .gap_1()
23586 .items_end()
23587 .w_full()
23588 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23589 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23590 }))
23591 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23592 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23593 })),
23594 )
23595 })
23596 }
23597}
23598
23599#[derive(Debug, Clone, Copy, PartialEq)]
23600pub struct LineHighlight {
23601 pub background: Background,
23602 pub border: Option<gpui::Hsla>,
23603 pub include_gutter: bool,
23604 pub type_id: Option<TypeId>,
23605}
23606
23607struct LineManipulationResult {
23608 pub new_text: String,
23609 pub line_count_before: usize,
23610 pub line_count_after: usize,
23611}
23612
23613fn render_diff_hunk_controls(
23614 row: u32,
23615 status: &DiffHunkStatus,
23616 hunk_range: Range<Anchor>,
23617 is_created_file: bool,
23618 line_height: Pixels,
23619 editor: &Entity<Editor>,
23620 _window: &mut Window,
23621 cx: &mut App,
23622) -> AnyElement {
23623 h_flex()
23624 .h(line_height)
23625 .mr_1()
23626 .gap_1()
23627 .px_0p5()
23628 .pb_1()
23629 .border_x_1()
23630 .border_b_1()
23631 .border_color(cx.theme().colors().border_variant)
23632 .rounded_b_lg()
23633 .bg(cx.theme().colors().editor_background)
23634 .gap_1()
23635 .block_mouse_except_scroll()
23636 .shadow_md()
23637 .child(if status.has_secondary_hunk() {
23638 Button::new(("stage", row as u64), "Stage")
23639 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23640 .tooltip({
23641 let focus_handle = editor.focus_handle(cx);
23642 move |window, cx| {
23643 Tooltip::for_action_in(
23644 "Stage Hunk",
23645 &::git::ToggleStaged,
23646 &focus_handle,
23647 window,
23648 cx,
23649 )
23650 }
23651 })
23652 .on_click({
23653 let editor = editor.clone();
23654 move |_event, _window, cx| {
23655 editor.update(cx, |editor, cx| {
23656 editor.stage_or_unstage_diff_hunks(
23657 true,
23658 vec![hunk_range.start..hunk_range.start],
23659 cx,
23660 );
23661 });
23662 }
23663 })
23664 } else {
23665 Button::new(("unstage", row as u64), "Unstage")
23666 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23667 .tooltip({
23668 let focus_handle = editor.focus_handle(cx);
23669 move |window, cx| {
23670 Tooltip::for_action_in(
23671 "Unstage Hunk",
23672 &::git::ToggleStaged,
23673 &focus_handle,
23674 window,
23675 cx,
23676 )
23677 }
23678 })
23679 .on_click({
23680 let editor = editor.clone();
23681 move |_event, _window, cx| {
23682 editor.update(cx, |editor, cx| {
23683 editor.stage_or_unstage_diff_hunks(
23684 false,
23685 vec![hunk_range.start..hunk_range.start],
23686 cx,
23687 );
23688 });
23689 }
23690 })
23691 })
23692 .child(
23693 Button::new(("restore", row as u64), "Restore")
23694 .tooltip({
23695 let focus_handle = editor.focus_handle(cx);
23696 move |window, cx| {
23697 Tooltip::for_action_in(
23698 "Restore Hunk",
23699 &::git::Restore,
23700 &focus_handle,
23701 window,
23702 cx,
23703 )
23704 }
23705 })
23706 .on_click({
23707 let editor = editor.clone();
23708 move |_event, window, cx| {
23709 editor.update(cx, |editor, cx| {
23710 let snapshot = editor.snapshot(window, cx);
23711 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23712 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23713 });
23714 }
23715 })
23716 .disabled(is_created_file),
23717 )
23718 .when(
23719 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23720 |el| {
23721 el.child(
23722 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23723 .shape(IconButtonShape::Square)
23724 .icon_size(IconSize::Small)
23725 // .disabled(!has_multiple_hunks)
23726 .tooltip({
23727 let focus_handle = editor.focus_handle(cx);
23728 move |window, cx| {
23729 Tooltip::for_action_in(
23730 "Next Hunk",
23731 &GoToHunk,
23732 &focus_handle,
23733 window,
23734 cx,
23735 )
23736 }
23737 })
23738 .on_click({
23739 let editor = editor.clone();
23740 move |_event, window, cx| {
23741 editor.update(cx, |editor, cx| {
23742 let snapshot = editor.snapshot(window, cx);
23743 let position =
23744 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23745 editor.go_to_hunk_before_or_after_position(
23746 &snapshot,
23747 position,
23748 Direction::Next,
23749 window,
23750 cx,
23751 );
23752 editor.expand_selected_diff_hunks(cx);
23753 });
23754 }
23755 }),
23756 )
23757 .child(
23758 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23759 .shape(IconButtonShape::Square)
23760 .icon_size(IconSize::Small)
23761 // .disabled(!has_multiple_hunks)
23762 .tooltip({
23763 let focus_handle = editor.focus_handle(cx);
23764 move |window, cx| {
23765 Tooltip::for_action_in(
23766 "Previous Hunk",
23767 &GoToPreviousHunk,
23768 &focus_handle,
23769 window,
23770 cx,
23771 )
23772 }
23773 })
23774 .on_click({
23775 let editor = editor.clone();
23776 move |_event, window, cx| {
23777 editor.update(cx, |editor, cx| {
23778 let snapshot = editor.snapshot(window, cx);
23779 let point =
23780 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23781 editor.go_to_hunk_before_or_after_position(
23782 &snapshot,
23783 point,
23784 Direction::Prev,
23785 window,
23786 cx,
23787 );
23788 editor.expand_selected_diff_hunks(cx);
23789 });
23790 }
23791 }),
23792 )
23793 },
23794 )
23795 .into_any_element()
23796}