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 display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
58};
59pub use editor_settings_controls::*;
60pub use element::{
61 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
62};
63pub use git::blame::BlameRenderer;
64pub use hover_popover::hover_markdown_style;
65pub use inline_completion::Direction;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, DisableAiSettings, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
111 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
112 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
113 div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use highlight_matching_bracket::refresh_matching_bracket_highlights;
116use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
117use hover_popover::{HoverState, hide_hover};
118use indent_guides::ActiveIndentGuidesState;
119use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
120use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
121use itertools::Itertools;
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId, LanguageServerName,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DocumentHighlight, InlayHint, Location, LocationLink, PrepareRenameResponse,
152 Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253struct InlineValueCache {
254 enabled: bool,
255 inlays: Vec<InlayId>,
256 refresh_task: Task<Option<()>>,
257}
258
259impl InlineValueCache {
260 fn new(enabled: bool) -> Self {
261 Self {
262 enabled,
263 inlays: Vec::new(),
264 refresh_task: Task::ready(None),
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
270pub enum InlayId {
271 InlineCompletion(usize),
272 DebuggerValue(usize),
273 // LSP
274 Hint(usize),
275 Color(usize),
276}
277
278impl InlayId {
279 fn id(&self) -> usize {
280 match self {
281 Self::InlineCompletion(id) => *id,
282 Self::DebuggerValue(id) => *id,
283 Self::Hint(id) => *id,
284 Self::Color(id) => *id,
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291enum DocumentHighlightRead {}
292enum DocumentHighlightWrite {}
293enum InputComposition {}
294pub enum PendingInput {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372 cx.on_action(move |_: &workspace::NewWindow, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 cx.activate(true);
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntry<Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntry<Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// When set to `true`, the editor's height will be determined by its content.
491 sized_by_content: bool,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sized_by_content: false,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub inline_completion_styles: InlineCompletionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 inline_completion_styles: InlineCompletionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 HighlightStyle {
585 color: Some(cx.theme().status().hint),
586 background_color: show_background.then(|| cx.theme().status().hint_background),
587 ..HighlightStyle::default()
588 }
589}
590
591pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
592 InlineCompletionStyles {
593 insertion: HighlightStyle {
594 color: Some(cx.theme().status().predictive),
595 ..HighlightStyle::default()
596 },
597 whitespace: HighlightStyle {
598 background_color: Some(cx.theme().status().created_background),
599 ..HighlightStyle::default()
600 },
601 }
602}
603
604type CompletionId = usize;
605
606pub(crate) enum EditDisplayMode {
607 TabAccept,
608 DiffPopover,
609 Inline,
610}
611
612enum InlineCompletion {
613 Edit {
614 edits: Vec<(Range<Anchor>, String)>,
615 edit_preview: Option<EditPreview>,
616 display_mode: EditDisplayMode,
617 snapshot: BufferSnapshot,
618 },
619 Move {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623}
624
625struct InlineCompletionState {
626 inlay_ids: Vec<InlayId>,
627 completion: InlineCompletion,
628 completion_id: Option<SharedString>,
629 invalidation_range: Range<Anchor>,
630}
631
632enum EditPredictionSettings {
633 Disabled,
634 Enabled {
635 show_in_menu: bool,
636 preview_requires_modifier: bool,
637 },
638}
639
640enum InlineCompletionHighlight {}
641
642#[derive(Debug, Clone)]
643struct InlineDiagnostic {
644 message: SharedString,
645 group_id: usize,
646 is_primary: bool,
647 start: Point,
648 severity: lsp::DiagnosticSeverity,
649}
650
651pub enum MenuInlineCompletionsPolicy {
652 Never,
653 ByProvider,
654}
655
656pub enum EditPredictionPreview {
657 /// Modifier is not pressed
658 Inactive { released_too_fast: bool },
659 /// Modifier pressed
660 Active {
661 since: Instant,
662 previous_scroll_position: Option<ScrollAnchor>,
663 },
664}
665
666impl EditPredictionPreview {
667 pub fn released_too_fast(&self) -> bool {
668 match self {
669 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
670 EditPredictionPreview::Active { .. } => false,
671 }
672 }
673
674 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
675 if let EditPredictionPreview::Active {
676 previous_scroll_position,
677 ..
678 } = self
679 {
680 *previous_scroll_position = scroll_position;
681 }
682 }
683}
684
685pub struct ContextMenuOptions {
686 pub min_entries_visible: usize,
687 pub max_entries_visible: usize,
688 pub placement: Option<ContextMenuPlacement>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum ContextMenuPlacement {
693 Above,
694 Below,
695}
696
697#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
698struct EditorActionId(usize);
699
700impl EditorActionId {
701 pub fn post_inc(&mut self) -> Self {
702 let answer = self.0;
703
704 *self = Self(answer + 1);
705
706 Self(answer)
707 }
708}
709
710// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
711// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
712
713type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
714type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
715
716#[derive(Default)]
717struct ScrollbarMarkerState {
718 scrollbar_size: Size<Pixels>,
719 dirty: bool,
720 markers: Arc<[PaintQuad]>,
721 pending_refresh: Option<Task<Result<()>>>,
722}
723
724impl ScrollbarMarkerState {
725 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
726 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
727 }
728}
729
730#[derive(Clone, Copy, PartialEq, Eq)]
731pub enum MinimapVisibility {
732 Disabled,
733 Enabled {
734 /// The configuration currently present in the users settings.
735 setting_configuration: bool,
736 /// Whether to override the currently set visibility from the users setting.
737 toggle_override: bool,
738 },
739}
740
741impl MinimapVisibility {
742 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
743 if mode.is_full() {
744 Self::Enabled {
745 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
746 toggle_override: false,
747 }
748 } else {
749 Self::Disabled
750 }
751 }
752
753 fn hidden(&self) -> Self {
754 match *self {
755 Self::Enabled {
756 setting_configuration,
757 ..
758 } => Self::Enabled {
759 setting_configuration,
760 toggle_override: setting_configuration,
761 },
762 Self::Disabled => Self::Disabled,
763 }
764 }
765
766 fn disabled(&self) -> bool {
767 match *self {
768 Self::Disabled => true,
769 _ => false,
770 }
771 }
772
773 fn settings_visibility(&self) -> bool {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => setting_configuration,
779 _ => false,
780 }
781 }
782
783 fn visible(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 toggle_override,
788 } => setting_configuration ^ toggle_override,
789 _ => false,
790 }
791 }
792
793 fn toggle_visibility(&self) -> Self {
794 match *self {
795 Self::Enabled {
796 toggle_override,
797 setting_configuration,
798 } => Self::Enabled {
799 setting_configuration,
800 toggle_override: !toggle_override,
801 },
802 Self::Disabled => Self::Disabled,
803 }
804 }
805}
806
807#[derive(Clone, Debug)]
808struct RunnableTasks {
809 templates: Vec<(TaskSourceKind, TaskTemplate)>,
810 offset: multi_buffer::Anchor,
811 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
812 column: u32,
813 // Values of all named captures, including those starting with '_'
814 extra_variables: HashMap<String, String>,
815 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
816 context_range: Range<BufferOffset>,
817}
818
819impl RunnableTasks {
820 fn resolve<'a>(
821 &'a self,
822 cx: &'a task::TaskContext,
823 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
824 self.templates.iter().filter_map(|(kind, template)| {
825 template
826 .resolve_task(&kind.to_id_base(), cx)
827 .map(|task| (kind.clone(), task))
828 })
829 }
830}
831
832#[derive(Clone)]
833pub struct ResolvedTasks {
834 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
835 position: Anchor,
836}
837
838#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
839struct BufferOffset(usize);
840
841// Addons allow storing per-editor state in other crates (e.g. Vim)
842pub trait Addon: 'static {
843 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
844
845 fn render_buffer_header_controls(
846 &self,
847 _: &ExcerptInfo,
848 _: &Window,
849 _: &App,
850 ) -> Option<AnyElement> {
851 None
852 }
853
854 fn to_any(&self) -> &dyn std::any::Any;
855
856 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
857 None
858 }
859}
860
861struct ChangeLocation {
862 current: Option<Vec<Anchor>>,
863 original: Vec<Anchor>,
864}
865impl ChangeLocation {
866 fn locations(&self) -> &[Anchor] {
867 self.current.as_ref().unwrap_or(&self.original)
868 }
869}
870
871/// A set of caret positions, registered when the editor was edited.
872pub struct ChangeList {
873 changes: Vec<ChangeLocation>,
874 /// Currently "selected" change.
875 position: Option<usize>,
876}
877
878impl ChangeList {
879 pub fn new() -> Self {
880 Self {
881 changes: Vec::new(),
882 position: None,
883 }
884 }
885
886 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
887 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
888 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
889 if self.changes.is_empty() {
890 return None;
891 }
892
893 let prev = self.position.unwrap_or(self.changes.len());
894 let next = if direction == Direction::Prev {
895 prev.saturating_sub(count)
896 } else {
897 (prev + count).min(self.changes.len() - 1)
898 };
899 self.position = Some(next);
900 self.changes.get(next).map(|change| change.locations())
901 }
902
903 /// Adds a new change to the list, resetting the change list position.
904 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
905 self.position.take();
906 if let Some(last) = self.changes.last_mut()
907 && group
908 {
909 last.current = Some(new_positions)
910 } else {
911 self.changes.push(ChangeLocation {
912 original: new_positions,
913 current: None,
914 });
915 }
916 }
917
918 pub fn last(&self) -> Option<&[Anchor]> {
919 self.changes.last().map(|change| change.locations())
920 }
921
922 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
923 self.changes.last().map(|change| change.original.as_slice())
924 }
925
926 pub fn invert_last_group(&mut self) {
927 if let Some(last) = self.changes.last_mut() {
928 if let Some(current) = last.current.as_mut() {
929 mem::swap(&mut last.original, current);
930 }
931 }
932 }
933}
934
935#[derive(Clone)]
936struct InlineBlamePopoverState {
937 scroll_handle: ScrollHandle,
938 commit_message: Option<ParsedCommitMessage>,
939 markdown: Entity<Markdown>,
940}
941
942struct InlineBlamePopover {
943 position: gpui::Point<Pixels>,
944 hide_task: Option<Task<()>>,
945 popover_bounds: Option<Bounds<Pixels>>,
946 popover_state: InlineBlamePopoverState,
947 keyboard_grace: bool,
948}
949
950enum SelectionDragState {
951 /// State when no drag related activity is detected.
952 None,
953 /// State when the mouse is down on a selection that is about to be dragged.
954 ReadyToDrag {
955 selection: Selection<Anchor>,
956 click_position: gpui::Point<Pixels>,
957 mouse_down_time: Instant,
958 },
959 /// State when the mouse is dragging the selection in the editor.
960 Dragging {
961 selection: Selection<Anchor>,
962 drop_cursor: Selection<Anchor>,
963 hide_drop_cursor: bool,
964 },
965}
966
967enum ColumnarSelectionState {
968 FromMouse {
969 selection_tail: Anchor,
970 display_point: Option<DisplayPoint>,
971 },
972 FromSelection {
973 selection_tail: Anchor,
974 },
975}
976
977/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
978/// a breakpoint on them.
979#[derive(Clone, Copy, Debug, PartialEq, Eq)]
980struct PhantomBreakpointIndicator {
981 display_row: DisplayRow,
982 /// There's a small debounce between hovering over the line and showing the indicator.
983 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
984 is_active: bool,
985 collides_with_existing_breakpoint: bool,
986}
987
988/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
989///
990/// See the [module level documentation](self) for more information.
991pub struct Editor {
992 focus_handle: FocusHandle,
993 last_focused_descendant: Option<WeakFocusHandle>,
994 /// The text buffer being edited
995 buffer: Entity<MultiBuffer>,
996 /// Map of how text in the buffer should be displayed.
997 /// Handles soft wraps, folds, fake inlay text insertions, etc.
998 pub display_map: Entity<DisplayMap>,
999 pub selections: SelectionsCollection,
1000 pub scroll_manager: ScrollManager,
1001 /// When inline assist editors are linked, they all render cursors because
1002 /// typing enters text into each of them, even the ones that aren't focused.
1003 pub(crate) show_cursor_when_unfocused: bool,
1004 columnar_selection_state: Option<ColumnarSelectionState>,
1005 add_selections_state: Option<AddSelectionsState>,
1006 select_next_state: Option<SelectNextState>,
1007 select_prev_state: Option<SelectNextState>,
1008 selection_history: SelectionHistory,
1009 defer_selection_effects: bool,
1010 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1011 autoclose_regions: Vec<AutocloseRegion>,
1012 snippet_stack: InvalidationStack<SnippetState>,
1013 select_syntax_node_history: SelectSyntaxNodeHistory,
1014 ime_transaction: Option<TransactionId>,
1015 pub diagnostics_max_severity: DiagnosticSeverity,
1016 active_diagnostics: ActiveDiagnostic,
1017 show_inline_diagnostics: bool,
1018 inline_diagnostics_update: Task<()>,
1019 inline_diagnostics_enabled: bool,
1020 diagnostics_enabled: bool,
1021 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1022 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1023 hard_wrap: Option<usize>,
1024
1025 // TODO: make this a access method
1026 pub project: Option<Entity<Project>>,
1027 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1028 completion_provider: Option<Rc<dyn CompletionProvider>>,
1029 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1030 blink_manager: Entity<BlinkManager>,
1031 show_cursor_names: bool,
1032 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1033 pub show_local_selections: bool,
1034 mode: EditorMode,
1035 show_breadcrumbs: bool,
1036 show_gutter: bool,
1037 show_scrollbars: ScrollbarAxes,
1038 minimap_visibility: MinimapVisibility,
1039 offset_content: bool,
1040 disable_expand_excerpt_buttons: bool,
1041 show_line_numbers: Option<bool>,
1042 use_relative_line_numbers: Option<bool>,
1043 show_git_diff_gutter: Option<bool>,
1044 show_code_actions: Option<bool>,
1045 show_runnables: Option<bool>,
1046 show_breakpoints: Option<bool>,
1047 show_wrap_guides: Option<bool>,
1048 show_indent_guides: Option<bool>,
1049 placeholder_text: Option<Arc<str>>,
1050 highlight_order: usize,
1051 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1052 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1053 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1054 scrollbar_marker_state: ScrollbarMarkerState,
1055 active_indent_guides_state: ActiveIndentGuidesState,
1056 nav_history: Option<ItemNavHistory>,
1057 context_menu: RefCell<Option<CodeContextMenu>>,
1058 context_menu_options: Option<ContextMenuOptions>,
1059 mouse_context_menu: Option<MouseContextMenu>,
1060 completion_tasks: Vec<(CompletionId, Task<()>)>,
1061 inline_blame_popover: Option<InlineBlamePopover>,
1062 inline_blame_popover_show_task: Option<Task<()>>,
1063 signature_help_state: SignatureHelpState,
1064 auto_signature_help: Option<bool>,
1065 find_all_references_task_sources: Vec<Anchor>,
1066 next_completion_id: CompletionId,
1067 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1068 code_actions_task: Option<Task<Result<()>>>,
1069 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1070 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1071 document_highlights_task: Option<Task<()>>,
1072 linked_editing_range_task: Option<Task<Option<()>>>,
1073 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1074 pending_rename: Option<RenameState>,
1075 searchable: bool,
1076 cursor_shape: CursorShape,
1077 current_line_highlight: Option<CurrentLineHighlight>,
1078 collapse_matches: bool,
1079 autoindent_mode: Option<AutoindentMode>,
1080 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1081 input_enabled: bool,
1082 use_modal_editing: bool,
1083 read_only: bool,
1084 leader_id: Option<CollaboratorId>,
1085 remote_id: Option<ViewId>,
1086 pub hover_state: HoverState,
1087 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1088 gutter_hovered: bool,
1089 hovered_link_state: Option<HoveredLinkState>,
1090 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1091 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1092 active_inline_completion: Option<InlineCompletionState>,
1093 /// Used to prevent flickering as the user types while the menu is open
1094 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1095 edit_prediction_settings: EditPredictionSettings,
1096 inline_completions_hidden_for_vim_mode: bool,
1097 show_inline_completions_override: Option<bool>,
1098 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1099 edit_prediction_preview: EditPredictionPreview,
1100 edit_prediction_indent_conflict: bool,
1101 edit_prediction_requires_modifier_in_indent_conflict: bool,
1102 inlay_hint_cache: InlayHintCache,
1103 next_inlay_id: usize,
1104 _subscriptions: Vec<Subscription>,
1105 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1106 gutter_dimensions: GutterDimensions,
1107 style: Option<EditorStyle>,
1108 text_style_refinement: Option<TextStyleRefinement>,
1109 next_editor_action_id: EditorActionId,
1110 editor_actions: Rc<
1111 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1112 >,
1113 use_autoclose: bool,
1114 use_auto_surround: bool,
1115 auto_replace_emoji_shortcode: bool,
1116 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1117 show_git_blame_gutter: bool,
1118 show_git_blame_inline: bool,
1119 show_git_blame_inline_delay_task: Option<Task<()>>,
1120 git_blame_inline_enabled: bool,
1121 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1122 serialize_dirty_buffers: bool,
1123 show_selection_menu: Option<bool>,
1124 blame: Option<Entity<GitBlame>>,
1125 blame_subscription: Option<Subscription>,
1126 custom_context_menu: Option<
1127 Box<
1128 dyn 'static
1129 + Fn(
1130 &mut Self,
1131 DisplayPoint,
1132 &mut Window,
1133 &mut Context<Self>,
1134 ) -> Option<Entity<ui::ContextMenu>>,
1135 >,
1136 >,
1137 last_bounds: Option<Bounds<Pixels>>,
1138 last_position_map: Option<Rc<PositionMap>>,
1139 expect_bounds_change: Option<Bounds<Pixels>>,
1140 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1141 tasks_update_task: Option<Task<()>>,
1142 breakpoint_store: Option<Entity<BreakpointStore>>,
1143 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1144 hovered_diff_hunk_row: Option<DisplayRow>,
1145 pull_diagnostics_task: Task<()>,
1146 in_project_search: bool,
1147 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1148 breadcrumb_header: Option<String>,
1149 focused_block: Option<FocusedBlock>,
1150 next_scroll_position: NextScrollCursorCenterTopBottom,
1151 addons: HashMap<TypeId, Box<dyn Addon>>,
1152 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1153 load_diff_task: Option<Shared<Task<()>>>,
1154 /// Whether we are temporarily displaying a diff other than git's
1155 temporary_diff_override: bool,
1156 selection_mark_mode: bool,
1157 toggle_fold_multiple_buffers: Task<()>,
1158 _scroll_cursor_center_top_bottom_task: Task<()>,
1159 serialize_selections: Task<()>,
1160 serialize_folds: Task<()>,
1161 mouse_cursor_hidden: bool,
1162 minimap: Option<Entity<Self>>,
1163 hide_mouse_mode: HideMouseMode,
1164 pub change_list: ChangeList,
1165 inline_value_cache: InlineValueCache,
1166 selection_drag_state: SelectionDragState,
1167 next_color_inlay_id: usize,
1168 colors: Option<LspColorData>,
1169 folding_newlines: Task<()>,
1170}
1171
1172#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1173enum NextScrollCursorCenterTopBottom {
1174 #[default]
1175 Center,
1176 Top,
1177 Bottom,
1178}
1179
1180impl NextScrollCursorCenterTopBottom {
1181 fn next(&self) -> Self {
1182 match self {
1183 Self::Center => Self::Top,
1184 Self::Top => Self::Bottom,
1185 Self::Bottom => Self::Center,
1186 }
1187 }
1188}
1189
1190#[derive(Clone)]
1191pub struct EditorSnapshot {
1192 pub mode: EditorMode,
1193 show_gutter: bool,
1194 show_line_numbers: Option<bool>,
1195 show_git_diff_gutter: Option<bool>,
1196 show_code_actions: Option<bool>,
1197 show_runnables: Option<bool>,
1198 show_breakpoints: Option<bool>,
1199 git_blame_gutter_max_author_length: Option<usize>,
1200 pub display_snapshot: DisplaySnapshot,
1201 pub placeholder_text: Option<Arc<str>>,
1202 is_focused: bool,
1203 scroll_anchor: ScrollAnchor,
1204 ongoing_scroll: OngoingScroll,
1205 current_line_highlight: CurrentLineHighlight,
1206 gutter_hovered: bool,
1207}
1208
1209#[derive(Default, Debug, Clone, Copy)]
1210pub struct GutterDimensions {
1211 pub left_padding: Pixels,
1212 pub right_padding: Pixels,
1213 pub width: Pixels,
1214 pub margin: Pixels,
1215 pub git_blame_entries_width: Option<Pixels>,
1216}
1217
1218impl GutterDimensions {
1219 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1220 Self {
1221 margin: Self::default_gutter_margin(font_id, font_size, cx),
1222 ..Default::default()
1223 }
1224 }
1225
1226 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1227 -cx.text_system().descent(font_id, font_size)
1228 }
1229 /// The full width of the space taken up by the gutter.
1230 pub fn full_width(&self) -> Pixels {
1231 self.margin + self.width
1232 }
1233
1234 /// The width of the space reserved for the fold indicators,
1235 /// use alongside 'justify_end' and `gutter_width` to
1236 /// right align content with the line numbers
1237 pub fn fold_area_width(&self) -> Pixels {
1238 self.margin + self.right_padding
1239 }
1240}
1241
1242struct CharacterDimensions {
1243 em_width: Pixels,
1244 em_advance: Pixels,
1245 line_height: Pixels,
1246}
1247
1248#[derive(Debug)]
1249pub struct RemoteSelection {
1250 pub replica_id: ReplicaId,
1251 pub selection: Selection<Anchor>,
1252 pub cursor_shape: CursorShape,
1253 pub collaborator_id: CollaboratorId,
1254 pub line_mode: bool,
1255 pub user_name: Option<SharedString>,
1256 pub color: PlayerColor,
1257}
1258
1259#[derive(Clone, Debug)]
1260struct SelectionHistoryEntry {
1261 selections: Arc<[Selection<Anchor>]>,
1262 select_next_state: Option<SelectNextState>,
1263 select_prev_state: Option<SelectNextState>,
1264 add_selections_state: Option<AddSelectionsState>,
1265}
1266
1267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1268enum SelectionHistoryMode {
1269 Normal,
1270 Undoing,
1271 Redoing,
1272 Skipping,
1273}
1274
1275#[derive(Clone, PartialEq, Eq, Hash)]
1276struct HoveredCursor {
1277 replica_id: u16,
1278 selection_id: usize,
1279}
1280
1281impl Default for SelectionHistoryMode {
1282 fn default() -> Self {
1283 Self::Normal
1284 }
1285}
1286
1287#[derive(Debug)]
1288/// SelectionEffects controls the side-effects of updating the selection.
1289///
1290/// The default behaviour does "what you mostly want":
1291/// - it pushes to the nav history if the cursor moved by >10 lines
1292/// - it re-triggers completion requests
1293/// - it scrolls to fit
1294///
1295/// You might want to modify these behaviours. For example when doing a "jump"
1296/// like go to definition, we always want to add to nav history; but when scrolling
1297/// in vim mode we never do.
1298///
1299/// Similarly, you might want to disable scrolling if you don't want the viewport to
1300/// move.
1301#[derive(Clone)]
1302pub struct SelectionEffects {
1303 nav_history: Option<bool>,
1304 completions: bool,
1305 scroll: Option<Autoscroll>,
1306}
1307
1308impl Default for SelectionEffects {
1309 fn default() -> Self {
1310 Self {
1311 nav_history: None,
1312 completions: true,
1313 scroll: Some(Autoscroll::fit()),
1314 }
1315 }
1316}
1317impl SelectionEffects {
1318 pub fn scroll(scroll: Autoscroll) -> Self {
1319 Self {
1320 scroll: Some(scroll),
1321 ..Default::default()
1322 }
1323 }
1324
1325 pub fn no_scroll() -> Self {
1326 Self {
1327 scroll: None,
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn completions(self, completions: bool) -> Self {
1333 Self {
1334 completions,
1335 ..self
1336 }
1337 }
1338
1339 pub fn nav_history(self, nav_history: bool) -> Self {
1340 Self {
1341 nav_history: Some(nav_history),
1342 ..self
1343 }
1344 }
1345}
1346
1347struct DeferredSelectionEffectsState {
1348 changed: bool,
1349 effects: SelectionEffects,
1350 old_cursor_position: Anchor,
1351 history_entry: SelectionHistoryEntry,
1352}
1353
1354#[derive(Default)]
1355struct SelectionHistory {
1356 #[allow(clippy::type_complexity)]
1357 selections_by_transaction:
1358 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1359 mode: SelectionHistoryMode,
1360 undo_stack: VecDeque<SelectionHistoryEntry>,
1361 redo_stack: VecDeque<SelectionHistoryEntry>,
1362}
1363
1364impl SelectionHistory {
1365 #[track_caller]
1366 fn insert_transaction(
1367 &mut self,
1368 transaction_id: TransactionId,
1369 selections: Arc<[Selection<Anchor>]>,
1370 ) {
1371 if selections.is_empty() {
1372 log::error!(
1373 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1374 std::panic::Location::caller()
1375 );
1376 return;
1377 }
1378 self.selections_by_transaction
1379 .insert(transaction_id, (selections, None));
1380 }
1381
1382 #[allow(clippy::type_complexity)]
1383 fn transaction(
1384 &self,
1385 transaction_id: TransactionId,
1386 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1387 self.selections_by_transaction.get(&transaction_id)
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction_mut(
1392 &mut self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get_mut(&transaction_id)
1396 }
1397
1398 fn push(&mut self, entry: SelectionHistoryEntry) {
1399 if !entry.selections.is_empty() {
1400 match self.mode {
1401 SelectionHistoryMode::Normal => {
1402 self.push_undo(entry);
1403 self.redo_stack.clear();
1404 }
1405 SelectionHistoryMode::Undoing => self.push_redo(entry),
1406 SelectionHistoryMode::Redoing => self.push_undo(entry),
1407 SelectionHistoryMode::Skipping => {}
1408 }
1409 }
1410 }
1411
1412 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1413 if self
1414 .undo_stack
1415 .back()
1416 .map_or(true, |e| e.selections != entry.selections)
1417 {
1418 self.undo_stack.push_back(entry);
1419 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1420 self.undo_stack.pop_front();
1421 }
1422 }
1423 }
1424
1425 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .redo_stack
1428 .back()
1429 .map_or(true, |e| e.selections != entry.selections)
1430 {
1431 self.redo_stack.push_back(entry);
1432 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.redo_stack.pop_front();
1434 }
1435 }
1436 }
1437}
1438
1439#[derive(Clone, Copy)]
1440pub struct RowHighlightOptions {
1441 pub autoscroll: bool,
1442 pub include_gutter: bool,
1443}
1444
1445impl Default for RowHighlightOptions {
1446 fn default() -> Self {
1447 Self {
1448 autoscroll: Default::default(),
1449 include_gutter: true,
1450 }
1451 }
1452}
1453
1454struct RowHighlight {
1455 index: usize,
1456 range: Range<Anchor>,
1457 color: Hsla,
1458 options: RowHighlightOptions,
1459 type_id: TypeId,
1460}
1461
1462#[derive(Clone, Debug)]
1463struct AddSelectionsState {
1464 groups: Vec<AddSelectionsGroup>,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsGroup {
1469 above: bool,
1470 stack: Vec<usize>,
1471}
1472
1473#[derive(Clone)]
1474struct SelectNextState {
1475 query: AhoCorasick,
1476 wordwise: bool,
1477 done: bool,
1478}
1479
1480impl std::fmt::Debug for SelectNextState {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 f.debug_struct(std::any::type_name::<Self>())
1483 .field("wordwise", &self.wordwise)
1484 .field("done", &self.done)
1485 .finish()
1486 }
1487}
1488
1489#[derive(Debug)]
1490struct AutocloseRegion {
1491 selection_id: usize,
1492 range: Range<Anchor>,
1493 pair: BracketPair,
1494}
1495
1496#[derive(Debug)]
1497struct SnippetState {
1498 ranges: Vec<Vec<Range<Anchor>>>,
1499 active_index: usize,
1500 choices: Vec<Option<Vec<String>>>,
1501}
1502
1503#[doc(hidden)]
1504pub struct RenameState {
1505 pub range: Range<Anchor>,
1506 pub old_name: Arc<str>,
1507 pub editor: Entity<Editor>,
1508 block_id: CustomBlockId,
1509}
1510
1511struct InvalidationStack<T>(Vec<T>);
1512
1513struct RegisteredInlineCompletionProvider {
1514 provider: Arc<dyn InlineCompletionProviderHandle>,
1515 _subscription: Subscription,
1516}
1517
1518#[derive(Debug, PartialEq, Eq)]
1519pub struct ActiveDiagnosticGroup {
1520 pub active_range: Range<Anchor>,
1521 pub active_message: String,
1522 pub group_id: usize,
1523 pub blocks: HashSet<CustomBlockId>,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527
1528pub(crate) enum ActiveDiagnostic {
1529 None,
1530 All,
1531 Group(ActiveDiagnosticGroup),
1532}
1533
1534#[derive(Serialize, Deserialize, Clone, Debug)]
1535pub struct ClipboardSelection {
1536 /// The number of bytes in this selection.
1537 pub len: usize,
1538 /// Whether this was a full-line selection.
1539 pub is_entire_line: bool,
1540 /// The indentation of the first line when this content was originally copied.
1541 pub first_line_indent: u32,
1542}
1543
1544// selections, scroll behavior, was newest selection reversed
1545type SelectSyntaxNodeHistoryState = (
1546 Box<[Selection<usize>]>,
1547 SelectSyntaxNodeScrollBehavior,
1548 bool,
1549);
1550
1551#[derive(Default)]
1552struct SelectSyntaxNodeHistory {
1553 stack: Vec<SelectSyntaxNodeHistoryState>,
1554 // disable temporarily to allow changing selections without losing the stack
1555 pub disable_clearing: bool,
1556}
1557
1558impl SelectSyntaxNodeHistory {
1559 pub fn try_clear(&mut self) {
1560 if !self.disable_clearing {
1561 self.stack.clear();
1562 }
1563 }
1564
1565 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1566 self.stack.push(selection);
1567 }
1568
1569 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1570 self.stack.pop()
1571 }
1572}
1573
1574enum SelectSyntaxNodeScrollBehavior {
1575 CursorTop,
1576 FitSelection,
1577 CursorBottom,
1578}
1579
1580#[derive(Debug)]
1581pub(crate) struct NavigationData {
1582 cursor_anchor: Anchor,
1583 cursor_position: Point,
1584 scroll_anchor: ScrollAnchor,
1585 scroll_top_row: u32,
1586}
1587
1588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1589pub enum GotoDefinitionKind {
1590 Symbol,
1591 Declaration,
1592 Type,
1593 Implementation,
1594}
1595
1596#[derive(Debug, Clone)]
1597enum InlayHintRefreshReason {
1598 ModifiersChanged(bool),
1599 Toggle(bool),
1600 SettingsChange(InlayHintSettings),
1601 NewLinesShown,
1602 BufferEdited(HashSet<Arc<Language>>),
1603 RefreshRequested,
1604 ExcerptsRemoved(Vec<ExcerptId>),
1605}
1606
1607impl InlayHintRefreshReason {
1608 fn description(&self) -> &'static str {
1609 match self {
1610 Self::ModifiersChanged(_) => "modifiers changed",
1611 Self::Toggle(_) => "toggle",
1612 Self::SettingsChange(_) => "settings change",
1613 Self::NewLinesShown => "new lines shown",
1614 Self::BufferEdited(_) => "buffer edited",
1615 Self::RefreshRequested => "refresh requested",
1616 Self::ExcerptsRemoved(_) => "excerpts removed",
1617 }
1618 }
1619}
1620
1621pub enum FormatTarget {
1622 Buffers(HashSet<Entity<Buffer>>),
1623 Ranges(Vec<Range<MultiBufferPoint>>),
1624}
1625
1626pub(crate) struct FocusedBlock {
1627 id: BlockId,
1628 focus_handle: WeakFocusHandle,
1629}
1630
1631#[derive(Clone)]
1632enum JumpData {
1633 MultiBufferRow {
1634 row: MultiBufferRow,
1635 line_offset_from_top: u32,
1636 },
1637 MultiBufferPoint {
1638 excerpt_id: ExcerptId,
1639 position: Point,
1640 anchor: text::Anchor,
1641 line_offset_from_top: u32,
1642 },
1643}
1644
1645pub enum MultibufferSelectionMode {
1646 First,
1647 All,
1648}
1649
1650#[derive(Clone, Copy, Debug, Default)]
1651pub struct RewrapOptions {
1652 pub override_language_settings: bool,
1653 pub preserve_existing_whitespace: bool,
1654}
1655
1656impl Editor {
1657 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1658 let buffer = cx.new(|cx| Buffer::local("", cx));
1659 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1660 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1661 }
1662
1663 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::full(), buffer, None, window, cx)
1667 }
1668
1669 pub fn auto_height(
1670 min_lines: usize,
1671 max_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: Some(max_lines),
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1690 /// The editor grows as tall as needed to fit its content.
1691 pub fn auto_height_unbounded(
1692 min_lines: usize,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| Buffer::local("", cx));
1697 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1698 Self::new(
1699 EditorMode::AutoHeight {
1700 min_lines,
1701 max_lines: None,
1702 },
1703 buffer,
1704 None,
1705 window,
1706 cx,
1707 )
1708 }
1709
1710 pub fn for_buffer(
1711 buffer: Entity<Buffer>,
1712 project: Option<Entity<Project>>,
1713 window: &mut Window,
1714 cx: &mut Context<Self>,
1715 ) -> Self {
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(EditorMode::full(), buffer, project, window, cx)
1718 }
1719
1720 pub fn for_multibuffer(
1721 buffer: Entity<MultiBuffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let mut clone = Self::new(
1731 self.mode.clone(),
1732 self.buffer.clone(),
1733 self.project.clone(),
1734 window,
1735 cx,
1736 );
1737 self.display_map.update(cx, |display_map, cx| {
1738 let snapshot = display_map.snapshot(cx);
1739 clone.display_map.update(cx, |display_map, cx| {
1740 display_map.set_state(&snapshot, cx);
1741 });
1742 });
1743 clone.folds_did_change(cx);
1744 clone.selections.clone_state(&self.selections);
1745 clone.scroll_manager.clone_state(&self.scroll_manager);
1746 clone.searchable = self.searchable;
1747 clone.read_only = self.read_only;
1748 clone
1749 }
1750
1751 pub fn new(
1752 mode: EditorMode,
1753 buffer: Entity<MultiBuffer>,
1754 project: Option<Entity<Project>>,
1755 window: &mut Window,
1756 cx: &mut Context<Self>,
1757 ) -> Self {
1758 Editor::new_internal(mode, buffer, project, None, window, cx)
1759 }
1760
1761 fn new_internal(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 display_map: Option<Entity<DisplayMap>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 debug_assert!(
1770 display_map.is_none() || mode.is_minimap(),
1771 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1772 );
1773
1774 let full_mode = mode.is_full();
1775 let is_minimap = mode.is_minimap();
1776 let diagnostics_max_severity = if full_mode {
1777 EditorSettings::get_global(cx)
1778 .diagnostics_max_severity
1779 .unwrap_or(DiagnosticSeverity::Hint)
1780 } else {
1781 DiagnosticSeverity::Off
1782 };
1783 let style = window.text_style();
1784 let font_size = style.font_size.to_pixels(window.rem_size());
1785 let editor = cx.entity().downgrade();
1786 let fold_placeholder = FoldPlaceholder {
1787 constrain_width: true,
1788 render: Arc::new(move |fold_id, fold_range, cx| {
1789 let editor = editor.clone();
1790 div()
1791 .id(fold_id)
1792 .bg(cx.theme().colors().ghost_element_background)
1793 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1794 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1795 .rounded_xs()
1796 .size_full()
1797 .cursor_pointer()
1798 .child("⋯")
1799 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1800 .on_click(move |_, _window, cx| {
1801 editor
1802 .update(cx, |editor, cx| {
1803 editor.unfold_ranges(
1804 &[fold_range.start..fold_range.end],
1805 true,
1806 false,
1807 cx,
1808 );
1809 cx.stop_propagation();
1810 })
1811 .ok();
1812 })
1813 .into_any()
1814 }),
1815 merge_adjacent: true,
1816 ..FoldPlaceholder::default()
1817 };
1818 let display_map = display_map.unwrap_or_else(|| {
1819 cx.new(|cx| {
1820 DisplayMap::new(
1821 buffer.clone(),
1822 style.font(),
1823 font_size,
1824 None,
1825 FILE_HEADER_HEIGHT,
1826 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1827 fold_placeholder,
1828 diagnostics_max_severity,
1829 cx,
1830 )
1831 })
1832 });
1833
1834 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1835
1836 let blink_manager = cx.new(|cx| {
1837 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1838 if is_minimap {
1839 blink_manager.disable(cx);
1840 }
1841 blink_manager
1842 });
1843
1844 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1845 .then(|| language_settings::SoftWrap::None);
1846
1847 let mut project_subscriptions = Vec::new();
1848 if full_mode {
1849 if let Some(project) = project.as_ref() {
1850 project_subscriptions.push(cx.subscribe_in(
1851 project,
1852 window,
1853 |editor, _, event, window, cx| match event {
1854 project::Event::RefreshCodeLens => {
1855 // we always query lens with actions, without storing them, always refreshing them
1856 }
1857 project::Event::RefreshInlayHints => {
1858 editor
1859 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1860 }
1861 project::Event::LanguageServerAdded(..)
1862 | project::Event::LanguageServerRemoved(..) => {
1863 if editor.tasks_update_task.is_none() {
1864 editor.tasks_update_task =
1865 Some(editor.refresh_runnables(window, cx));
1866 }
1867 editor.update_lsp_data(true, None, window, cx);
1868 }
1869 project::Event::SnippetEdit(id, snippet_edits) => {
1870 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1871 let focus_handle = editor.focus_handle(cx);
1872 if focus_handle.is_focused(window) {
1873 let snapshot = buffer.read(cx).snapshot();
1874 for (range, snippet) in snippet_edits {
1875 let editor_range =
1876 language::range_from_lsp(*range).to_offset(&snapshot);
1877 editor
1878 .insert_snippet(
1879 &[editor_range],
1880 snippet.clone(),
1881 window,
1882 cx,
1883 )
1884 .ok();
1885 }
1886 }
1887 }
1888 }
1889 _ => {}
1890 },
1891 ));
1892 if let Some(task_inventory) = project
1893 .read(cx)
1894 .task_store()
1895 .read(cx)
1896 .task_inventory()
1897 .cloned()
1898 {
1899 project_subscriptions.push(cx.observe_in(
1900 &task_inventory,
1901 window,
1902 |editor, _, window, cx| {
1903 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1904 },
1905 ));
1906 };
1907
1908 project_subscriptions.push(cx.subscribe_in(
1909 &project.read(cx).breakpoint_store(),
1910 window,
1911 |editor, _, event, window, cx| match event {
1912 BreakpointStoreEvent::ClearDebugLines => {
1913 editor.clear_row_highlights::<ActiveDebugLine>();
1914 editor.refresh_inline_values(cx);
1915 }
1916 BreakpointStoreEvent::SetDebugLine => {
1917 if editor.go_to_active_debug_line(window, cx) {
1918 cx.stop_propagation();
1919 }
1920
1921 editor.refresh_inline_values(cx);
1922 }
1923 _ => {}
1924 },
1925 ));
1926 let git_store = project.read(cx).git_store().clone();
1927 let project = project.clone();
1928 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1929 match event {
1930 GitStoreEvent::RepositoryUpdated(
1931 _,
1932 RepositoryEvent::Updated {
1933 new_instance: true, ..
1934 },
1935 _,
1936 ) => {
1937 this.load_diff_task = Some(
1938 update_uncommitted_diff_for_buffer(
1939 cx.entity(),
1940 &project,
1941 this.buffer.read(cx).all_buffers(),
1942 this.buffer.clone(),
1943 cx,
1944 )
1945 .shared(),
1946 );
1947 }
1948 _ => {}
1949 }
1950 }));
1951 }
1952 }
1953
1954 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1955
1956 let inlay_hint_settings =
1957 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1958 let focus_handle = cx.focus_handle();
1959 if !is_minimap {
1960 cx.on_focus(&focus_handle, window, Self::handle_focus)
1961 .detach();
1962 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1963 .detach();
1964 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1965 .detach();
1966 cx.on_blur(&focus_handle, window, Self::handle_blur)
1967 .detach();
1968 cx.observe_pending_input(window, Self::observe_pending_input)
1969 .detach();
1970 }
1971
1972 let show_indent_guides = if matches!(
1973 mode,
1974 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1975 ) {
1976 Some(false)
1977 } else {
1978 None
1979 };
1980
1981 let breakpoint_store = match (&mode, project.as_ref()) {
1982 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1983 _ => None,
1984 };
1985
1986 let mut code_action_providers = Vec::new();
1987 let mut load_uncommitted_diff = None;
1988 if let Some(project) = project.clone() {
1989 load_uncommitted_diff = Some(
1990 update_uncommitted_diff_for_buffer(
1991 cx.entity(),
1992 &project,
1993 buffer.read(cx).all_buffers(),
1994 buffer.clone(),
1995 cx,
1996 )
1997 .shared(),
1998 );
1999 code_action_providers.push(Rc::new(project) as Rc<_>);
2000 }
2001
2002 let mut editor = Self {
2003 focus_handle,
2004 show_cursor_when_unfocused: false,
2005 last_focused_descendant: None,
2006 buffer: buffer.clone(),
2007 display_map: display_map.clone(),
2008 selections,
2009 scroll_manager: ScrollManager::new(cx),
2010 columnar_selection_state: None,
2011 add_selections_state: None,
2012 select_next_state: None,
2013 select_prev_state: None,
2014 selection_history: SelectionHistory::default(),
2015 defer_selection_effects: false,
2016 deferred_selection_effects_state: None,
2017 autoclose_regions: Vec::new(),
2018 snippet_stack: InvalidationStack::default(),
2019 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2020 ime_transaction: None,
2021 active_diagnostics: ActiveDiagnostic::None,
2022 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2023 inline_diagnostics_update: Task::ready(()),
2024 inline_diagnostics: Vec::new(),
2025 soft_wrap_mode_override,
2026 diagnostics_max_severity,
2027 hard_wrap: None,
2028 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2029 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2030 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2031 project,
2032 blink_manager: blink_manager.clone(),
2033 show_local_selections: true,
2034 show_scrollbars: ScrollbarAxes {
2035 horizontal: full_mode,
2036 vertical: full_mode,
2037 },
2038 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2039 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2040 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2041 show_gutter: full_mode,
2042 show_line_numbers: (!full_mode).then_some(false),
2043 use_relative_line_numbers: None,
2044 disable_expand_excerpt_buttons: !full_mode,
2045 show_git_diff_gutter: None,
2046 show_code_actions: None,
2047 show_runnables: None,
2048 show_breakpoints: None,
2049 show_wrap_guides: None,
2050 show_indent_guides,
2051 placeholder_text: None,
2052 highlight_order: 0,
2053 highlighted_rows: HashMap::default(),
2054 background_highlights: TreeMap::default(),
2055 gutter_highlights: TreeMap::default(),
2056 scrollbar_marker_state: ScrollbarMarkerState::default(),
2057 active_indent_guides_state: ActiveIndentGuidesState::default(),
2058 nav_history: None,
2059 context_menu: RefCell::new(None),
2060 context_menu_options: None,
2061 mouse_context_menu: None,
2062 completion_tasks: Vec::new(),
2063 inline_blame_popover: None,
2064 inline_blame_popover_show_task: None,
2065 signature_help_state: SignatureHelpState::default(),
2066 auto_signature_help: None,
2067 find_all_references_task_sources: Vec::new(),
2068 next_completion_id: 0,
2069 next_inlay_id: 0,
2070 code_action_providers,
2071 available_code_actions: None,
2072 code_actions_task: None,
2073 quick_selection_highlight_task: None,
2074 debounced_selection_highlight_task: None,
2075 document_highlights_task: None,
2076 linked_editing_range_task: None,
2077 pending_rename: None,
2078 searchable: !is_minimap,
2079 cursor_shape: EditorSettings::get_global(cx)
2080 .cursor_shape
2081 .unwrap_or_default(),
2082 current_line_highlight: None,
2083 autoindent_mode: Some(AutoindentMode::EachLine),
2084 collapse_matches: false,
2085 workspace: None,
2086 input_enabled: !is_minimap,
2087 use_modal_editing: full_mode,
2088 read_only: is_minimap,
2089 use_autoclose: true,
2090 use_auto_surround: true,
2091 auto_replace_emoji_shortcode: false,
2092 jsx_tag_auto_close_enabled_in_any_buffer: false,
2093 leader_id: None,
2094 remote_id: None,
2095 hover_state: HoverState::default(),
2096 pending_mouse_down: None,
2097 hovered_link_state: None,
2098 edit_prediction_provider: None,
2099 active_inline_completion: None,
2100 stale_inline_completion_in_menu: None,
2101 edit_prediction_preview: EditPredictionPreview::Inactive {
2102 released_too_fast: false,
2103 },
2104 inline_diagnostics_enabled: full_mode,
2105 diagnostics_enabled: full_mode,
2106 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2107 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2108 gutter_hovered: false,
2109 pixel_position_of_newest_cursor: None,
2110 last_bounds: None,
2111 last_position_map: None,
2112 expect_bounds_change: None,
2113 gutter_dimensions: GutterDimensions::default(),
2114 style: None,
2115 show_cursor_names: false,
2116 hovered_cursors: HashMap::default(),
2117 next_editor_action_id: EditorActionId::default(),
2118 editor_actions: Rc::default(),
2119 inline_completions_hidden_for_vim_mode: false,
2120 show_inline_completions_override: None,
2121 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2122 edit_prediction_settings: EditPredictionSettings::Disabled,
2123 edit_prediction_indent_conflict: false,
2124 edit_prediction_requires_modifier_in_indent_conflict: true,
2125 custom_context_menu: None,
2126 show_git_blame_gutter: false,
2127 show_git_blame_inline: false,
2128 show_selection_menu: None,
2129 show_git_blame_inline_delay_task: None,
2130 git_blame_inline_enabled: full_mode
2131 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2132 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2133 serialize_dirty_buffers: !is_minimap
2134 && ProjectSettings::get_global(cx)
2135 .session
2136 .restore_unsaved_buffers,
2137 blame: None,
2138 blame_subscription: None,
2139 tasks: BTreeMap::default(),
2140
2141 breakpoint_store,
2142 gutter_breakpoint_indicator: (None, None),
2143 hovered_diff_hunk_row: None,
2144 _subscriptions: (!is_minimap)
2145 .then(|| {
2146 vec![
2147 cx.observe(&buffer, Self::on_buffer_changed),
2148 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2149 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2150 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2151 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2152 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2153 cx.observe_window_activation(window, |editor, window, cx| {
2154 let active = window.is_window_active();
2155 editor.blink_manager.update(cx, |blink_manager, cx| {
2156 if active {
2157 blink_manager.enable(cx);
2158 } else {
2159 blink_manager.disable(cx);
2160 }
2161 });
2162 if active {
2163 editor.show_mouse_cursor(cx);
2164 }
2165 }),
2166 ]
2167 })
2168 .unwrap_or_default(),
2169 tasks_update_task: None,
2170 pull_diagnostics_task: Task::ready(()),
2171 colors: None,
2172 next_color_inlay_id: 0,
2173 linked_edit_ranges: Default::default(),
2174 in_project_search: false,
2175 previous_search_ranges: None,
2176 breadcrumb_header: None,
2177 focused_block: None,
2178 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2179 addons: HashMap::default(),
2180 registered_buffers: HashMap::default(),
2181 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2182 selection_mark_mode: false,
2183 toggle_fold_multiple_buffers: Task::ready(()),
2184 serialize_selections: Task::ready(()),
2185 serialize_folds: Task::ready(()),
2186 text_style_refinement: None,
2187 load_diff_task: load_uncommitted_diff,
2188 temporary_diff_override: false,
2189 mouse_cursor_hidden: false,
2190 minimap: None,
2191 hide_mouse_mode: EditorSettings::get_global(cx)
2192 .hide_mouse
2193 .unwrap_or_default(),
2194 change_list: ChangeList::new(),
2195 mode,
2196 selection_drag_state: SelectionDragState::None,
2197 folding_newlines: Task::ready(()),
2198 };
2199
2200 if is_minimap {
2201 return editor;
2202 }
2203
2204 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2205 editor
2206 ._subscriptions
2207 .push(cx.observe(breakpoints, |_, _, cx| {
2208 cx.notify();
2209 }));
2210 }
2211 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2212 editor._subscriptions.extend(project_subscriptions);
2213
2214 editor._subscriptions.push(cx.subscribe_in(
2215 &cx.entity(),
2216 window,
2217 |editor, _, e: &EditorEvent, window, cx| match e {
2218 EditorEvent::ScrollPositionChanged { local, .. } => {
2219 if *local {
2220 let new_anchor = editor.scroll_manager.anchor();
2221 let snapshot = editor.snapshot(window, cx);
2222 editor.update_restoration_data(cx, move |data| {
2223 data.scroll_position = (
2224 new_anchor.top_row(&snapshot.buffer_snapshot),
2225 new_anchor.offset,
2226 );
2227 });
2228 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2229 editor.inline_blame_popover.take();
2230 }
2231 }
2232 EditorEvent::Edited { .. } => {
2233 if !vim_enabled(cx) {
2234 let (map, selections) = editor.selections.all_adjusted_display(cx);
2235 let pop_state = editor
2236 .change_list
2237 .last()
2238 .map(|previous| {
2239 previous.len() == selections.len()
2240 && previous.iter().enumerate().all(|(ix, p)| {
2241 p.to_display_point(&map).row()
2242 == selections[ix].head().row()
2243 })
2244 })
2245 .unwrap_or(false);
2246 let new_positions = selections
2247 .into_iter()
2248 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2249 .collect();
2250 editor
2251 .change_list
2252 .push_to_change_list(pop_state, new_positions);
2253 }
2254 }
2255 _ => (),
2256 },
2257 ));
2258
2259 if let Some(dap_store) = editor
2260 .project
2261 .as_ref()
2262 .map(|project| project.read(cx).dap_store())
2263 {
2264 let weak_editor = cx.weak_entity();
2265
2266 editor
2267 ._subscriptions
2268 .push(
2269 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2270 let session_entity = cx.entity();
2271 weak_editor
2272 .update(cx, |editor, cx| {
2273 editor._subscriptions.push(
2274 cx.subscribe(&session_entity, Self::on_debug_session_event),
2275 );
2276 })
2277 .ok();
2278 }),
2279 );
2280
2281 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2282 editor
2283 ._subscriptions
2284 .push(cx.subscribe(&session, Self::on_debug_session_event));
2285 }
2286 }
2287
2288 // skip adding the initial selection to selection history
2289 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2290 editor.end_selection(window, cx);
2291 editor.selection_history.mode = SelectionHistoryMode::Normal;
2292
2293 editor.scroll_manager.show_scrollbars(window, cx);
2294 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2295
2296 if full_mode {
2297 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2298 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2299
2300 if editor.git_blame_inline_enabled {
2301 editor.start_git_blame_inline(false, window, cx);
2302 }
2303
2304 editor.go_to_active_debug_line(window, cx);
2305
2306 if let Some(buffer) = buffer.read(cx).as_singleton() {
2307 if let Some(project) = editor.project.as_ref() {
2308 let handle = project.update(cx, |project, cx| {
2309 project.register_buffer_with_language_servers(&buffer, cx)
2310 });
2311 editor
2312 .registered_buffers
2313 .insert(buffer.read(cx).remote_id(), handle);
2314 }
2315 }
2316
2317 editor.minimap =
2318 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2319 editor.colors = Some(LspColorData::new(cx));
2320 editor.update_lsp_data(false, None, window, cx);
2321 }
2322
2323 if editor.mode.is_full() {
2324 editor.report_editor_event("Editor Opened", None, cx);
2325 }
2326
2327 editor
2328 }
2329
2330 pub fn deploy_mouse_context_menu(
2331 &mut self,
2332 position: gpui::Point<Pixels>,
2333 context_menu: Entity<ContextMenu>,
2334 window: &mut Window,
2335 cx: &mut Context<Self>,
2336 ) {
2337 self.mouse_context_menu = Some(MouseContextMenu::new(
2338 self,
2339 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2340 context_menu,
2341 window,
2342 cx,
2343 ));
2344 }
2345
2346 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2347 self.mouse_context_menu
2348 .as_ref()
2349 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2350 }
2351
2352 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2353 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2354 }
2355
2356 fn key_context_internal(
2357 &self,
2358 has_active_edit_prediction: bool,
2359 window: &Window,
2360 cx: &App,
2361 ) -> KeyContext {
2362 let mut key_context = KeyContext::new_with_defaults();
2363 key_context.add("Editor");
2364 let mode = match self.mode {
2365 EditorMode::SingleLine { .. } => "single_line",
2366 EditorMode::AutoHeight { .. } => "auto_height",
2367 EditorMode::Minimap { .. } => "minimap",
2368 EditorMode::Full { .. } => "full",
2369 };
2370
2371 if EditorSettings::jupyter_enabled(cx) {
2372 key_context.add("jupyter");
2373 }
2374
2375 key_context.set("mode", mode);
2376 if self.pending_rename.is_some() {
2377 key_context.add("renaming");
2378 }
2379
2380 match self.context_menu.borrow().as_ref() {
2381 Some(CodeContextMenu::Completions(menu)) => {
2382 if menu.visible() {
2383 key_context.add("menu");
2384 key_context.add("showing_completions");
2385 }
2386 }
2387 Some(CodeContextMenu::CodeActions(menu)) => {
2388 if menu.visible() {
2389 key_context.add("menu");
2390 key_context.add("showing_code_actions")
2391 }
2392 }
2393 None => {}
2394 }
2395
2396 if self.signature_help_state.has_multiple_signatures() {
2397 key_context.add("showing_signature_help");
2398 }
2399
2400 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2401 if !self.focus_handle(cx).contains_focused(window, cx)
2402 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2403 {
2404 for addon in self.addons.values() {
2405 addon.extend_key_context(&mut key_context, cx)
2406 }
2407 }
2408
2409 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2410 if let Some(extension) = singleton_buffer
2411 .read(cx)
2412 .file()
2413 .and_then(|file| file.path().extension()?.to_str())
2414 {
2415 key_context.set("extension", extension.to_string());
2416 }
2417 } else {
2418 key_context.add("multibuffer");
2419 }
2420
2421 if has_active_edit_prediction {
2422 if self.edit_prediction_in_conflict() {
2423 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2424 } else {
2425 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2426 key_context.add("copilot_suggestion");
2427 }
2428 }
2429
2430 if self.selection_mark_mode {
2431 key_context.add("selection_mode");
2432 }
2433
2434 key_context
2435 }
2436
2437 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2438 if self.mouse_cursor_hidden {
2439 self.mouse_cursor_hidden = false;
2440 cx.notify();
2441 }
2442 }
2443
2444 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2445 let hide_mouse_cursor = match origin {
2446 HideMouseCursorOrigin::TypingAction => {
2447 matches!(
2448 self.hide_mouse_mode,
2449 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2450 )
2451 }
2452 HideMouseCursorOrigin::MovementAction => {
2453 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2454 }
2455 };
2456 if self.mouse_cursor_hidden != hide_mouse_cursor {
2457 self.mouse_cursor_hidden = hide_mouse_cursor;
2458 cx.notify();
2459 }
2460 }
2461
2462 pub fn edit_prediction_in_conflict(&self) -> bool {
2463 if !self.show_edit_predictions_in_menu() {
2464 return false;
2465 }
2466
2467 let showing_completions = self
2468 .context_menu
2469 .borrow()
2470 .as_ref()
2471 .map_or(false, |context| {
2472 matches!(context, CodeContextMenu::Completions(_))
2473 });
2474
2475 showing_completions
2476 || self.edit_prediction_requires_modifier()
2477 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2478 // bindings to insert tab characters.
2479 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2480 }
2481
2482 pub fn accept_edit_prediction_keybind(
2483 &self,
2484 accept_partial: bool,
2485 window: &Window,
2486 cx: &App,
2487 ) -> AcceptEditPredictionBinding {
2488 let key_context = self.key_context_internal(true, window, cx);
2489 let in_conflict = self.edit_prediction_in_conflict();
2490
2491 let bindings = if accept_partial {
2492 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2493 } else {
2494 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2495 };
2496
2497 // TODO: if the binding contains multiple keystrokes, display all of them, not
2498 // just the first one.
2499 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2500 !in_conflict
2501 || binding
2502 .keystrokes()
2503 .first()
2504 .map_or(false, |keystroke| keystroke.modifiers.modified())
2505 }))
2506 }
2507
2508 pub fn new_file(
2509 workspace: &mut Workspace,
2510 _: &workspace::NewFile,
2511 window: &mut Window,
2512 cx: &mut Context<Workspace>,
2513 ) {
2514 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2515 "Failed to create buffer",
2516 window,
2517 cx,
2518 |e, _, _| match e.error_code() {
2519 ErrorCode::RemoteUpgradeRequired => Some(format!(
2520 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2521 e.error_tag("required").unwrap_or("the latest version")
2522 )),
2523 _ => None,
2524 },
2525 );
2526 }
2527
2528 pub fn new_in_workspace(
2529 workspace: &mut Workspace,
2530 window: &mut Window,
2531 cx: &mut Context<Workspace>,
2532 ) -> Task<Result<Entity<Editor>>> {
2533 let project = workspace.project().clone();
2534 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2535
2536 cx.spawn_in(window, async move |workspace, cx| {
2537 let buffer = create.await?;
2538 workspace.update_in(cx, |workspace, window, cx| {
2539 let editor =
2540 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2541 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2542 editor
2543 })
2544 })
2545 }
2546
2547 fn new_file_vertical(
2548 workspace: &mut Workspace,
2549 _: &workspace::NewFileSplitVertical,
2550 window: &mut Window,
2551 cx: &mut Context<Workspace>,
2552 ) {
2553 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2554 }
2555
2556 fn new_file_horizontal(
2557 workspace: &mut Workspace,
2558 _: &workspace::NewFileSplitHorizontal,
2559 window: &mut Window,
2560 cx: &mut Context<Workspace>,
2561 ) {
2562 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2563 }
2564
2565 fn new_file_in_direction(
2566 workspace: &mut Workspace,
2567 direction: SplitDirection,
2568 window: &mut Window,
2569 cx: &mut Context<Workspace>,
2570 ) {
2571 let project = workspace.project().clone();
2572 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2573
2574 cx.spawn_in(window, async move |workspace, cx| {
2575 let buffer = create.await?;
2576 workspace.update_in(cx, move |workspace, window, cx| {
2577 workspace.split_item(
2578 direction,
2579 Box::new(
2580 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2581 ),
2582 window,
2583 cx,
2584 )
2585 })?;
2586 anyhow::Ok(())
2587 })
2588 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2589 match e.error_code() {
2590 ErrorCode::RemoteUpgradeRequired => Some(format!(
2591 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2592 e.error_tag("required").unwrap_or("the latest version")
2593 )),
2594 _ => None,
2595 }
2596 });
2597 }
2598
2599 pub fn leader_id(&self) -> Option<CollaboratorId> {
2600 self.leader_id
2601 }
2602
2603 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2604 &self.buffer
2605 }
2606
2607 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2608 self.workspace.as_ref()?.0.upgrade()
2609 }
2610
2611 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2612 self.buffer().read(cx).title(cx)
2613 }
2614
2615 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2616 let git_blame_gutter_max_author_length = self
2617 .render_git_blame_gutter(cx)
2618 .then(|| {
2619 if let Some(blame) = self.blame.as_ref() {
2620 let max_author_length =
2621 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2622 Some(max_author_length)
2623 } else {
2624 None
2625 }
2626 })
2627 .flatten();
2628
2629 EditorSnapshot {
2630 mode: self.mode.clone(),
2631 show_gutter: self.show_gutter,
2632 show_line_numbers: self.show_line_numbers,
2633 show_git_diff_gutter: self.show_git_diff_gutter,
2634 show_code_actions: self.show_code_actions,
2635 show_runnables: self.show_runnables,
2636 show_breakpoints: self.show_breakpoints,
2637 git_blame_gutter_max_author_length,
2638 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2639 scroll_anchor: self.scroll_manager.anchor(),
2640 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2641 placeholder_text: self.placeholder_text.clone(),
2642 is_focused: self.focus_handle.is_focused(window),
2643 current_line_highlight: self
2644 .current_line_highlight
2645 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2646 gutter_hovered: self.gutter_hovered,
2647 }
2648 }
2649
2650 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2651 self.buffer.read(cx).language_at(point, cx)
2652 }
2653
2654 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2655 self.buffer.read(cx).read(cx).file_at(point).cloned()
2656 }
2657
2658 pub fn active_excerpt(
2659 &self,
2660 cx: &App,
2661 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2662 self.buffer
2663 .read(cx)
2664 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2665 }
2666
2667 pub fn mode(&self) -> &EditorMode {
2668 &self.mode
2669 }
2670
2671 pub fn set_mode(&mut self, mode: EditorMode) {
2672 self.mode = mode;
2673 }
2674
2675 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2676 self.collaboration_hub.as_deref()
2677 }
2678
2679 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2680 self.collaboration_hub = Some(hub);
2681 }
2682
2683 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2684 self.in_project_search = in_project_search;
2685 }
2686
2687 pub fn set_custom_context_menu(
2688 &mut self,
2689 f: impl 'static
2690 + Fn(
2691 &mut Self,
2692 DisplayPoint,
2693 &mut Window,
2694 &mut Context<Self>,
2695 ) -> Option<Entity<ui::ContextMenu>>,
2696 ) {
2697 self.custom_context_menu = Some(Box::new(f))
2698 }
2699
2700 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2701 self.completion_provider = provider;
2702 }
2703
2704 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2705 self.semantics_provider.clone()
2706 }
2707
2708 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2709 self.semantics_provider = provider;
2710 }
2711
2712 pub fn set_edit_prediction_provider<T>(
2713 &mut self,
2714 provider: Option<Entity<T>>,
2715 window: &mut Window,
2716 cx: &mut Context<Self>,
2717 ) where
2718 T: EditPredictionProvider,
2719 {
2720 self.edit_prediction_provider =
2721 provider.map(|provider| RegisteredInlineCompletionProvider {
2722 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2723 if this.focus_handle.is_focused(window) {
2724 this.update_visible_inline_completion(window, cx);
2725 }
2726 }),
2727 provider: Arc::new(provider),
2728 });
2729 self.update_edit_prediction_settings(cx);
2730 self.refresh_inline_completion(false, false, window, cx);
2731 }
2732
2733 pub fn placeholder_text(&self) -> Option<&str> {
2734 self.placeholder_text.as_deref()
2735 }
2736
2737 pub fn set_placeholder_text(
2738 &mut self,
2739 placeholder_text: impl Into<Arc<str>>,
2740 cx: &mut Context<Self>,
2741 ) {
2742 let placeholder_text = Some(placeholder_text.into());
2743 if self.placeholder_text != placeholder_text {
2744 self.placeholder_text = placeholder_text;
2745 cx.notify();
2746 }
2747 }
2748
2749 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2750 self.cursor_shape = cursor_shape;
2751
2752 // Disrupt blink for immediate user feedback that the cursor shape has changed
2753 self.blink_manager.update(cx, BlinkManager::show_cursor);
2754
2755 cx.notify();
2756 }
2757
2758 pub fn set_current_line_highlight(
2759 &mut self,
2760 current_line_highlight: Option<CurrentLineHighlight>,
2761 ) {
2762 self.current_line_highlight = current_line_highlight;
2763 }
2764
2765 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2766 self.collapse_matches = collapse_matches;
2767 }
2768
2769 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2770 let buffers = self.buffer.read(cx).all_buffers();
2771 let Some(project) = self.project.as_ref() else {
2772 return;
2773 };
2774 project.update(cx, |project, cx| {
2775 for buffer in buffers {
2776 self.registered_buffers
2777 .entry(buffer.read(cx).remote_id())
2778 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2779 }
2780 })
2781 }
2782
2783 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2784 if self.collapse_matches {
2785 return range.start..range.start;
2786 }
2787 range.clone()
2788 }
2789
2790 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2791 if self.display_map.read(cx).clip_at_line_ends != clip {
2792 self.display_map
2793 .update(cx, |map, _| map.clip_at_line_ends = clip);
2794 }
2795 }
2796
2797 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2798 self.input_enabled = input_enabled;
2799 }
2800
2801 pub fn set_inline_completions_hidden_for_vim_mode(
2802 &mut self,
2803 hidden: bool,
2804 window: &mut Window,
2805 cx: &mut Context<Self>,
2806 ) {
2807 if hidden != self.inline_completions_hidden_for_vim_mode {
2808 self.inline_completions_hidden_for_vim_mode = hidden;
2809 if hidden {
2810 self.update_visible_inline_completion(window, cx);
2811 } else {
2812 self.refresh_inline_completion(true, false, window, cx);
2813 }
2814 }
2815 }
2816
2817 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2818 self.menu_inline_completions_policy = value;
2819 }
2820
2821 pub fn set_autoindent(&mut self, autoindent: bool) {
2822 if autoindent {
2823 self.autoindent_mode = Some(AutoindentMode::EachLine);
2824 } else {
2825 self.autoindent_mode = None;
2826 }
2827 }
2828
2829 pub fn read_only(&self, cx: &App) -> bool {
2830 self.read_only || self.buffer.read(cx).read_only()
2831 }
2832
2833 pub fn set_read_only(&mut self, read_only: bool) {
2834 self.read_only = read_only;
2835 }
2836
2837 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2838 self.use_autoclose = autoclose;
2839 }
2840
2841 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2842 self.use_auto_surround = auto_surround;
2843 }
2844
2845 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2846 self.auto_replace_emoji_shortcode = auto_replace;
2847 }
2848
2849 pub fn toggle_edit_predictions(
2850 &mut self,
2851 _: &ToggleEditPrediction,
2852 window: &mut Window,
2853 cx: &mut Context<Self>,
2854 ) {
2855 if self.show_inline_completions_override.is_some() {
2856 self.set_show_edit_predictions(None, window, cx);
2857 } else {
2858 let show_edit_predictions = !self.edit_predictions_enabled();
2859 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2860 }
2861 }
2862
2863 pub fn set_show_edit_predictions(
2864 &mut self,
2865 show_edit_predictions: Option<bool>,
2866 window: &mut Window,
2867 cx: &mut Context<Self>,
2868 ) {
2869 self.show_inline_completions_override = show_edit_predictions;
2870 self.update_edit_prediction_settings(cx);
2871
2872 if let Some(false) = show_edit_predictions {
2873 self.discard_inline_completion(false, cx);
2874 } else {
2875 self.refresh_inline_completion(false, true, window, cx);
2876 }
2877 }
2878
2879 fn inline_completions_disabled_in_scope(
2880 &self,
2881 buffer: &Entity<Buffer>,
2882 buffer_position: language::Anchor,
2883 cx: &App,
2884 ) -> bool {
2885 let snapshot = buffer.read(cx).snapshot();
2886 let settings = snapshot.settings_at(buffer_position, cx);
2887
2888 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2889 return false;
2890 };
2891
2892 scope.override_name().map_or(false, |scope_name| {
2893 settings
2894 .edit_predictions_disabled_in
2895 .iter()
2896 .any(|s| s == scope_name)
2897 })
2898 }
2899
2900 pub fn set_use_modal_editing(&mut self, to: bool) {
2901 self.use_modal_editing = to;
2902 }
2903
2904 pub fn use_modal_editing(&self) -> bool {
2905 self.use_modal_editing
2906 }
2907
2908 fn selections_did_change(
2909 &mut self,
2910 local: bool,
2911 old_cursor_position: &Anchor,
2912 effects: SelectionEffects,
2913 window: &mut Window,
2914 cx: &mut Context<Self>,
2915 ) {
2916 window.invalidate_character_coordinates();
2917
2918 // Copy selections to primary selection buffer
2919 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2920 if local {
2921 let selections = self.selections.all::<usize>(cx);
2922 let buffer_handle = self.buffer.read(cx).read(cx);
2923
2924 let mut text = String::new();
2925 for (index, selection) in selections.iter().enumerate() {
2926 let text_for_selection = buffer_handle
2927 .text_for_range(selection.start..selection.end)
2928 .collect::<String>();
2929
2930 text.push_str(&text_for_selection);
2931 if index != selections.len() - 1 {
2932 text.push('\n');
2933 }
2934 }
2935
2936 if !text.is_empty() {
2937 cx.write_to_primary(ClipboardItem::new_string(text));
2938 }
2939 }
2940
2941 let selection_anchors = self.selections.disjoint_anchors();
2942
2943 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2944 self.buffer.update(cx, |buffer, cx| {
2945 buffer.set_active_selections(
2946 &selection_anchors,
2947 self.selections.line_mode,
2948 self.cursor_shape,
2949 cx,
2950 )
2951 });
2952 }
2953 let display_map = self
2954 .display_map
2955 .update(cx, |display_map, cx| display_map.snapshot(cx));
2956 let buffer = &display_map.buffer_snapshot;
2957 if self.selections.count() == 1 {
2958 self.add_selections_state = None;
2959 }
2960 self.select_next_state = None;
2961 self.select_prev_state = None;
2962 self.select_syntax_node_history.try_clear();
2963 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2964 self.snippet_stack.invalidate(&selection_anchors, buffer);
2965 self.take_rename(false, window, cx);
2966
2967 let newest_selection = self.selections.newest_anchor();
2968 let new_cursor_position = newest_selection.head();
2969 let selection_start = newest_selection.start;
2970
2971 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2972 self.push_to_nav_history(
2973 *old_cursor_position,
2974 Some(new_cursor_position.to_point(buffer)),
2975 false,
2976 effects.nav_history == Some(true),
2977 cx,
2978 );
2979 }
2980
2981 if local {
2982 if let Some(buffer_id) = new_cursor_position.buffer_id {
2983 if !self.registered_buffers.contains_key(&buffer_id) {
2984 if let Some(project) = self.project.as_ref() {
2985 project.update(cx, |project, cx| {
2986 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2987 return;
2988 };
2989 self.registered_buffers.insert(
2990 buffer_id,
2991 project.register_buffer_with_language_servers(&buffer, cx),
2992 );
2993 })
2994 }
2995 }
2996 }
2997
2998 let mut context_menu = self.context_menu.borrow_mut();
2999 let completion_menu = match context_menu.as_ref() {
3000 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3001 Some(CodeContextMenu::CodeActions(_)) => {
3002 *context_menu = None;
3003 None
3004 }
3005 None => None,
3006 };
3007 let completion_position = completion_menu.map(|menu| menu.initial_position);
3008 drop(context_menu);
3009
3010 if effects.completions {
3011 if let Some(completion_position) = completion_position {
3012 let start_offset = selection_start.to_offset(buffer);
3013 let position_matches = start_offset == completion_position.to_offset(buffer);
3014 let continue_showing = if position_matches {
3015 if self.snippet_stack.is_empty() {
3016 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3017 } else {
3018 // Snippet choices can be shown even when the cursor is in whitespace.
3019 // Dismissing the menu with actions like backspace is handled by
3020 // invalidation regions.
3021 true
3022 }
3023 } else {
3024 false
3025 };
3026
3027 if continue_showing {
3028 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3029 } else {
3030 self.hide_context_menu(window, cx);
3031 }
3032 }
3033 }
3034
3035 hide_hover(self, cx);
3036
3037 if old_cursor_position.to_display_point(&display_map).row()
3038 != new_cursor_position.to_display_point(&display_map).row()
3039 {
3040 self.available_code_actions.take();
3041 }
3042 self.refresh_code_actions(window, cx);
3043 self.refresh_document_highlights(cx);
3044 self.refresh_selected_text_highlights(false, window, cx);
3045 refresh_matching_bracket_highlights(self, window, cx);
3046 self.update_visible_inline_completion(window, cx);
3047 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3048 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3049 self.inline_blame_popover.take();
3050 if self.git_blame_inline_enabled {
3051 self.start_inline_blame_timer(window, cx);
3052 }
3053 }
3054
3055 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3056 cx.emit(EditorEvent::SelectionsChanged { local });
3057
3058 let selections = &self.selections.disjoint;
3059 if selections.len() == 1 {
3060 cx.emit(SearchEvent::ActiveMatchChanged)
3061 }
3062 if local {
3063 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3064 let inmemory_selections = selections
3065 .iter()
3066 .map(|s| {
3067 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3068 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3069 })
3070 .collect();
3071 self.update_restoration_data(cx, |data| {
3072 data.selections = inmemory_selections;
3073 });
3074
3075 if WorkspaceSettings::get(None, cx).restore_on_startup
3076 != RestoreOnStartupBehavior::None
3077 {
3078 if let Some(workspace_id) =
3079 self.workspace.as_ref().and_then(|workspace| workspace.1)
3080 {
3081 let snapshot = self.buffer().read(cx).snapshot(cx);
3082 let selections = selections.clone();
3083 let background_executor = cx.background_executor().clone();
3084 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3085 self.serialize_selections = cx.background_spawn(async move {
3086 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3087 let db_selections = selections
3088 .iter()
3089 .map(|selection| {
3090 (
3091 selection.start.to_offset(&snapshot),
3092 selection.end.to_offset(&snapshot),
3093 )
3094 })
3095 .collect();
3096
3097 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3098 .await
3099 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3100 .log_err();
3101 });
3102 }
3103 }
3104 }
3105 }
3106
3107 cx.notify();
3108 }
3109
3110 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3111 use text::ToOffset as _;
3112 use text::ToPoint as _;
3113
3114 if self.mode.is_minimap()
3115 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3116 {
3117 return;
3118 }
3119
3120 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3121 return;
3122 };
3123
3124 let snapshot = singleton.read(cx).snapshot();
3125 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3126 let display_snapshot = display_map.snapshot(cx);
3127
3128 display_snapshot
3129 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3130 .map(|fold| {
3131 fold.range.start.text_anchor.to_point(&snapshot)
3132 ..fold.range.end.text_anchor.to_point(&snapshot)
3133 })
3134 .collect()
3135 });
3136 self.update_restoration_data(cx, |data| {
3137 data.folds = inmemory_folds;
3138 });
3139
3140 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3141 return;
3142 };
3143 let background_executor = cx.background_executor().clone();
3144 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3145 let db_folds = self.display_map.update(cx, |display_map, cx| {
3146 display_map
3147 .snapshot(cx)
3148 .folds_in_range(0..snapshot.len())
3149 .map(|fold| {
3150 (
3151 fold.range.start.text_anchor.to_offset(&snapshot),
3152 fold.range.end.text_anchor.to_offset(&snapshot),
3153 )
3154 })
3155 .collect()
3156 });
3157 self.serialize_folds = cx.background_spawn(async move {
3158 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3159 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3160 .await
3161 .with_context(|| {
3162 format!(
3163 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3164 )
3165 })
3166 .log_err();
3167 });
3168 }
3169
3170 pub fn sync_selections(
3171 &mut self,
3172 other: Entity<Editor>,
3173 cx: &mut Context<Self>,
3174 ) -> gpui::Subscription {
3175 let other_selections = other.read(cx).selections.disjoint.to_vec();
3176 self.selections.change_with(cx, |selections| {
3177 selections.select_anchors(other_selections);
3178 });
3179
3180 let other_subscription =
3181 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3182 EditorEvent::SelectionsChanged { local: true } => {
3183 let other_selections = other.read(cx).selections.disjoint.to_vec();
3184 if other_selections.is_empty() {
3185 return;
3186 }
3187 this.selections.change_with(cx, |selections| {
3188 selections.select_anchors(other_selections);
3189 });
3190 }
3191 _ => {}
3192 });
3193
3194 let this_subscription =
3195 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3196 EditorEvent::SelectionsChanged { local: true } => {
3197 let these_selections = this.selections.disjoint.to_vec();
3198 if these_selections.is_empty() {
3199 return;
3200 }
3201 other.update(cx, |other_editor, cx| {
3202 other_editor.selections.change_with(cx, |selections| {
3203 selections.select_anchors(these_selections);
3204 })
3205 });
3206 }
3207 _ => {}
3208 });
3209
3210 Subscription::join(other_subscription, this_subscription)
3211 }
3212
3213 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3214 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3215 /// effects of selection change occur at the end of the transaction.
3216 pub fn change_selections<R>(
3217 &mut self,
3218 effects: SelectionEffects,
3219 window: &mut Window,
3220 cx: &mut Context<Self>,
3221 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3222 ) -> R {
3223 if let Some(state) = &mut self.deferred_selection_effects_state {
3224 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3225 state.effects.completions = effects.completions;
3226 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3227 let (changed, result) = self.selections.change_with(cx, change);
3228 state.changed |= changed;
3229 return result;
3230 }
3231 let mut state = DeferredSelectionEffectsState {
3232 changed: false,
3233 effects,
3234 old_cursor_position: self.selections.newest_anchor().head(),
3235 history_entry: SelectionHistoryEntry {
3236 selections: self.selections.disjoint_anchors(),
3237 select_next_state: self.select_next_state.clone(),
3238 select_prev_state: self.select_prev_state.clone(),
3239 add_selections_state: self.add_selections_state.clone(),
3240 },
3241 };
3242 let (changed, result) = self.selections.change_with(cx, change);
3243 state.changed = state.changed || changed;
3244 if self.defer_selection_effects {
3245 self.deferred_selection_effects_state = Some(state);
3246 } else {
3247 self.apply_selection_effects(state, window, cx);
3248 }
3249 result
3250 }
3251
3252 /// Defers the effects of selection change, so that the effects of multiple calls to
3253 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3254 /// to selection history and the state of popovers based on selection position aren't
3255 /// erroneously updated.
3256 pub fn with_selection_effects_deferred<R>(
3257 &mut self,
3258 window: &mut Window,
3259 cx: &mut Context<Self>,
3260 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3261 ) -> R {
3262 let already_deferred = self.defer_selection_effects;
3263 self.defer_selection_effects = true;
3264 let result = update(self, window, cx);
3265 if !already_deferred {
3266 self.defer_selection_effects = false;
3267 if let Some(state) = self.deferred_selection_effects_state.take() {
3268 self.apply_selection_effects(state, window, cx);
3269 }
3270 }
3271 result
3272 }
3273
3274 fn apply_selection_effects(
3275 &mut self,
3276 state: DeferredSelectionEffectsState,
3277 window: &mut Window,
3278 cx: &mut Context<Self>,
3279 ) {
3280 if state.changed {
3281 self.selection_history.push(state.history_entry);
3282
3283 if let Some(autoscroll) = state.effects.scroll {
3284 self.request_autoscroll(autoscroll, cx);
3285 }
3286
3287 let old_cursor_position = &state.old_cursor_position;
3288
3289 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3290
3291 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3292 self.show_signature_help(&ShowSignatureHelp, window, cx);
3293 }
3294 }
3295 }
3296
3297 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3298 where
3299 I: IntoIterator<Item = (Range<S>, T)>,
3300 S: ToOffset,
3301 T: Into<Arc<str>>,
3302 {
3303 if self.read_only(cx) {
3304 return;
3305 }
3306
3307 self.buffer
3308 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3309 }
3310
3311 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3312 where
3313 I: IntoIterator<Item = (Range<S>, T)>,
3314 S: ToOffset,
3315 T: Into<Arc<str>>,
3316 {
3317 if self.read_only(cx) {
3318 return;
3319 }
3320
3321 self.buffer.update(cx, |buffer, cx| {
3322 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3323 });
3324 }
3325
3326 pub fn edit_with_block_indent<I, S, T>(
3327 &mut self,
3328 edits: I,
3329 original_indent_columns: Vec<Option<u32>>,
3330 cx: &mut Context<Self>,
3331 ) where
3332 I: IntoIterator<Item = (Range<S>, T)>,
3333 S: ToOffset,
3334 T: Into<Arc<str>>,
3335 {
3336 if self.read_only(cx) {
3337 return;
3338 }
3339
3340 self.buffer.update(cx, |buffer, cx| {
3341 buffer.edit(
3342 edits,
3343 Some(AutoindentMode::Block {
3344 original_indent_columns,
3345 }),
3346 cx,
3347 )
3348 });
3349 }
3350
3351 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3352 self.hide_context_menu(window, cx);
3353
3354 match phase {
3355 SelectPhase::Begin {
3356 position,
3357 add,
3358 click_count,
3359 } => self.begin_selection(position, add, click_count, window, cx),
3360 SelectPhase::BeginColumnar {
3361 position,
3362 goal_column,
3363 reset,
3364 mode,
3365 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3366 SelectPhase::Extend {
3367 position,
3368 click_count,
3369 } => self.extend_selection(position, click_count, window, cx),
3370 SelectPhase::Update {
3371 position,
3372 goal_column,
3373 scroll_delta,
3374 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3375 SelectPhase::End => self.end_selection(window, cx),
3376 }
3377 }
3378
3379 fn extend_selection(
3380 &mut self,
3381 position: DisplayPoint,
3382 click_count: usize,
3383 window: &mut Window,
3384 cx: &mut Context<Self>,
3385 ) {
3386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3387 let tail = self.selections.newest::<usize>(cx).tail();
3388 self.begin_selection(position, false, click_count, window, cx);
3389
3390 let position = position.to_offset(&display_map, Bias::Left);
3391 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3392
3393 let mut pending_selection = self
3394 .selections
3395 .pending_anchor()
3396 .expect("extend_selection not called with pending selection");
3397 if position >= tail {
3398 pending_selection.start = tail_anchor;
3399 } else {
3400 pending_selection.end = tail_anchor;
3401 pending_selection.reversed = true;
3402 }
3403
3404 let mut pending_mode = self.selections.pending_mode().unwrap();
3405 match &mut pending_mode {
3406 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3407 _ => {}
3408 }
3409
3410 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3411 SelectionEffects::scroll(Autoscroll::fit())
3412 } else {
3413 SelectionEffects::no_scroll()
3414 };
3415
3416 self.change_selections(effects, window, cx, |s| {
3417 s.set_pending(pending_selection, pending_mode)
3418 });
3419 }
3420
3421 fn begin_selection(
3422 &mut self,
3423 position: DisplayPoint,
3424 add: bool,
3425 click_count: usize,
3426 window: &mut Window,
3427 cx: &mut Context<Self>,
3428 ) {
3429 if !self.focus_handle.is_focused(window) {
3430 self.last_focused_descendant = None;
3431 window.focus(&self.focus_handle);
3432 }
3433
3434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3435 let buffer = &display_map.buffer_snapshot;
3436 let position = display_map.clip_point(position, Bias::Left);
3437
3438 let start;
3439 let end;
3440 let mode;
3441 let mut auto_scroll;
3442 match click_count {
3443 1 => {
3444 start = buffer.anchor_before(position.to_point(&display_map));
3445 end = start;
3446 mode = SelectMode::Character;
3447 auto_scroll = true;
3448 }
3449 2 => {
3450 let position = display_map
3451 .clip_point(position, Bias::Left)
3452 .to_offset(&display_map, Bias::Left);
3453 let (range, _) = buffer.surrounding_word(position, false);
3454 start = buffer.anchor_before(range.start);
3455 end = buffer.anchor_before(range.end);
3456 mode = SelectMode::Word(start..end);
3457 auto_scroll = true;
3458 }
3459 3 => {
3460 let position = display_map
3461 .clip_point(position, Bias::Left)
3462 .to_point(&display_map);
3463 let line_start = display_map.prev_line_boundary(position).0;
3464 let next_line_start = buffer.clip_point(
3465 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3466 Bias::Left,
3467 );
3468 start = buffer.anchor_before(line_start);
3469 end = buffer.anchor_before(next_line_start);
3470 mode = SelectMode::Line(start..end);
3471 auto_scroll = true;
3472 }
3473 _ => {
3474 start = buffer.anchor_before(0);
3475 end = buffer.anchor_before(buffer.len());
3476 mode = SelectMode::All;
3477 auto_scroll = false;
3478 }
3479 }
3480 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3481
3482 let point_to_delete: Option<usize> = {
3483 let selected_points: Vec<Selection<Point>> =
3484 self.selections.disjoint_in_range(start..end, cx);
3485
3486 if !add || click_count > 1 {
3487 None
3488 } else if !selected_points.is_empty() {
3489 Some(selected_points[0].id)
3490 } else {
3491 let clicked_point_already_selected =
3492 self.selections.disjoint.iter().find(|selection| {
3493 selection.start.to_point(buffer) == start.to_point(buffer)
3494 || selection.end.to_point(buffer) == end.to_point(buffer)
3495 });
3496
3497 clicked_point_already_selected.map(|selection| selection.id)
3498 }
3499 };
3500
3501 let selections_count = self.selections.count();
3502 let effects = if auto_scroll {
3503 SelectionEffects::default()
3504 } else {
3505 SelectionEffects::no_scroll()
3506 };
3507
3508 self.change_selections(effects, window, cx, |s| {
3509 if let Some(point_to_delete) = point_to_delete {
3510 s.delete(point_to_delete);
3511
3512 if selections_count == 1 {
3513 s.set_pending_anchor_range(start..end, mode);
3514 }
3515 } else {
3516 if !add {
3517 s.clear_disjoint();
3518 }
3519
3520 s.set_pending_anchor_range(start..end, mode);
3521 }
3522 });
3523 }
3524
3525 fn begin_columnar_selection(
3526 &mut self,
3527 position: DisplayPoint,
3528 goal_column: u32,
3529 reset: bool,
3530 mode: ColumnarMode,
3531 window: &mut Window,
3532 cx: &mut Context<Self>,
3533 ) {
3534 if !self.focus_handle.is_focused(window) {
3535 self.last_focused_descendant = None;
3536 window.focus(&self.focus_handle);
3537 }
3538
3539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3540
3541 if reset {
3542 let pointer_position = display_map
3543 .buffer_snapshot
3544 .anchor_before(position.to_point(&display_map));
3545
3546 self.change_selections(
3547 SelectionEffects::scroll(Autoscroll::newest()),
3548 window,
3549 cx,
3550 |s| {
3551 s.clear_disjoint();
3552 s.set_pending_anchor_range(
3553 pointer_position..pointer_position,
3554 SelectMode::Character,
3555 );
3556 },
3557 );
3558 };
3559
3560 let tail = self.selections.newest::<Point>(cx).tail();
3561 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3562 self.columnar_selection_state = match mode {
3563 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3564 selection_tail: selection_anchor,
3565 display_point: if reset {
3566 if position.column() != goal_column {
3567 Some(DisplayPoint::new(position.row(), goal_column))
3568 } else {
3569 None
3570 }
3571 } else {
3572 None
3573 },
3574 }),
3575 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3576 selection_tail: selection_anchor,
3577 }),
3578 };
3579
3580 if !reset {
3581 self.select_columns(position, goal_column, &display_map, window, cx);
3582 }
3583 }
3584
3585 fn update_selection(
3586 &mut self,
3587 position: DisplayPoint,
3588 goal_column: u32,
3589 scroll_delta: gpui::Point<f32>,
3590 window: &mut Window,
3591 cx: &mut Context<Self>,
3592 ) {
3593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3594
3595 if self.columnar_selection_state.is_some() {
3596 self.select_columns(position, goal_column, &display_map, window, cx);
3597 } else if let Some(mut pending) = self.selections.pending_anchor() {
3598 let buffer = &display_map.buffer_snapshot;
3599 let head;
3600 let tail;
3601 let mode = self.selections.pending_mode().unwrap();
3602 match &mode {
3603 SelectMode::Character => {
3604 head = position.to_point(&display_map);
3605 tail = pending.tail().to_point(buffer);
3606 }
3607 SelectMode::Word(original_range) => {
3608 let offset = display_map
3609 .clip_point(position, Bias::Left)
3610 .to_offset(&display_map, Bias::Left);
3611 let original_range = original_range.to_offset(buffer);
3612
3613 let head_offset = if buffer.is_inside_word(offset, false)
3614 || original_range.contains(&offset)
3615 {
3616 let (word_range, _) = buffer.surrounding_word(offset, false);
3617 if word_range.start < original_range.start {
3618 word_range.start
3619 } else {
3620 word_range.end
3621 }
3622 } else {
3623 offset
3624 };
3625
3626 head = head_offset.to_point(buffer);
3627 if head_offset <= original_range.start {
3628 tail = original_range.end.to_point(buffer);
3629 } else {
3630 tail = original_range.start.to_point(buffer);
3631 }
3632 }
3633 SelectMode::Line(original_range) => {
3634 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3635
3636 let position = display_map
3637 .clip_point(position, Bias::Left)
3638 .to_point(&display_map);
3639 let line_start = display_map.prev_line_boundary(position).0;
3640 let next_line_start = buffer.clip_point(
3641 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3642 Bias::Left,
3643 );
3644
3645 if line_start < original_range.start {
3646 head = line_start
3647 } else {
3648 head = next_line_start
3649 }
3650
3651 if head <= original_range.start {
3652 tail = original_range.end;
3653 } else {
3654 tail = original_range.start;
3655 }
3656 }
3657 SelectMode::All => {
3658 return;
3659 }
3660 };
3661
3662 if head < tail {
3663 pending.start = buffer.anchor_before(head);
3664 pending.end = buffer.anchor_before(tail);
3665 pending.reversed = true;
3666 } else {
3667 pending.start = buffer.anchor_before(tail);
3668 pending.end = buffer.anchor_before(head);
3669 pending.reversed = false;
3670 }
3671
3672 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3673 s.set_pending(pending, mode);
3674 });
3675 } else {
3676 log::error!("update_selection dispatched with no pending selection");
3677 return;
3678 }
3679
3680 self.apply_scroll_delta(scroll_delta, window, cx);
3681 cx.notify();
3682 }
3683
3684 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3685 self.columnar_selection_state.take();
3686 if self.selections.pending_anchor().is_some() {
3687 let selections = self.selections.all::<usize>(cx);
3688 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3689 s.select(selections);
3690 s.clear_pending();
3691 });
3692 }
3693 }
3694
3695 fn select_columns(
3696 &mut self,
3697 head: DisplayPoint,
3698 goal_column: u32,
3699 display_map: &DisplaySnapshot,
3700 window: &mut Window,
3701 cx: &mut Context<Self>,
3702 ) {
3703 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3704 return;
3705 };
3706
3707 let tail = match columnar_state {
3708 ColumnarSelectionState::FromMouse {
3709 selection_tail,
3710 display_point,
3711 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3712 ColumnarSelectionState::FromSelection { selection_tail } => {
3713 selection_tail.to_display_point(&display_map)
3714 }
3715 };
3716
3717 let start_row = cmp::min(tail.row(), head.row());
3718 let end_row = cmp::max(tail.row(), head.row());
3719 let start_column = cmp::min(tail.column(), goal_column);
3720 let end_column = cmp::max(tail.column(), goal_column);
3721 let reversed = start_column < tail.column();
3722
3723 let selection_ranges = (start_row.0..=end_row.0)
3724 .map(DisplayRow)
3725 .filter_map(|row| {
3726 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3727 || start_column <= display_map.line_len(row))
3728 && !display_map.is_block_line(row)
3729 {
3730 let start = display_map
3731 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3732 .to_point(display_map);
3733 let end = display_map
3734 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3735 .to_point(display_map);
3736 if reversed {
3737 Some(end..start)
3738 } else {
3739 Some(start..end)
3740 }
3741 } else {
3742 None
3743 }
3744 })
3745 .collect::<Vec<_>>();
3746
3747 let ranges = match columnar_state {
3748 ColumnarSelectionState::FromMouse { .. } => {
3749 let mut non_empty_ranges = selection_ranges
3750 .iter()
3751 .filter(|selection_range| selection_range.start != selection_range.end)
3752 .peekable();
3753 if non_empty_ranges.peek().is_some() {
3754 non_empty_ranges.cloned().collect()
3755 } else {
3756 selection_ranges
3757 }
3758 }
3759 _ => selection_ranges,
3760 };
3761
3762 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3763 s.select_ranges(ranges);
3764 });
3765 cx.notify();
3766 }
3767
3768 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3769 self.selections
3770 .all_adjusted(cx)
3771 .iter()
3772 .any(|selection| !selection.is_empty())
3773 }
3774
3775 pub fn has_pending_nonempty_selection(&self) -> bool {
3776 let pending_nonempty_selection = match self.selections.pending_anchor() {
3777 Some(Selection { start, end, .. }) => start != end,
3778 None => false,
3779 };
3780
3781 pending_nonempty_selection
3782 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3783 }
3784
3785 pub fn has_pending_selection(&self) -> bool {
3786 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3787 }
3788
3789 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3790 self.selection_mark_mode = false;
3791 self.selection_drag_state = SelectionDragState::None;
3792
3793 if self.clear_expanded_diff_hunks(cx) {
3794 cx.notify();
3795 return;
3796 }
3797 if self.dismiss_menus_and_popups(true, window, cx) {
3798 return;
3799 }
3800
3801 if self.mode.is_full()
3802 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3803 {
3804 return;
3805 }
3806
3807 cx.propagate();
3808 }
3809
3810 pub fn dismiss_menus_and_popups(
3811 &mut self,
3812 is_user_requested: bool,
3813 window: &mut Window,
3814 cx: &mut Context<Self>,
3815 ) -> bool {
3816 if self.take_rename(false, window, cx).is_some() {
3817 return true;
3818 }
3819
3820 if hide_hover(self, cx) {
3821 return true;
3822 }
3823
3824 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3825 return true;
3826 }
3827
3828 if self.hide_context_menu(window, cx).is_some() {
3829 return true;
3830 }
3831
3832 if self.mouse_context_menu.take().is_some() {
3833 return true;
3834 }
3835
3836 if is_user_requested && self.discard_inline_completion(true, cx) {
3837 return true;
3838 }
3839
3840 if self.snippet_stack.pop().is_some() {
3841 return true;
3842 }
3843
3844 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3845 self.dismiss_diagnostics(cx);
3846 return true;
3847 }
3848
3849 false
3850 }
3851
3852 fn linked_editing_ranges_for(
3853 &self,
3854 selection: Range<text::Anchor>,
3855 cx: &App,
3856 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3857 if self.linked_edit_ranges.is_empty() {
3858 return None;
3859 }
3860 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3861 selection.end.buffer_id.and_then(|end_buffer_id| {
3862 if selection.start.buffer_id != Some(end_buffer_id) {
3863 return None;
3864 }
3865 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3866 let snapshot = buffer.read(cx).snapshot();
3867 self.linked_edit_ranges
3868 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3869 .map(|ranges| (ranges, snapshot, buffer))
3870 })?;
3871 use text::ToOffset as TO;
3872 // find offset from the start of current range to current cursor position
3873 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3874
3875 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3876 let start_difference = start_offset - start_byte_offset;
3877 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3878 let end_difference = end_offset - start_byte_offset;
3879 // Current range has associated linked ranges.
3880 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3881 for range in linked_ranges.iter() {
3882 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3883 let end_offset = start_offset + end_difference;
3884 let start_offset = start_offset + start_difference;
3885 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3886 continue;
3887 }
3888 if self.selections.disjoint_anchor_ranges().any(|s| {
3889 if s.start.buffer_id != selection.start.buffer_id
3890 || s.end.buffer_id != selection.end.buffer_id
3891 {
3892 return false;
3893 }
3894 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3895 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3896 }) {
3897 continue;
3898 }
3899 let start = buffer_snapshot.anchor_after(start_offset);
3900 let end = buffer_snapshot.anchor_after(end_offset);
3901 linked_edits
3902 .entry(buffer.clone())
3903 .or_default()
3904 .push(start..end);
3905 }
3906 Some(linked_edits)
3907 }
3908
3909 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3910 let text: Arc<str> = text.into();
3911
3912 if self.read_only(cx) {
3913 return;
3914 }
3915
3916 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3917
3918 let selections = self.selections.all_adjusted(cx);
3919 let mut bracket_inserted = false;
3920 let mut edits = Vec::new();
3921 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3922 let mut new_selections = Vec::with_capacity(selections.len());
3923 let mut new_autoclose_regions = Vec::new();
3924 let snapshot = self.buffer.read(cx).read(cx);
3925 let mut clear_linked_edit_ranges = false;
3926
3927 for (selection, autoclose_region) in
3928 self.selections_with_autoclose_regions(selections, &snapshot)
3929 {
3930 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3931 // Determine if the inserted text matches the opening or closing
3932 // bracket of any of this language's bracket pairs.
3933 let mut bracket_pair = None;
3934 let mut is_bracket_pair_start = false;
3935 let mut is_bracket_pair_end = false;
3936 if !text.is_empty() {
3937 let mut bracket_pair_matching_end = None;
3938 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3939 // and they are removing the character that triggered IME popup.
3940 for (pair, enabled) in scope.brackets() {
3941 if !pair.close && !pair.surround {
3942 continue;
3943 }
3944
3945 if enabled && pair.start.ends_with(text.as_ref()) {
3946 let prefix_len = pair.start.len() - text.len();
3947 let preceding_text_matches_prefix = prefix_len == 0
3948 || (selection.start.column >= (prefix_len as u32)
3949 && snapshot.contains_str_at(
3950 Point::new(
3951 selection.start.row,
3952 selection.start.column - (prefix_len as u32),
3953 ),
3954 &pair.start[..prefix_len],
3955 ));
3956 if preceding_text_matches_prefix {
3957 bracket_pair = Some(pair.clone());
3958 is_bracket_pair_start = true;
3959 break;
3960 }
3961 }
3962 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3963 {
3964 // take first bracket pair matching end, but don't break in case a later bracket
3965 // pair matches start
3966 bracket_pair_matching_end = Some(pair.clone());
3967 }
3968 }
3969 if let Some(end) = bracket_pair_matching_end
3970 && bracket_pair.is_none()
3971 {
3972 bracket_pair = Some(end);
3973 is_bracket_pair_end = true;
3974 }
3975 }
3976
3977 if let Some(bracket_pair) = bracket_pair {
3978 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3979 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3980 let auto_surround =
3981 self.use_auto_surround && snapshot_settings.use_auto_surround;
3982 if selection.is_empty() {
3983 if is_bracket_pair_start {
3984 // If the inserted text is a suffix of an opening bracket and the
3985 // selection is preceded by the rest of the opening bracket, then
3986 // insert the closing bracket.
3987 let following_text_allows_autoclose = snapshot
3988 .chars_at(selection.start)
3989 .next()
3990 .map_or(true, |c| scope.should_autoclose_before(c));
3991
3992 let preceding_text_allows_autoclose = selection.start.column == 0
3993 || snapshot.reversed_chars_at(selection.start).next().map_or(
3994 true,
3995 |c| {
3996 bracket_pair.start != bracket_pair.end
3997 || !snapshot
3998 .char_classifier_at(selection.start)
3999 .is_word(c)
4000 },
4001 );
4002
4003 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4004 && bracket_pair.start.len() == 1
4005 {
4006 let target = bracket_pair.start.chars().next().unwrap();
4007 let current_line_count = snapshot
4008 .reversed_chars_at(selection.start)
4009 .take_while(|&c| c != '\n')
4010 .filter(|&c| c == target)
4011 .count();
4012 current_line_count % 2 == 1
4013 } else {
4014 false
4015 };
4016
4017 if autoclose
4018 && bracket_pair.close
4019 && following_text_allows_autoclose
4020 && preceding_text_allows_autoclose
4021 && !is_closing_quote
4022 {
4023 let anchor = snapshot.anchor_before(selection.end);
4024 new_selections.push((selection.map(|_| anchor), text.len()));
4025 new_autoclose_regions.push((
4026 anchor,
4027 text.len(),
4028 selection.id,
4029 bracket_pair.clone(),
4030 ));
4031 edits.push((
4032 selection.range(),
4033 format!("{}{}", text, bracket_pair.end).into(),
4034 ));
4035 bracket_inserted = true;
4036 continue;
4037 }
4038 }
4039
4040 if let Some(region) = autoclose_region {
4041 // If the selection is followed by an auto-inserted closing bracket,
4042 // then don't insert that closing bracket again; just move the selection
4043 // past the closing bracket.
4044 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4045 && text.as_ref() == region.pair.end.as_str()
4046 && snapshot.contains_str_at(region.range.end, text.as_ref());
4047 if should_skip {
4048 let anchor = snapshot.anchor_after(selection.end);
4049 new_selections
4050 .push((selection.map(|_| anchor), region.pair.end.len()));
4051 continue;
4052 }
4053 }
4054
4055 let always_treat_brackets_as_autoclosed = snapshot
4056 .language_settings_at(selection.start, cx)
4057 .always_treat_brackets_as_autoclosed;
4058 if always_treat_brackets_as_autoclosed
4059 && is_bracket_pair_end
4060 && snapshot.contains_str_at(selection.end, text.as_ref())
4061 {
4062 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4063 // and the inserted text is a closing bracket and the selection is followed
4064 // by the closing bracket then move the selection past the closing bracket.
4065 let anchor = snapshot.anchor_after(selection.end);
4066 new_selections.push((selection.map(|_| anchor), text.len()));
4067 continue;
4068 }
4069 }
4070 // If an opening bracket is 1 character long and is typed while
4071 // text is selected, then surround that text with the bracket pair.
4072 else if auto_surround
4073 && bracket_pair.surround
4074 && is_bracket_pair_start
4075 && bracket_pair.start.chars().count() == 1
4076 {
4077 edits.push((selection.start..selection.start, text.clone()));
4078 edits.push((
4079 selection.end..selection.end,
4080 bracket_pair.end.as_str().into(),
4081 ));
4082 bracket_inserted = true;
4083 new_selections.push((
4084 Selection {
4085 id: selection.id,
4086 start: snapshot.anchor_after(selection.start),
4087 end: snapshot.anchor_before(selection.end),
4088 reversed: selection.reversed,
4089 goal: selection.goal,
4090 },
4091 0,
4092 ));
4093 continue;
4094 }
4095 }
4096 }
4097
4098 if self.auto_replace_emoji_shortcode
4099 && selection.is_empty()
4100 && text.as_ref().ends_with(':')
4101 {
4102 if let Some(possible_emoji_short_code) =
4103 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4104 {
4105 if !possible_emoji_short_code.is_empty() {
4106 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4107 let emoji_shortcode_start = Point::new(
4108 selection.start.row,
4109 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4110 );
4111
4112 // Remove shortcode from buffer
4113 edits.push((
4114 emoji_shortcode_start..selection.start,
4115 "".to_string().into(),
4116 ));
4117 new_selections.push((
4118 Selection {
4119 id: selection.id,
4120 start: snapshot.anchor_after(emoji_shortcode_start),
4121 end: snapshot.anchor_before(selection.start),
4122 reversed: selection.reversed,
4123 goal: selection.goal,
4124 },
4125 0,
4126 ));
4127
4128 // Insert emoji
4129 let selection_start_anchor = snapshot.anchor_after(selection.start);
4130 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4131 edits.push((selection.start..selection.end, emoji.to_string().into()));
4132
4133 continue;
4134 }
4135 }
4136 }
4137 }
4138
4139 // If not handling any auto-close operation, then just replace the selected
4140 // text with the given input and move the selection to the end of the
4141 // newly inserted text.
4142 let anchor = snapshot.anchor_after(selection.end);
4143 if !self.linked_edit_ranges.is_empty() {
4144 let start_anchor = snapshot.anchor_before(selection.start);
4145
4146 let is_word_char = text.chars().next().map_or(true, |char| {
4147 let classifier = snapshot
4148 .char_classifier_at(start_anchor.to_offset(&snapshot))
4149 .ignore_punctuation(true);
4150 classifier.is_word(char)
4151 });
4152
4153 if is_word_char {
4154 if let Some(ranges) = self
4155 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4156 {
4157 for (buffer, edits) in ranges {
4158 linked_edits
4159 .entry(buffer.clone())
4160 .or_default()
4161 .extend(edits.into_iter().map(|range| (range, text.clone())));
4162 }
4163 }
4164 } else {
4165 clear_linked_edit_ranges = true;
4166 }
4167 }
4168
4169 new_selections.push((selection.map(|_| anchor), 0));
4170 edits.push((selection.start..selection.end, text.clone()));
4171 }
4172
4173 drop(snapshot);
4174
4175 self.transact(window, cx, |this, window, cx| {
4176 if clear_linked_edit_ranges {
4177 this.linked_edit_ranges.clear();
4178 }
4179 let initial_buffer_versions =
4180 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4181
4182 this.buffer.update(cx, |buffer, cx| {
4183 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4184 });
4185 for (buffer, edits) in linked_edits {
4186 buffer.update(cx, |buffer, cx| {
4187 let snapshot = buffer.snapshot();
4188 let edits = edits
4189 .into_iter()
4190 .map(|(range, text)| {
4191 use text::ToPoint as TP;
4192 let end_point = TP::to_point(&range.end, &snapshot);
4193 let start_point = TP::to_point(&range.start, &snapshot);
4194 (start_point..end_point, text)
4195 })
4196 .sorted_by_key(|(range, _)| range.start);
4197 buffer.edit(edits, None, cx);
4198 })
4199 }
4200 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4201 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4202 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4203 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4204 .zip(new_selection_deltas)
4205 .map(|(selection, delta)| Selection {
4206 id: selection.id,
4207 start: selection.start + delta,
4208 end: selection.end + delta,
4209 reversed: selection.reversed,
4210 goal: SelectionGoal::None,
4211 })
4212 .collect::<Vec<_>>();
4213
4214 let mut i = 0;
4215 for (position, delta, selection_id, pair) in new_autoclose_regions {
4216 let position = position.to_offset(&map.buffer_snapshot) + delta;
4217 let start = map.buffer_snapshot.anchor_before(position);
4218 let end = map.buffer_snapshot.anchor_after(position);
4219 while let Some(existing_state) = this.autoclose_regions.get(i) {
4220 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4221 Ordering::Less => i += 1,
4222 Ordering::Greater => break,
4223 Ordering::Equal => {
4224 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4225 Ordering::Less => i += 1,
4226 Ordering::Equal => break,
4227 Ordering::Greater => break,
4228 }
4229 }
4230 }
4231 }
4232 this.autoclose_regions.insert(
4233 i,
4234 AutocloseRegion {
4235 selection_id,
4236 range: start..end,
4237 pair,
4238 },
4239 );
4240 }
4241
4242 let had_active_inline_completion = this.has_active_inline_completion();
4243 this.change_selections(
4244 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4245 window,
4246 cx,
4247 |s| s.select(new_selections),
4248 );
4249
4250 if !bracket_inserted {
4251 if let Some(on_type_format_task) =
4252 this.trigger_on_type_formatting(text.to_string(), window, cx)
4253 {
4254 on_type_format_task.detach_and_log_err(cx);
4255 }
4256 }
4257
4258 let editor_settings = EditorSettings::get_global(cx);
4259 if bracket_inserted
4260 && (editor_settings.auto_signature_help
4261 || editor_settings.show_signature_help_after_edits)
4262 {
4263 this.show_signature_help(&ShowSignatureHelp, window, cx);
4264 }
4265
4266 let trigger_in_words =
4267 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4268 if this.hard_wrap.is_some() {
4269 let latest: Range<Point> = this.selections.newest(cx).range();
4270 if latest.is_empty()
4271 && this
4272 .buffer()
4273 .read(cx)
4274 .snapshot(cx)
4275 .line_len(MultiBufferRow(latest.start.row))
4276 == latest.start.column
4277 {
4278 this.rewrap_impl(
4279 RewrapOptions {
4280 override_language_settings: true,
4281 preserve_existing_whitespace: true,
4282 },
4283 cx,
4284 )
4285 }
4286 }
4287 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4288 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4289 this.refresh_inline_completion(true, false, window, cx);
4290 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4291 });
4292 }
4293
4294 fn find_possible_emoji_shortcode_at_position(
4295 snapshot: &MultiBufferSnapshot,
4296 position: Point,
4297 ) -> Option<String> {
4298 let mut chars = Vec::new();
4299 let mut found_colon = false;
4300 for char in snapshot.reversed_chars_at(position).take(100) {
4301 // Found a possible emoji shortcode in the middle of the buffer
4302 if found_colon {
4303 if char.is_whitespace() {
4304 chars.reverse();
4305 return Some(chars.iter().collect());
4306 }
4307 // If the previous character is not a whitespace, we are in the middle of a word
4308 // and we only want to complete the shortcode if the word is made up of other emojis
4309 let mut containing_word = String::new();
4310 for ch in snapshot
4311 .reversed_chars_at(position)
4312 .skip(chars.len() + 1)
4313 .take(100)
4314 {
4315 if ch.is_whitespace() {
4316 break;
4317 }
4318 containing_word.push(ch);
4319 }
4320 let containing_word = containing_word.chars().rev().collect::<String>();
4321 if util::word_consists_of_emojis(containing_word.as_str()) {
4322 chars.reverse();
4323 return Some(chars.iter().collect());
4324 }
4325 }
4326
4327 if char.is_whitespace() || !char.is_ascii() {
4328 return None;
4329 }
4330 if char == ':' {
4331 found_colon = true;
4332 } else {
4333 chars.push(char);
4334 }
4335 }
4336 // Found a possible emoji shortcode at the beginning of the buffer
4337 chars.reverse();
4338 Some(chars.iter().collect())
4339 }
4340
4341 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4342 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4343 self.transact(window, cx, |this, window, cx| {
4344 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4345 let selections = this.selections.all::<usize>(cx);
4346 let multi_buffer = this.buffer.read(cx);
4347 let buffer = multi_buffer.snapshot(cx);
4348 selections
4349 .iter()
4350 .map(|selection| {
4351 let start_point = selection.start.to_point(&buffer);
4352 let mut existing_indent =
4353 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4354 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4355 let start = selection.start;
4356 let end = selection.end;
4357 let selection_is_empty = start == end;
4358 let language_scope = buffer.language_scope_at(start);
4359 let (
4360 comment_delimiter,
4361 doc_delimiter,
4362 insert_extra_newline,
4363 indent_on_newline,
4364 indent_on_extra_newline,
4365 ) = if let Some(language) = &language_scope {
4366 let mut insert_extra_newline =
4367 insert_extra_newline_brackets(&buffer, start..end, language)
4368 || insert_extra_newline_tree_sitter(&buffer, start..end);
4369
4370 // Comment extension on newline is allowed only for cursor selections
4371 let comment_delimiter = maybe!({
4372 if !selection_is_empty {
4373 return None;
4374 }
4375
4376 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4377 return None;
4378 }
4379
4380 let delimiters = language.line_comment_prefixes();
4381 let max_len_of_delimiter =
4382 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4383 let (snapshot, range) =
4384 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4385
4386 let num_of_whitespaces = snapshot
4387 .chars_for_range(range.clone())
4388 .take_while(|c| c.is_whitespace())
4389 .count();
4390 let comment_candidate = snapshot
4391 .chars_for_range(range.clone())
4392 .skip(num_of_whitespaces)
4393 .take(max_len_of_delimiter)
4394 .collect::<String>();
4395 let (delimiter, trimmed_len) = delimiters
4396 .iter()
4397 .filter_map(|delimiter| {
4398 let prefix = delimiter.trim_end();
4399 if comment_candidate.starts_with(prefix) {
4400 Some((delimiter, prefix.len()))
4401 } else {
4402 None
4403 }
4404 })
4405 .max_by_key(|(_, len)| *len)?;
4406
4407 if let Some(BlockCommentConfig {
4408 start: block_start, ..
4409 }) = language.block_comment()
4410 {
4411 let block_start_trimmed = block_start.trim_end();
4412 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4413 let line_content = snapshot
4414 .chars_for_range(range)
4415 .skip(num_of_whitespaces)
4416 .take(block_start_trimmed.len())
4417 .collect::<String>();
4418
4419 if line_content.starts_with(block_start_trimmed) {
4420 return None;
4421 }
4422 }
4423 }
4424
4425 let cursor_is_placed_after_comment_marker =
4426 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4427 if cursor_is_placed_after_comment_marker {
4428 Some(delimiter.clone())
4429 } else {
4430 None
4431 }
4432 });
4433
4434 let mut indent_on_newline = IndentSize::spaces(0);
4435 let mut indent_on_extra_newline = IndentSize::spaces(0);
4436
4437 let doc_delimiter = maybe!({
4438 if !selection_is_empty {
4439 return None;
4440 }
4441
4442 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4443 return None;
4444 }
4445
4446 let BlockCommentConfig {
4447 start: start_tag,
4448 end: end_tag,
4449 prefix: delimiter,
4450 tab_size: len,
4451 } = language.documentation_comment()?;
4452 let is_within_block_comment = buffer
4453 .language_scope_at(start_point)
4454 .is_some_and(|scope| scope.override_name() == Some("comment"));
4455 if !is_within_block_comment {
4456 return None;
4457 }
4458
4459 let (snapshot, range) =
4460 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4461
4462 let num_of_whitespaces = snapshot
4463 .chars_for_range(range.clone())
4464 .take_while(|c| c.is_whitespace())
4465 .count();
4466
4467 // 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.
4468 let column = start_point.column;
4469 let cursor_is_after_start_tag = {
4470 let start_tag_len = start_tag.len();
4471 let start_tag_line = snapshot
4472 .chars_for_range(range.clone())
4473 .skip(num_of_whitespaces)
4474 .take(start_tag_len)
4475 .collect::<String>();
4476 if start_tag_line.starts_with(start_tag.as_ref()) {
4477 num_of_whitespaces + start_tag_len <= column as usize
4478 } else {
4479 false
4480 }
4481 };
4482
4483 let cursor_is_after_delimiter = {
4484 let delimiter_trim = delimiter.trim_end();
4485 let delimiter_line = snapshot
4486 .chars_for_range(range.clone())
4487 .skip(num_of_whitespaces)
4488 .take(delimiter_trim.len())
4489 .collect::<String>();
4490 if delimiter_line.starts_with(delimiter_trim) {
4491 num_of_whitespaces + delimiter_trim.len() <= column as usize
4492 } else {
4493 false
4494 }
4495 };
4496
4497 let cursor_is_before_end_tag_if_exists = {
4498 let mut char_position = 0u32;
4499 let mut end_tag_offset = None;
4500
4501 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4502 if let Some(byte_pos) = chunk.find(&**end_tag) {
4503 let chars_before_match =
4504 chunk[..byte_pos].chars().count() as u32;
4505 end_tag_offset =
4506 Some(char_position + chars_before_match);
4507 break 'outer;
4508 }
4509 char_position += chunk.chars().count() as u32;
4510 }
4511
4512 if let Some(end_tag_offset) = end_tag_offset {
4513 let cursor_is_before_end_tag = column <= end_tag_offset;
4514 if cursor_is_after_start_tag {
4515 if cursor_is_before_end_tag {
4516 insert_extra_newline = true;
4517 }
4518 let cursor_is_at_start_of_end_tag =
4519 column == end_tag_offset;
4520 if cursor_is_at_start_of_end_tag {
4521 indent_on_extra_newline.len = *len;
4522 }
4523 }
4524 cursor_is_before_end_tag
4525 } else {
4526 true
4527 }
4528 };
4529
4530 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4531 && cursor_is_before_end_tag_if_exists
4532 {
4533 if cursor_is_after_start_tag {
4534 indent_on_newline.len = *len;
4535 }
4536 Some(delimiter.clone())
4537 } else {
4538 None
4539 }
4540 });
4541
4542 (
4543 comment_delimiter,
4544 doc_delimiter,
4545 insert_extra_newline,
4546 indent_on_newline,
4547 indent_on_extra_newline,
4548 )
4549 } else {
4550 (
4551 None,
4552 None,
4553 false,
4554 IndentSize::default(),
4555 IndentSize::default(),
4556 )
4557 };
4558
4559 let prevent_auto_indent = doc_delimiter.is_some();
4560 let delimiter = comment_delimiter.or(doc_delimiter);
4561
4562 let capacity_for_delimiter =
4563 delimiter.as_deref().map(str::len).unwrap_or_default();
4564 let mut new_text = String::with_capacity(
4565 1 + capacity_for_delimiter
4566 + existing_indent.len as usize
4567 + indent_on_newline.len as usize
4568 + indent_on_extra_newline.len as usize,
4569 );
4570 new_text.push('\n');
4571 new_text.extend(existing_indent.chars());
4572 new_text.extend(indent_on_newline.chars());
4573
4574 if let Some(delimiter) = &delimiter {
4575 new_text.push_str(delimiter);
4576 }
4577
4578 if insert_extra_newline {
4579 new_text.push('\n');
4580 new_text.extend(existing_indent.chars());
4581 new_text.extend(indent_on_extra_newline.chars());
4582 }
4583
4584 let anchor = buffer.anchor_after(end);
4585 let new_selection = selection.map(|_| anchor);
4586 (
4587 ((start..end, new_text), prevent_auto_indent),
4588 (insert_extra_newline, new_selection),
4589 )
4590 })
4591 .unzip()
4592 };
4593
4594 let mut auto_indent_edits = Vec::new();
4595 let mut edits = Vec::new();
4596 for (edit, prevent_auto_indent) in edits_with_flags {
4597 if prevent_auto_indent {
4598 edits.push(edit);
4599 } else {
4600 auto_indent_edits.push(edit);
4601 }
4602 }
4603 if !edits.is_empty() {
4604 this.edit(edits, cx);
4605 }
4606 if !auto_indent_edits.is_empty() {
4607 this.edit_with_autoindent(auto_indent_edits, cx);
4608 }
4609
4610 let buffer = this.buffer.read(cx).snapshot(cx);
4611 let new_selections = selection_info
4612 .into_iter()
4613 .map(|(extra_newline_inserted, new_selection)| {
4614 let mut cursor = new_selection.end.to_point(&buffer);
4615 if extra_newline_inserted {
4616 cursor.row -= 1;
4617 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4618 }
4619 new_selection.map(|_| cursor)
4620 })
4621 .collect();
4622
4623 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4624 this.refresh_inline_completion(true, false, window, cx);
4625 });
4626 }
4627
4628 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4630
4631 let buffer = self.buffer.read(cx);
4632 let snapshot = buffer.snapshot(cx);
4633
4634 let mut edits = Vec::new();
4635 let mut rows = Vec::new();
4636
4637 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4638 let cursor = selection.head();
4639 let row = cursor.row;
4640
4641 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4642
4643 let newline = "\n".to_string();
4644 edits.push((start_of_line..start_of_line, newline));
4645
4646 rows.push(row + rows_inserted as u32);
4647 }
4648
4649 self.transact(window, cx, |editor, window, cx| {
4650 editor.edit(edits, cx);
4651
4652 editor.change_selections(Default::default(), window, cx, |s| {
4653 let mut index = 0;
4654 s.move_cursors_with(|map, _, _| {
4655 let row = rows[index];
4656 index += 1;
4657
4658 let point = Point::new(row, 0);
4659 let boundary = map.next_line_boundary(point).1;
4660 let clipped = map.clip_point(boundary, Bias::Left);
4661
4662 (clipped, SelectionGoal::None)
4663 });
4664 });
4665
4666 let mut indent_edits = Vec::new();
4667 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4668 for row in rows {
4669 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4670 for (row, indent) in indents {
4671 if indent.len == 0 {
4672 continue;
4673 }
4674
4675 let text = match indent.kind {
4676 IndentKind::Space => " ".repeat(indent.len as usize),
4677 IndentKind::Tab => "\t".repeat(indent.len as usize),
4678 };
4679 let point = Point::new(row.0, 0);
4680 indent_edits.push((point..point, text));
4681 }
4682 }
4683 editor.edit(indent_edits, cx);
4684 });
4685 }
4686
4687 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4688 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4689
4690 let buffer = self.buffer.read(cx);
4691 let snapshot = buffer.snapshot(cx);
4692
4693 let mut edits = Vec::new();
4694 let mut rows = Vec::new();
4695 let mut rows_inserted = 0;
4696
4697 for selection in self.selections.all_adjusted(cx) {
4698 let cursor = selection.head();
4699 let row = cursor.row;
4700
4701 let point = Point::new(row + 1, 0);
4702 let start_of_line = snapshot.clip_point(point, Bias::Left);
4703
4704 let newline = "\n".to_string();
4705 edits.push((start_of_line..start_of_line, newline));
4706
4707 rows_inserted += 1;
4708 rows.push(row + rows_inserted);
4709 }
4710
4711 self.transact(window, cx, |editor, window, cx| {
4712 editor.edit(edits, cx);
4713
4714 editor.change_selections(Default::default(), window, cx, |s| {
4715 let mut index = 0;
4716 s.move_cursors_with(|map, _, _| {
4717 let row = rows[index];
4718 index += 1;
4719
4720 let point = Point::new(row, 0);
4721 let boundary = map.next_line_boundary(point).1;
4722 let clipped = map.clip_point(boundary, Bias::Left);
4723
4724 (clipped, SelectionGoal::None)
4725 });
4726 });
4727
4728 let mut indent_edits = Vec::new();
4729 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4730 for row in rows {
4731 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4732 for (row, indent) in indents {
4733 if indent.len == 0 {
4734 continue;
4735 }
4736
4737 let text = match indent.kind {
4738 IndentKind::Space => " ".repeat(indent.len as usize),
4739 IndentKind::Tab => "\t".repeat(indent.len as usize),
4740 };
4741 let point = Point::new(row.0, 0);
4742 indent_edits.push((point..point, text));
4743 }
4744 }
4745 editor.edit(indent_edits, cx);
4746 });
4747 }
4748
4749 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4750 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4751 original_indent_columns: Vec::new(),
4752 });
4753 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4754 }
4755
4756 fn insert_with_autoindent_mode(
4757 &mut self,
4758 text: &str,
4759 autoindent_mode: Option<AutoindentMode>,
4760 window: &mut Window,
4761 cx: &mut Context<Self>,
4762 ) {
4763 if self.read_only(cx) {
4764 return;
4765 }
4766
4767 let text: Arc<str> = text.into();
4768 self.transact(window, cx, |this, window, cx| {
4769 let old_selections = this.selections.all_adjusted(cx);
4770 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4771 let anchors = {
4772 let snapshot = buffer.read(cx);
4773 old_selections
4774 .iter()
4775 .map(|s| {
4776 let anchor = snapshot.anchor_after(s.head());
4777 s.map(|_| anchor)
4778 })
4779 .collect::<Vec<_>>()
4780 };
4781 buffer.edit(
4782 old_selections
4783 .iter()
4784 .map(|s| (s.start..s.end, text.clone())),
4785 autoindent_mode,
4786 cx,
4787 );
4788 anchors
4789 });
4790
4791 this.change_selections(Default::default(), window, cx, |s| {
4792 s.select_anchors(selection_anchors);
4793 });
4794
4795 cx.notify();
4796 });
4797 }
4798
4799 fn trigger_completion_on_input(
4800 &mut self,
4801 text: &str,
4802 trigger_in_words: bool,
4803 window: &mut Window,
4804 cx: &mut Context<Self>,
4805 ) {
4806 let completions_source = self
4807 .context_menu
4808 .borrow()
4809 .as_ref()
4810 .and_then(|menu| match menu {
4811 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4812 CodeContextMenu::CodeActions(_) => None,
4813 });
4814
4815 match completions_source {
4816 Some(CompletionsMenuSource::Words) => {
4817 self.show_word_completions(&ShowWordCompletions, window, cx)
4818 }
4819 Some(CompletionsMenuSource::Normal)
4820 | Some(CompletionsMenuSource::SnippetChoices)
4821 | None
4822 if self.is_completion_trigger(
4823 text,
4824 trigger_in_words,
4825 completions_source.is_some(),
4826 cx,
4827 ) =>
4828 {
4829 self.show_completions(
4830 &ShowCompletions {
4831 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4832 },
4833 window,
4834 cx,
4835 )
4836 }
4837 _ => {
4838 self.hide_context_menu(window, cx);
4839 }
4840 }
4841 }
4842
4843 fn is_completion_trigger(
4844 &self,
4845 text: &str,
4846 trigger_in_words: bool,
4847 menu_is_open: bool,
4848 cx: &mut Context<Self>,
4849 ) -> bool {
4850 let position = self.selections.newest_anchor().head();
4851 let multibuffer = self.buffer.read(cx);
4852 let Some(buffer) = position
4853 .buffer_id
4854 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4855 else {
4856 return false;
4857 };
4858
4859 if let Some(completion_provider) = &self.completion_provider {
4860 completion_provider.is_completion_trigger(
4861 &buffer,
4862 position.text_anchor,
4863 text,
4864 trigger_in_words,
4865 menu_is_open,
4866 cx,
4867 )
4868 } else {
4869 false
4870 }
4871 }
4872
4873 /// If any empty selections is touching the start of its innermost containing autoclose
4874 /// region, expand it to select the brackets.
4875 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4876 let selections = self.selections.all::<usize>(cx);
4877 let buffer = self.buffer.read(cx).read(cx);
4878 let new_selections = self
4879 .selections_with_autoclose_regions(selections, &buffer)
4880 .map(|(mut selection, region)| {
4881 if !selection.is_empty() {
4882 return selection;
4883 }
4884
4885 if let Some(region) = region {
4886 let mut range = region.range.to_offset(&buffer);
4887 if selection.start == range.start && range.start >= region.pair.start.len() {
4888 range.start -= region.pair.start.len();
4889 if buffer.contains_str_at(range.start, ®ion.pair.start)
4890 && buffer.contains_str_at(range.end, ®ion.pair.end)
4891 {
4892 range.end += region.pair.end.len();
4893 selection.start = range.start;
4894 selection.end = range.end;
4895
4896 return selection;
4897 }
4898 }
4899 }
4900
4901 let always_treat_brackets_as_autoclosed = buffer
4902 .language_settings_at(selection.start, cx)
4903 .always_treat_brackets_as_autoclosed;
4904
4905 if !always_treat_brackets_as_autoclosed {
4906 return selection;
4907 }
4908
4909 if let Some(scope) = buffer.language_scope_at(selection.start) {
4910 for (pair, enabled) in scope.brackets() {
4911 if !enabled || !pair.close {
4912 continue;
4913 }
4914
4915 if buffer.contains_str_at(selection.start, &pair.end) {
4916 let pair_start_len = pair.start.len();
4917 if buffer.contains_str_at(
4918 selection.start.saturating_sub(pair_start_len),
4919 &pair.start,
4920 ) {
4921 selection.start -= pair_start_len;
4922 selection.end += pair.end.len();
4923
4924 return selection;
4925 }
4926 }
4927 }
4928 }
4929
4930 selection
4931 })
4932 .collect();
4933
4934 drop(buffer);
4935 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4936 selections.select(new_selections)
4937 });
4938 }
4939
4940 /// Iterate the given selections, and for each one, find the smallest surrounding
4941 /// autoclose region. This uses the ordering of the selections and the autoclose
4942 /// regions to avoid repeated comparisons.
4943 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4944 &'a self,
4945 selections: impl IntoIterator<Item = Selection<D>>,
4946 buffer: &'a MultiBufferSnapshot,
4947 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4948 let mut i = 0;
4949 let mut regions = self.autoclose_regions.as_slice();
4950 selections.into_iter().map(move |selection| {
4951 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4952
4953 let mut enclosing = None;
4954 while let Some(pair_state) = regions.get(i) {
4955 if pair_state.range.end.to_offset(buffer) < range.start {
4956 regions = ®ions[i + 1..];
4957 i = 0;
4958 } else if pair_state.range.start.to_offset(buffer) > range.end {
4959 break;
4960 } else {
4961 if pair_state.selection_id == selection.id {
4962 enclosing = Some(pair_state);
4963 }
4964 i += 1;
4965 }
4966 }
4967
4968 (selection, enclosing)
4969 })
4970 }
4971
4972 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4973 fn invalidate_autoclose_regions(
4974 &mut self,
4975 mut selections: &[Selection<Anchor>],
4976 buffer: &MultiBufferSnapshot,
4977 ) {
4978 self.autoclose_regions.retain(|state| {
4979 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4980 return false;
4981 }
4982
4983 let mut i = 0;
4984 while let Some(selection) = selections.get(i) {
4985 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4986 selections = &selections[1..];
4987 continue;
4988 }
4989 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4990 break;
4991 }
4992 if selection.id == state.selection_id {
4993 return true;
4994 } else {
4995 i += 1;
4996 }
4997 }
4998 false
4999 });
5000 }
5001
5002 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5003 let offset = position.to_offset(buffer);
5004 let (word_range, kind) = buffer.surrounding_word(offset, true);
5005 if offset > word_range.start && kind == Some(CharKind::Word) {
5006 Some(
5007 buffer
5008 .text_for_range(word_range.start..offset)
5009 .collect::<String>(),
5010 )
5011 } else {
5012 None
5013 }
5014 }
5015
5016 pub fn toggle_inline_values(
5017 &mut self,
5018 _: &ToggleInlineValues,
5019 _: &mut Window,
5020 cx: &mut Context<Self>,
5021 ) {
5022 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5023
5024 self.refresh_inline_values(cx);
5025 }
5026
5027 pub fn toggle_inlay_hints(
5028 &mut self,
5029 _: &ToggleInlayHints,
5030 _: &mut Window,
5031 cx: &mut Context<Self>,
5032 ) {
5033 self.refresh_inlay_hints(
5034 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5035 cx,
5036 );
5037 }
5038
5039 pub fn inlay_hints_enabled(&self) -> bool {
5040 self.inlay_hint_cache.enabled
5041 }
5042
5043 pub fn inline_values_enabled(&self) -> bool {
5044 self.inline_value_cache.enabled
5045 }
5046
5047 #[cfg(any(test, feature = "test-support"))]
5048 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5049 self.display_map
5050 .read(cx)
5051 .current_inlays()
5052 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5053 .cloned()
5054 .collect()
5055 }
5056
5057 #[cfg(any(test, feature = "test-support"))]
5058 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5059 self.display_map
5060 .read(cx)
5061 .current_inlays()
5062 .cloned()
5063 .collect()
5064 }
5065
5066 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5067 if self.semantics_provider.is_none() || !self.mode.is_full() {
5068 return;
5069 }
5070
5071 let reason_description = reason.description();
5072 let ignore_debounce = matches!(
5073 reason,
5074 InlayHintRefreshReason::SettingsChange(_)
5075 | InlayHintRefreshReason::Toggle(_)
5076 | InlayHintRefreshReason::ExcerptsRemoved(_)
5077 | InlayHintRefreshReason::ModifiersChanged(_)
5078 );
5079 let (invalidate_cache, required_languages) = match reason {
5080 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5081 match self.inlay_hint_cache.modifiers_override(enabled) {
5082 Some(enabled) => {
5083 if enabled {
5084 (InvalidationStrategy::RefreshRequested, None)
5085 } else {
5086 self.splice_inlays(
5087 &self
5088 .visible_inlay_hints(cx)
5089 .iter()
5090 .map(|inlay| inlay.id)
5091 .collect::<Vec<InlayId>>(),
5092 Vec::new(),
5093 cx,
5094 );
5095 return;
5096 }
5097 }
5098 None => return,
5099 }
5100 }
5101 InlayHintRefreshReason::Toggle(enabled) => {
5102 if self.inlay_hint_cache.toggle(enabled) {
5103 if enabled {
5104 (InvalidationStrategy::RefreshRequested, None)
5105 } else {
5106 self.splice_inlays(
5107 &self
5108 .visible_inlay_hints(cx)
5109 .iter()
5110 .map(|inlay| inlay.id)
5111 .collect::<Vec<InlayId>>(),
5112 Vec::new(),
5113 cx,
5114 );
5115 return;
5116 }
5117 } else {
5118 return;
5119 }
5120 }
5121 InlayHintRefreshReason::SettingsChange(new_settings) => {
5122 match self.inlay_hint_cache.update_settings(
5123 &self.buffer,
5124 new_settings,
5125 self.visible_inlay_hints(cx),
5126 cx,
5127 ) {
5128 ControlFlow::Break(Some(InlaySplice {
5129 to_remove,
5130 to_insert,
5131 })) => {
5132 self.splice_inlays(&to_remove, to_insert, cx);
5133 return;
5134 }
5135 ControlFlow::Break(None) => return,
5136 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5137 }
5138 }
5139 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5140 if let Some(InlaySplice {
5141 to_remove,
5142 to_insert,
5143 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5144 {
5145 self.splice_inlays(&to_remove, to_insert, cx);
5146 }
5147 self.display_map.update(cx, |display_map, _| {
5148 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5149 });
5150 return;
5151 }
5152 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5153 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5154 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5155 }
5156 InlayHintRefreshReason::RefreshRequested => {
5157 (InvalidationStrategy::RefreshRequested, None)
5158 }
5159 };
5160
5161 if let Some(InlaySplice {
5162 to_remove,
5163 to_insert,
5164 }) = self.inlay_hint_cache.spawn_hint_refresh(
5165 reason_description,
5166 self.visible_excerpts(required_languages.as_ref(), cx),
5167 invalidate_cache,
5168 ignore_debounce,
5169 cx,
5170 ) {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 }
5174
5175 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5176 self.display_map
5177 .read(cx)
5178 .current_inlays()
5179 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5180 .cloned()
5181 .collect()
5182 }
5183
5184 pub fn visible_excerpts(
5185 &self,
5186 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5187 cx: &mut Context<Editor>,
5188 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5189 let Some(project) = self.project.as_ref() else {
5190 return HashMap::default();
5191 };
5192 let project = project.read(cx);
5193 let multi_buffer = self.buffer().read(cx);
5194 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5195 let multi_buffer_visible_start = self
5196 .scroll_manager
5197 .anchor()
5198 .anchor
5199 .to_point(&multi_buffer_snapshot);
5200 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5201 multi_buffer_visible_start
5202 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5203 Bias::Left,
5204 );
5205 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5206 multi_buffer_snapshot
5207 .range_to_buffer_ranges(multi_buffer_visible_range)
5208 .into_iter()
5209 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5210 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5211 let buffer_file = project::File::from_dyn(buffer.file())?;
5212 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5213 let worktree_entry = buffer_worktree
5214 .read(cx)
5215 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5216 if worktree_entry.is_ignored {
5217 return None;
5218 }
5219
5220 let language = buffer.language()?;
5221 if let Some(restrict_to_languages) = restrict_to_languages {
5222 if !restrict_to_languages.contains(language) {
5223 return None;
5224 }
5225 }
5226 Some((
5227 excerpt_id,
5228 (
5229 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5230 buffer.version().clone(),
5231 excerpt_visible_range,
5232 ),
5233 ))
5234 })
5235 .collect()
5236 }
5237
5238 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5239 TextLayoutDetails {
5240 text_system: window.text_system().clone(),
5241 editor_style: self.style.clone().unwrap(),
5242 rem_size: window.rem_size(),
5243 scroll_anchor: self.scroll_manager.anchor(),
5244 visible_rows: self.visible_line_count(),
5245 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5246 }
5247 }
5248
5249 pub fn splice_inlays(
5250 &self,
5251 to_remove: &[InlayId],
5252 to_insert: Vec<Inlay>,
5253 cx: &mut Context<Self>,
5254 ) {
5255 self.display_map.update(cx, |display_map, cx| {
5256 display_map.splice_inlays(to_remove, to_insert, cx)
5257 });
5258 cx.notify();
5259 }
5260
5261 fn trigger_on_type_formatting(
5262 &self,
5263 input: String,
5264 window: &mut Window,
5265 cx: &mut Context<Self>,
5266 ) -> Option<Task<Result<()>>> {
5267 if input.len() != 1 {
5268 return None;
5269 }
5270
5271 let project = self.project.as_ref()?;
5272 let position = self.selections.newest_anchor().head();
5273 let (buffer, buffer_position) = self
5274 .buffer
5275 .read(cx)
5276 .text_anchor_for_position(position, cx)?;
5277
5278 let settings = language_settings::language_settings(
5279 buffer
5280 .read(cx)
5281 .language_at(buffer_position)
5282 .map(|l| l.name()),
5283 buffer.read(cx).file(),
5284 cx,
5285 );
5286 if !settings.use_on_type_format {
5287 return None;
5288 }
5289
5290 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5291 // hence we do LSP request & edit on host side only — add formats to host's history.
5292 let push_to_lsp_host_history = true;
5293 // If this is not the host, append its history with new edits.
5294 let push_to_client_history = project.read(cx).is_via_collab();
5295
5296 let on_type_formatting = project.update(cx, |project, cx| {
5297 project.on_type_format(
5298 buffer.clone(),
5299 buffer_position,
5300 input,
5301 push_to_lsp_host_history,
5302 cx,
5303 )
5304 });
5305 Some(cx.spawn_in(window, async move |editor, cx| {
5306 if let Some(transaction) = on_type_formatting.await? {
5307 if push_to_client_history {
5308 buffer
5309 .update(cx, |buffer, _| {
5310 buffer.push_transaction(transaction, Instant::now());
5311 buffer.finalize_last_transaction();
5312 })
5313 .ok();
5314 }
5315 editor.update(cx, |editor, cx| {
5316 editor.refresh_document_highlights(cx);
5317 })?;
5318 }
5319 Ok(())
5320 }))
5321 }
5322
5323 pub fn show_word_completions(
5324 &mut self,
5325 _: &ShowWordCompletions,
5326 window: &mut Window,
5327 cx: &mut Context<Self>,
5328 ) {
5329 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5330 }
5331
5332 pub fn show_completions(
5333 &mut self,
5334 options: &ShowCompletions,
5335 window: &mut Window,
5336 cx: &mut Context<Self>,
5337 ) {
5338 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5339 }
5340
5341 fn open_or_update_completions_menu(
5342 &mut self,
5343 requested_source: Option<CompletionsMenuSource>,
5344 trigger: Option<&str>,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) {
5348 if self.pending_rename.is_some() {
5349 return;
5350 }
5351
5352 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5353
5354 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5355 // inserted and selected. To handle that case, the start of the selection is used so that
5356 // the menu starts with all choices.
5357 let position = self
5358 .selections
5359 .newest_anchor()
5360 .start
5361 .bias_right(&multibuffer_snapshot);
5362 if position.diff_base_anchor.is_some() {
5363 return;
5364 }
5365 let (buffer, buffer_position) =
5366 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5367 output
5368 } else {
5369 return;
5370 };
5371 let buffer_snapshot = buffer.read(cx).snapshot();
5372
5373 let query: Option<Arc<String>> =
5374 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5375
5376 drop(multibuffer_snapshot);
5377
5378 let provider = match requested_source {
5379 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5380 Some(CompletionsMenuSource::Words) => None,
5381 Some(CompletionsMenuSource::SnippetChoices) => {
5382 log::error!("bug: SnippetChoices requested_source is not handled");
5383 None
5384 }
5385 };
5386
5387 let sort_completions = provider
5388 .as_ref()
5389 .map_or(false, |provider| provider.sort_completions());
5390
5391 let filter_completions = provider
5392 .as_ref()
5393 .map_or(true, |provider| provider.filter_completions());
5394
5395 let trigger_kind = match trigger {
5396 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5397 CompletionTriggerKind::TRIGGER_CHARACTER
5398 }
5399 _ => CompletionTriggerKind::INVOKED,
5400 };
5401 let completion_context = CompletionContext {
5402 trigger_character: trigger.and_then(|trigger| {
5403 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5404 Some(String::from(trigger))
5405 } else {
5406 None
5407 }
5408 }),
5409 trigger_kind,
5410 };
5411
5412 // Hide the current completions menu when a trigger char is typed. Without this, cached
5413 // completions from before the trigger char may be reused (#32774). Snippet choices could
5414 // involve trigger chars, so this is skipped in that case.
5415 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5416 {
5417 let menu_is_open = matches!(
5418 self.context_menu.borrow().as_ref(),
5419 Some(CodeContextMenu::Completions(_))
5420 );
5421 if menu_is_open {
5422 self.hide_context_menu(window, cx);
5423 }
5424 }
5425
5426 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5427 if filter_completions {
5428 menu.filter(query.clone(), provider.clone(), window, cx);
5429 }
5430 // When `is_incomplete` is false, no need to re-query completions when the current query
5431 // is a suffix of the initial query.
5432 if !menu.is_incomplete {
5433 // If the new query is a suffix of the old query (typing more characters) and
5434 // the previous result was complete, the existing completions can be filtered.
5435 //
5436 // Note that this is always true for snippet completions.
5437 let query_matches = match (&menu.initial_query, &query) {
5438 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5439 (None, _) => true,
5440 _ => false,
5441 };
5442 if query_matches {
5443 let position_matches = if menu.initial_position == position {
5444 true
5445 } else {
5446 let snapshot = self.buffer.read(cx).read(cx);
5447 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5448 };
5449 if position_matches {
5450 return;
5451 }
5452 }
5453 }
5454 };
5455
5456 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5457 buffer_snapshot.surrounding_word(buffer_position, false)
5458 {
5459 let word_to_exclude = buffer_snapshot
5460 .text_for_range(word_range.clone())
5461 .collect::<String>();
5462 (
5463 buffer_snapshot.anchor_before(word_range.start)
5464 ..buffer_snapshot.anchor_after(buffer_position),
5465 Some(word_to_exclude),
5466 )
5467 } else {
5468 (buffer_position..buffer_position, None)
5469 };
5470
5471 let language = buffer_snapshot
5472 .language_at(buffer_position)
5473 .map(|language| language.name());
5474
5475 let completion_settings =
5476 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5477
5478 let show_completion_documentation = buffer_snapshot
5479 .settings_at(buffer_position, cx)
5480 .show_completion_documentation;
5481
5482 // The document can be large, so stay in reasonable bounds when searching for words,
5483 // otherwise completion pop-up might be slow to appear.
5484 const WORD_LOOKUP_ROWS: u32 = 5_000;
5485 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5486 let min_word_search = buffer_snapshot.clip_point(
5487 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5488 Bias::Left,
5489 );
5490 let max_word_search = buffer_snapshot.clip_point(
5491 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5492 Bias::Right,
5493 );
5494 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5495 ..buffer_snapshot.point_to_offset(max_word_search);
5496
5497 let skip_digits = query
5498 .as_ref()
5499 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5500
5501 let (mut words, provider_responses) = match &provider {
5502 Some(provider) => {
5503 let provider_responses = provider.completions(
5504 position.excerpt_id,
5505 &buffer,
5506 buffer_position,
5507 completion_context,
5508 window,
5509 cx,
5510 );
5511
5512 let words = match completion_settings.words {
5513 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5514 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5515 .background_spawn(async move {
5516 buffer_snapshot.words_in_range(WordsQuery {
5517 fuzzy_contents: None,
5518 range: word_search_range,
5519 skip_digits,
5520 })
5521 }),
5522 };
5523
5524 (words, provider_responses)
5525 }
5526 None => (
5527 cx.background_spawn(async move {
5528 buffer_snapshot.words_in_range(WordsQuery {
5529 fuzzy_contents: None,
5530 range: word_search_range,
5531 skip_digits,
5532 })
5533 }),
5534 Task::ready(Ok(Vec::new())),
5535 ),
5536 };
5537
5538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5539
5540 let id = post_inc(&mut self.next_completion_id);
5541 let task = cx.spawn_in(window, async move |editor, cx| {
5542 let Ok(()) = editor.update(cx, |this, _| {
5543 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5544 }) else {
5545 return;
5546 };
5547
5548 // TODO: Ideally completions from different sources would be selectively re-queried, so
5549 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5550 let mut completions = Vec::new();
5551 let mut is_incomplete = false;
5552 if let Some(provider_responses) = provider_responses.await.log_err() {
5553 if !provider_responses.is_empty() {
5554 for response in provider_responses {
5555 completions.extend(response.completions);
5556 is_incomplete = is_incomplete || response.is_incomplete;
5557 }
5558 if completion_settings.words == WordsCompletionMode::Fallback {
5559 words = Task::ready(BTreeMap::default());
5560 }
5561 }
5562 }
5563
5564 let mut words = words.await;
5565 if let Some(word_to_exclude) = &word_to_exclude {
5566 words.remove(word_to_exclude);
5567 }
5568 for lsp_completion in &completions {
5569 words.remove(&lsp_completion.new_text);
5570 }
5571 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5572 replace_range: word_replace_range.clone(),
5573 new_text: word.clone(),
5574 label: CodeLabel::plain(word, None),
5575 icon_path: None,
5576 documentation: None,
5577 source: CompletionSource::BufferWord {
5578 word_range,
5579 resolved: false,
5580 },
5581 insert_text_mode: Some(InsertTextMode::AS_IS),
5582 confirm: None,
5583 }));
5584
5585 let menu = if completions.is_empty() {
5586 None
5587 } else {
5588 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5589 let languages = editor
5590 .workspace
5591 .as_ref()
5592 .and_then(|(workspace, _)| workspace.upgrade())
5593 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5594 let menu = CompletionsMenu::new(
5595 id,
5596 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5597 sort_completions,
5598 show_completion_documentation,
5599 position,
5600 query.clone(),
5601 is_incomplete,
5602 buffer.clone(),
5603 completions.into(),
5604 snippet_sort_order,
5605 languages,
5606 language,
5607 cx,
5608 );
5609
5610 let query = if filter_completions { query } else { None };
5611 let matches_task = if let Some(query) = query {
5612 menu.do_async_filtering(query, cx)
5613 } else {
5614 Task::ready(menu.unfiltered_matches())
5615 };
5616 (menu, matches_task)
5617 }) else {
5618 return;
5619 };
5620
5621 let matches = matches_task.await;
5622
5623 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5624 // Newer menu already set, so exit.
5625 match editor.context_menu.borrow().as_ref() {
5626 Some(CodeContextMenu::Completions(prev_menu)) => {
5627 if prev_menu.id > id {
5628 return;
5629 }
5630 }
5631 _ => {}
5632 };
5633
5634 // Only valid to take prev_menu because it the new menu is immediately set
5635 // below, or the menu is hidden.
5636 match editor.context_menu.borrow_mut().take() {
5637 Some(CodeContextMenu::Completions(prev_menu)) => {
5638 let position_matches =
5639 if prev_menu.initial_position == menu.initial_position {
5640 true
5641 } else {
5642 let snapshot = editor.buffer.read(cx).read(cx);
5643 prev_menu.initial_position.to_offset(&snapshot)
5644 == menu.initial_position.to_offset(&snapshot)
5645 };
5646 if position_matches {
5647 // Preserve markdown cache before `set_filter_results` because it will
5648 // try to populate the documentation cache.
5649 menu.preserve_markdown_cache(prev_menu);
5650 }
5651 }
5652 _ => {}
5653 };
5654
5655 menu.set_filter_results(matches, provider, window, cx);
5656 }) else {
5657 return;
5658 };
5659
5660 menu.visible().then_some(menu)
5661 };
5662
5663 editor
5664 .update_in(cx, |editor, window, cx| {
5665 if editor.focus_handle.is_focused(window) {
5666 if let Some(menu) = menu {
5667 *editor.context_menu.borrow_mut() =
5668 Some(CodeContextMenu::Completions(menu));
5669
5670 crate::hover_popover::hide_hover(editor, cx);
5671 if editor.show_edit_predictions_in_menu() {
5672 editor.update_visible_inline_completion(window, cx);
5673 } else {
5674 editor.discard_inline_completion(false, cx);
5675 }
5676
5677 cx.notify();
5678 return;
5679 }
5680 }
5681
5682 if editor.completion_tasks.len() <= 1 {
5683 // If there are no more completion tasks and the last menu was empty, we should hide it.
5684 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5685 // If it was already hidden and we don't show inline completions in the menu, we should
5686 // also show the inline-completion when available.
5687 if was_hidden && editor.show_edit_predictions_in_menu() {
5688 editor.update_visible_inline_completion(window, cx);
5689 }
5690 }
5691 })
5692 .ok();
5693 });
5694
5695 self.completion_tasks.push((id, task));
5696 }
5697
5698 #[cfg(feature = "test-support")]
5699 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5700 let menu = self.context_menu.borrow();
5701 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5702 let completions = menu.completions.borrow();
5703 Some(completions.to_vec())
5704 } else {
5705 None
5706 }
5707 }
5708
5709 pub fn with_completions_menu_matching_id<R>(
5710 &self,
5711 id: CompletionId,
5712 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5713 ) -> R {
5714 let mut context_menu = self.context_menu.borrow_mut();
5715 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5716 return f(None);
5717 };
5718 if completions_menu.id != id {
5719 return f(None);
5720 }
5721 f(Some(completions_menu))
5722 }
5723
5724 pub fn confirm_completion(
5725 &mut self,
5726 action: &ConfirmCompletion,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) -> Option<Task<Result<()>>> {
5730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5731 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5732 }
5733
5734 pub fn confirm_completion_insert(
5735 &mut self,
5736 _: &ConfirmCompletionInsert,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) -> Option<Task<Result<()>>> {
5740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5741 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5742 }
5743
5744 pub fn confirm_completion_replace(
5745 &mut self,
5746 _: &ConfirmCompletionReplace,
5747 window: &mut Window,
5748 cx: &mut Context<Self>,
5749 ) -> Option<Task<Result<()>>> {
5750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5751 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5752 }
5753
5754 pub fn compose_completion(
5755 &mut self,
5756 action: &ComposeCompletion,
5757 window: &mut Window,
5758 cx: &mut Context<Self>,
5759 ) -> Option<Task<Result<()>>> {
5760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5761 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5762 }
5763
5764 fn do_completion(
5765 &mut self,
5766 item_ix: Option<usize>,
5767 intent: CompletionIntent,
5768 window: &mut Window,
5769 cx: &mut Context<Editor>,
5770 ) -> Option<Task<Result<()>>> {
5771 use language::ToOffset as _;
5772
5773 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5774 else {
5775 return None;
5776 };
5777
5778 let candidate_id = {
5779 let entries = completions_menu.entries.borrow();
5780 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5781 if self.show_edit_predictions_in_menu() {
5782 self.discard_inline_completion(true, cx);
5783 }
5784 mat.candidate_id
5785 };
5786
5787 let completion = completions_menu
5788 .completions
5789 .borrow()
5790 .get(candidate_id)?
5791 .clone();
5792 cx.stop_propagation();
5793
5794 let buffer_handle = completions_menu.buffer.clone();
5795
5796 let CompletionEdit {
5797 new_text,
5798 snippet,
5799 replace_range,
5800 } = process_completion_for_edit(
5801 &completion,
5802 intent,
5803 &buffer_handle,
5804 &completions_menu.initial_position.text_anchor,
5805 cx,
5806 );
5807
5808 let buffer = buffer_handle.read(cx);
5809 let snapshot = self.buffer.read(cx).snapshot(cx);
5810 let newest_anchor = self.selections.newest_anchor();
5811 let replace_range_multibuffer = {
5812 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5813 let multibuffer_anchor = snapshot
5814 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5815 .unwrap()
5816 ..snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5818 .unwrap();
5819 multibuffer_anchor.start.to_offset(&snapshot)
5820 ..multibuffer_anchor.end.to_offset(&snapshot)
5821 };
5822 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5823 return None;
5824 }
5825
5826 let old_text = buffer
5827 .text_for_range(replace_range.clone())
5828 .collect::<String>();
5829 let lookbehind = newest_anchor
5830 .start
5831 .text_anchor
5832 .to_offset(buffer)
5833 .saturating_sub(replace_range.start);
5834 let lookahead = replace_range
5835 .end
5836 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5837 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5838 let suffix = &old_text[lookbehind.min(old_text.len())..];
5839
5840 let selections = self.selections.all::<usize>(cx);
5841 let mut ranges = Vec::new();
5842 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5843
5844 for selection in &selections {
5845 let range = if selection.id == newest_anchor.id {
5846 replace_range_multibuffer.clone()
5847 } else {
5848 let mut range = selection.range();
5849
5850 // if prefix is present, don't duplicate it
5851 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5852 range.start = range.start.saturating_sub(lookbehind);
5853
5854 // if suffix is also present, mimic the newest cursor and replace it
5855 if selection.id != newest_anchor.id
5856 && snapshot.contains_str_at(range.end, suffix)
5857 {
5858 range.end += lookahead;
5859 }
5860 }
5861 range
5862 };
5863
5864 ranges.push(range.clone());
5865
5866 if !self.linked_edit_ranges.is_empty() {
5867 let start_anchor = snapshot.anchor_before(range.start);
5868 let end_anchor = snapshot.anchor_after(range.end);
5869 if let Some(ranges) = self
5870 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5871 {
5872 for (buffer, edits) in ranges {
5873 linked_edits
5874 .entry(buffer.clone())
5875 .or_default()
5876 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5877 }
5878 }
5879 }
5880 }
5881
5882 let common_prefix_len = old_text
5883 .chars()
5884 .zip(new_text.chars())
5885 .take_while(|(a, b)| a == b)
5886 .map(|(a, _)| a.len_utf8())
5887 .sum::<usize>();
5888
5889 cx.emit(EditorEvent::InputHandled {
5890 utf16_range_to_replace: None,
5891 text: new_text[common_prefix_len..].into(),
5892 });
5893
5894 self.transact(window, cx, |editor, window, cx| {
5895 if let Some(mut snippet) = snippet {
5896 snippet.text = new_text.to_string();
5897 editor
5898 .insert_snippet(&ranges, snippet, window, cx)
5899 .log_err();
5900 } else {
5901 editor.buffer.update(cx, |multi_buffer, cx| {
5902 let auto_indent = match completion.insert_text_mode {
5903 Some(InsertTextMode::AS_IS) => None,
5904 _ => editor.autoindent_mode.clone(),
5905 };
5906 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5907 multi_buffer.edit(edits, auto_indent, cx);
5908 });
5909 }
5910 for (buffer, edits) in linked_edits {
5911 buffer.update(cx, |buffer, cx| {
5912 let snapshot = buffer.snapshot();
5913 let edits = edits
5914 .into_iter()
5915 .map(|(range, text)| {
5916 use text::ToPoint as TP;
5917 let end_point = TP::to_point(&range.end, &snapshot);
5918 let start_point = TP::to_point(&range.start, &snapshot);
5919 (start_point..end_point, text)
5920 })
5921 .sorted_by_key(|(range, _)| range.start);
5922 buffer.edit(edits, None, cx);
5923 })
5924 }
5925
5926 editor.refresh_inline_completion(true, false, window, cx);
5927 });
5928 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5929
5930 let show_new_completions_on_confirm = completion
5931 .confirm
5932 .as_ref()
5933 .map_or(false, |confirm| confirm(intent, window, cx));
5934 if show_new_completions_on_confirm {
5935 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5936 }
5937
5938 let provider = self.completion_provider.as_ref()?;
5939 drop(completion);
5940 let apply_edits = provider.apply_additional_edits_for_completion(
5941 buffer_handle,
5942 completions_menu.completions.clone(),
5943 candidate_id,
5944 true,
5945 cx,
5946 );
5947
5948 let editor_settings = EditorSettings::get_global(cx);
5949 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5950 // After the code completion is finished, users often want to know what signatures are needed.
5951 // so we should automatically call signature_help
5952 self.show_signature_help(&ShowSignatureHelp, window, cx);
5953 }
5954
5955 Some(cx.foreground_executor().spawn(async move {
5956 apply_edits.await?;
5957 Ok(())
5958 }))
5959 }
5960
5961 pub fn toggle_code_actions(
5962 &mut self,
5963 action: &ToggleCodeActions,
5964 window: &mut Window,
5965 cx: &mut Context<Self>,
5966 ) {
5967 let quick_launch = action.quick_launch;
5968 let mut context_menu = self.context_menu.borrow_mut();
5969 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5970 if code_actions.deployed_from == action.deployed_from {
5971 // Toggle if we're selecting the same one
5972 *context_menu = None;
5973 cx.notify();
5974 return;
5975 } else {
5976 // Otherwise, clear it and start a new one
5977 *context_menu = None;
5978 cx.notify();
5979 }
5980 }
5981 drop(context_menu);
5982 let snapshot = self.snapshot(window, cx);
5983 let deployed_from = action.deployed_from.clone();
5984 let action = action.clone();
5985 self.completion_tasks.clear();
5986 self.discard_inline_completion(false, cx);
5987
5988 let multibuffer_point = match &action.deployed_from {
5989 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5990 DisplayPoint::new(*row, 0).to_point(&snapshot)
5991 }
5992 _ => self.selections.newest::<Point>(cx).head(),
5993 };
5994 let Some((buffer, buffer_row)) = snapshot
5995 .buffer_snapshot
5996 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5997 .and_then(|(buffer_snapshot, range)| {
5998 self.buffer()
5999 .read(cx)
6000 .buffer(buffer_snapshot.remote_id())
6001 .map(|buffer| (buffer, range.start.row))
6002 })
6003 else {
6004 return;
6005 };
6006 let buffer_id = buffer.read(cx).remote_id();
6007 let tasks = self
6008 .tasks
6009 .get(&(buffer_id, buffer_row))
6010 .map(|t| Arc::new(t.to_owned()));
6011
6012 if !self.focus_handle.is_focused(window) {
6013 return;
6014 }
6015 let project = self.project.clone();
6016
6017 let code_actions_task = match deployed_from {
6018 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6019 _ => self.code_actions(buffer_row, window, cx),
6020 };
6021
6022 let runnable_task = match deployed_from {
6023 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6024 _ => {
6025 let mut task_context_task = Task::ready(None);
6026 if let Some(tasks) = &tasks {
6027 if let Some(project) = project {
6028 task_context_task =
6029 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6030 }
6031 }
6032
6033 cx.spawn_in(window, {
6034 let buffer = buffer.clone();
6035 async move |editor, cx| {
6036 let task_context = task_context_task.await;
6037
6038 let resolved_tasks =
6039 tasks
6040 .zip(task_context.clone())
6041 .map(|(tasks, task_context)| ResolvedTasks {
6042 templates: tasks.resolve(&task_context).collect(),
6043 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6044 multibuffer_point.row,
6045 tasks.column,
6046 )),
6047 });
6048 let debug_scenarios = editor
6049 .update(cx, |editor, cx| {
6050 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6051 })?
6052 .await;
6053 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6054 }
6055 })
6056 }
6057 };
6058
6059 cx.spawn_in(window, async move |editor, cx| {
6060 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6061 let code_actions = code_actions_task.await;
6062 let spawn_straight_away = quick_launch
6063 && resolved_tasks
6064 .as_ref()
6065 .map_or(false, |tasks| tasks.templates.len() == 1)
6066 && code_actions
6067 .as_ref()
6068 .map_or(true, |actions| actions.is_empty())
6069 && debug_scenarios.is_empty();
6070
6071 editor.update_in(cx, |editor, window, cx| {
6072 crate::hover_popover::hide_hover(editor, cx);
6073 let actions = CodeActionContents::new(
6074 resolved_tasks,
6075 code_actions,
6076 debug_scenarios,
6077 task_context.unwrap_or_default(),
6078 );
6079
6080 // Don't show the menu if there are no actions available
6081 if actions.is_empty() {
6082 cx.notify();
6083 return Task::ready(Ok(()));
6084 }
6085
6086 *editor.context_menu.borrow_mut() =
6087 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6088 buffer,
6089 actions,
6090 selected_item: Default::default(),
6091 scroll_handle: UniformListScrollHandle::default(),
6092 deployed_from,
6093 }));
6094 cx.notify();
6095 if spawn_straight_away {
6096 if let Some(task) = editor.confirm_code_action(
6097 &ConfirmCodeAction { item_ix: Some(0) },
6098 window,
6099 cx,
6100 ) {
6101 return task;
6102 }
6103 }
6104
6105 Task::ready(Ok(()))
6106 })
6107 })
6108 .detach_and_log_err(cx);
6109 }
6110
6111 fn debug_scenarios(
6112 &mut self,
6113 resolved_tasks: &Option<ResolvedTasks>,
6114 buffer: &Entity<Buffer>,
6115 cx: &mut App,
6116 ) -> Task<Vec<task::DebugScenario>> {
6117 maybe!({
6118 let project = self.project.as_ref()?;
6119 let dap_store = project.read(cx).dap_store();
6120 let mut scenarios = vec![];
6121 let resolved_tasks = resolved_tasks.as_ref()?;
6122 let buffer = buffer.read(cx);
6123 let language = buffer.language()?;
6124 let file = buffer.file();
6125 let debug_adapter = language_settings(language.name().into(), file, cx)
6126 .debuggers
6127 .first()
6128 .map(SharedString::from)
6129 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6130
6131 dap_store.update(cx, |dap_store, cx| {
6132 for (_, task) in &resolved_tasks.templates {
6133 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6134 task.original_task().clone(),
6135 debug_adapter.clone().into(),
6136 task.display_label().to_owned().into(),
6137 cx,
6138 );
6139 scenarios.push(maybe_scenario);
6140 }
6141 });
6142 Some(cx.background_spawn(async move {
6143 let scenarios = futures::future::join_all(scenarios)
6144 .await
6145 .into_iter()
6146 .flatten()
6147 .collect::<Vec<_>>();
6148 scenarios
6149 }))
6150 })
6151 .unwrap_or_else(|| Task::ready(vec![]))
6152 }
6153
6154 fn code_actions(
6155 &mut self,
6156 buffer_row: u32,
6157 window: &mut Window,
6158 cx: &mut Context<Self>,
6159 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6160 let mut task = self.code_actions_task.take();
6161 cx.spawn_in(window, async move |editor, cx| {
6162 while let Some(prev_task) = task {
6163 prev_task.await.log_err();
6164 task = editor
6165 .update(cx, |this, _| this.code_actions_task.take())
6166 .ok()?;
6167 }
6168
6169 editor
6170 .update(cx, |editor, cx| {
6171 editor
6172 .available_code_actions
6173 .clone()
6174 .and_then(|(location, code_actions)| {
6175 let snapshot = location.buffer.read(cx).snapshot();
6176 let point_range = location.range.to_point(&snapshot);
6177 let point_range = point_range.start.row..=point_range.end.row;
6178 if point_range.contains(&buffer_row) {
6179 Some(code_actions)
6180 } else {
6181 None
6182 }
6183 })
6184 })
6185 .ok()
6186 .flatten()
6187 })
6188 }
6189
6190 pub fn confirm_code_action(
6191 &mut self,
6192 action: &ConfirmCodeAction,
6193 window: &mut Window,
6194 cx: &mut Context<Self>,
6195 ) -> Option<Task<Result<()>>> {
6196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6197
6198 let actions_menu =
6199 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6200 menu
6201 } else {
6202 return None;
6203 };
6204
6205 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6206 let action = actions_menu.actions.get(action_ix)?;
6207 let title = action.label();
6208 let buffer = actions_menu.buffer;
6209 let workspace = self.workspace()?;
6210
6211 match action {
6212 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6213 workspace.update(cx, |workspace, cx| {
6214 workspace.schedule_resolved_task(
6215 task_source_kind,
6216 resolved_task,
6217 false,
6218 window,
6219 cx,
6220 );
6221
6222 Some(Task::ready(Ok(())))
6223 })
6224 }
6225 CodeActionsItem::CodeAction {
6226 excerpt_id,
6227 action,
6228 provider,
6229 } => {
6230 let apply_code_action =
6231 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6232 let workspace = workspace.downgrade();
6233 Some(cx.spawn_in(window, async move |editor, cx| {
6234 let project_transaction = apply_code_action.await?;
6235 Self::open_project_transaction(
6236 &editor,
6237 workspace,
6238 project_transaction,
6239 title,
6240 cx,
6241 )
6242 .await
6243 }))
6244 }
6245 CodeActionsItem::DebugScenario(scenario) => {
6246 let context = actions_menu.actions.context.clone();
6247
6248 workspace.update(cx, |workspace, cx| {
6249 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6250 workspace.start_debug_session(
6251 scenario,
6252 context,
6253 Some(buffer),
6254 None,
6255 window,
6256 cx,
6257 );
6258 });
6259 Some(Task::ready(Ok(())))
6260 }
6261 }
6262 }
6263
6264 pub async fn open_project_transaction(
6265 this: &WeakEntity<Editor>,
6266 workspace: WeakEntity<Workspace>,
6267 transaction: ProjectTransaction,
6268 title: String,
6269 cx: &mut AsyncWindowContext,
6270 ) -> Result<()> {
6271 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6272 cx.update(|_, cx| {
6273 entries.sort_unstable_by_key(|(buffer, _)| {
6274 buffer.read(cx).file().map(|f| f.path().clone())
6275 });
6276 })?;
6277
6278 // If the project transaction's edits are all contained within this editor, then
6279 // avoid opening a new editor to display them.
6280
6281 if let Some((buffer, transaction)) = entries.first() {
6282 if entries.len() == 1 {
6283 let excerpt = this.update(cx, |editor, cx| {
6284 editor
6285 .buffer()
6286 .read(cx)
6287 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6288 })?;
6289 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6290 if excerpted_buffer == *buffer {
6291 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6292 let excerpt_range = excerpt_range.to_offset(buffer);
6293 buffer
6294 .edited_ranges_for_transaction::<usize>(transaction)
6295 .all(|range| {
6296 excerpt_range.start <= range.start
6297 && excerpt_range.end >= range.end
6298 })
6299 })?;
6300
6301 if all_edits_within_excerpt {
6302 return Ok(());
6303 }
6304 }
6305 }
6306 }
6307 } else {
6308 return Ok(());
6309 }
6310
6311 let mut ranges_to_highlight = Vec::new();
6312 let excerpt_buffer = cx.new(|cx| {
6313 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6314 for (buffer_handle, transaction) in &entries {
6315 let edited_ranges = buffer_handle
6316 .read(cx)
6317 .edited_ranges_for_transaction::<Point>(transaction)
6318 .collect::<Vec<_>>();
6319 let (ranges, _) = multibuffer.set_excerpts_for_path(
6320 PathKey::for_buffer(buffer_handle, cx),
6321 buffer_handle.clone(),
6322 edited_ranges,
6323 DEFAULT_MULTIBUFFER_CONTEXT,
6324 cx,
6325 );
6326
6327 ranges_to_highlight.extend(ranges);
6328 }
6329 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6330 multibuffer
6331 })?;
6332
6333 workspace.update_in(cx, |workspace, window, cx| {
6334 let project = workspace.project().clone();
6335 let editor =
6336 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6337 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6338 editor.update(cx, |editor, cx| {
6339 editor.highlight_background::<Self>(
6340 &ranges_to_highlight,
6341 |theme| theme.colors().editor_highlighted_line_background,
6342 cx,
6343 );
6344 });
6345 })?;
6346
6347 Ok(())
6348 }
6349
6350 pub fn clear_code_action_providers(&mut self) {
6351 self.code_action_providers.clear();
6352 self.available_code_actions.take();
6353 }
6354
6355 pub fn add_code_action_provider(
6356 &mut self,
6357 provider: Rc<dyn CodeActionProvider>,
6358 window: &mut Window,
6359 cx: &mut Context<Self>,
6360 ) {
6361 if self
6362 .code_action_providers
6363 .iter()
6364 .any(|existing_provider| existing_provider.id() == provider.id())
6365 {
6366 return;
6367 }
6368
6369 self.code_action_providers.push(provider);
6370 self.refresh_code_actions(window, cx);
6371 }
6372
6373 pub fn remove_code_action_provider(
6374 &mut self,
6375 id: Arc<str>,
6376 window: &mut Window,
6377 cx: &mut Context<Self>,
6378 ) {
6379 self.code_action_providers
6380 .retain(|provider| provider.id() != id);
6381 self.refresh_code_actions(window, cx);
6382 }
6383
6384 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6385 !self.code_action_providers.is_empty()
6386 && EditorSettings::get_global(cx).toolbar.code_actions
6387 }
6388
6389 pub fn has_available_code_actions(&self) -> bool {
6390 self.available_code_actions
6391 .as_ref()
6392 .is_some_and(|(_, actions)| !actions.is_empty())
6393 }
6394
6395 fn render_inline_code_actions(
6396 &self,
6397 icon_size: ui::IconSize,
6398 display_row: DisplayRow,
6399 is_active: bool,
6400 cx: &mut Context<Self>,
6401 ) -> AnyElement {
6402 let show_tooltip = !self.context_menu_visible();
6403 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6404 .icon_size(icon_size)
6405 .shape(ui::IconButtonShape::Square)
6406 .style(ButtonStyle::Transparent)
6407 .icon_color(ui::Color::Hidden)
6408 .toggle_state(is_active)
6409 .when(show_tooltip, |this| {
6410 this.tooltip({
6411 let focus_handle = self.focus_handle.clone();
6412 move |window, cx| {
6413 Tooltip::for_action_in(
6414 "Toggle Code Actions",
6415 &ToggleCodeActions {
6416 deployed_from: None,
6417 quick_launch: false,
6418 },
6419 &focus_handle,
6420 window,
6421 cx,
6422 )
6423 }
6424 })
6425 })
6426 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6427 window.focus(&editor.focus_handle(cx));
6428 editor.toggle_code_actions(
6429 &crate::actions::ToggleCodeActions {
6430 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6431 display_row,
6432 )),
6433 quick_launch: false,
6434 },
6435 window,
6436 cx,
6437 );
6438 }))
6439 .into_any_element()
6440 }
6441
6442 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6443 &self.context_menu
6444 }
6445
6446 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6447 let newest_selection = self.selections.newest_anchor().clone();
6448 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6449 let buffer = self.buffer.read(cx);
6450 if newest_selection.head().diff_base_anchor.is_some() {
6451 return None;
6452 }
6453 let (start_buffer, start) =
6454 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6455 let (end_buffer, end) =
6456 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6457 if start_buffer != end_buffer {
6458 return None;
6459 }
6460
6461 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6462 cx.background_executor()
6463 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6464 .await;
6465
6466 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6467 let providers = this.code_action_providers.clone();
6468 let tasks = this
6469 .code_action_providers
6470 .iter()
6471 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6472 .collect::<Vec<_>>();
6473 (providers, tasks)
6474 })?;
6475
6476 let mut actions = Vec::new();
6477 for (provider, provider_actions) in
6478 providers.into_iter().zip(future::join_all(tasks).await)
6479 {
6480 if let Some(provider_actions) = provider_actions.log_err() {
6481 actions.extend(provider_actions.into_iter().map(|action| {
6482 AvailableCodeAction {
6483 excerpt_id: newest_selection.start.excerpt_id,
6484 action,
6485 provider: provider.clone(),
6486 }
6487 }));
6488 }
6489 }
6490
6491 this.update(cx, |this, cx| {
6492 this.available_code_actions = if actions.is_empty() {
6493 None
6494 } else {
6495 Some((
6496 Location {
6497 buffer: start_buffer,
6498 range: start..end,
6499 },
6500 actions.into(),
6501 ))
6502 };
6503 cx.notify();
6504 })
6505 }));
6506 None
6507 }
6508
6509 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6510 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6511 self.show_git_blame_inline = false;
6512
6513 self.show_git_blame_inline_delay_task =
6514 Some(cx.spawn_in(window, async move |this, cx| {
6515 cx.background_executor().timer(delay).await;
6516
6517 this.update(cx, |this, cx| {
6518 this.show_git_blame_inline = true;
6519 cx.notify();
6520 })
6521 .log_err();
6522 }));
6523 }
6524 }
6525
6526 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6527 let snapshot = self.snapshot(window, cx);
6528 let cursor = self.selections.newest::<Point>(cx).head();
6529 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6530 else {
6531 return;
6532 };
6533
6534 let Some(blame) = self.blame.as_ref() else {
6535 return;
6536 };
6537
6538 let row_info = RowInfo {
6539 buffer_id: Some(buffer.remote_id()),
6540 buffer_row: Some(point.row),
6541 ..Default::default()
6542 };
6543 let Some(blame_entry) = blame
6544 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6545 .flatten()
6546 else {
6547 return;
6548 };
6549
6550 let anchor = self.selections.newest_anchor().head();
6551 let position = self.to_pixel_point(anchor, &snapshot, window);
6552 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6553 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6554 };
6555 }
6556
6557 fn show_blame_popover(
6558 &mut self,
6559 blame_entry: &BlameEntry,
6560 position: gpui::Point<Pixels>,
6561 ignore_timeout: bool,
6562 cx: &mut Context<Self>,
6563 ) {
6564 if let Some(state) = &mut self.inline_blame_popover {
6565 state.hide_task.take();
6566 } else {
6567 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6568 let blame_entry = blame_entry.clone();
6569 let show_task = cx.spawn(async move |editor, cx| {
6570 if !ignore_timeout {
6571 cx.background_executor()
6572 .timer(std::time::Duration::from_millis(blame_popover_delay))
6573 .await;
6574 }
6575 editor
6576 .update(cx, |editor, cx| {
6577 editor.inline_blame_popover_show_task.take();
6578 let Some(blame) = editor.blame.as_ref() else {
6579 return;
6580 };
6581 let blame = blame.read(cx);
6582 let details = blame.details_for_entry(&blame_entry);
6583 let markdown = cx.new(|cx| {
6584 Markdown::new(
6585 details
6586 .as_ref()
6587 .map(|message| message.message.clone())
6588 .unwrap_or_default(),
6589 None,
6590 None,
6591 cx,
6592 )
6593 });
6594 editor.inline_blame_popover = Some(InlineBlamePopover {
6595 position,
6596 hide_task: None,
6597 popover_bounds: None,
6598 popover_state: InlineBlamePopoverState {
6599 scroll_handle: ScrollHandle::new(),
6600 commit_message: details,
6601 markdown,
6602 },
6603 keyboard_grace: ignore_timeout,
6604 });
6605 cx.notify();
6606 })
6607 .ok();
6608 });
6609 self.inline_blame_popover_show_task = Some(show_task);
6610 }
6611 }
6612
6613 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6614 self.inline_blame_popover_show_task.take();
6615 if let Some(state) = &mut self.inline_blame_popover {
6616 let hide_task = cx.spawn(async move |editor, cx| {
6617 cx.background_executor()
6618 .timer(std::time::Duration::from_millis(100))
6619 .await;
6620 editor
6621 .update(cx, |editor, cx| {
6622 editor.inline_blame_popover.take();
6623 cx.notify();
6624 })
6625 .ok();
6626 });
6627 state.hide_task = Some(hide_task);
6628 }
6629 }
6630
6631 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6632 if self.pending_rename.is_some() {
6633 return None;
6634 }
6635
6636 let provider = self.semantics_provider.clone()?;
6637 let buffer = self.buffer.read(cx);
6638 let newest_selection = self.selections.newest_anchor().clone();
6639 let cursor_position = newest_selection.head();
6640 let (cursor_buffer, cursor_buffer_position) =
6641 buffer.text_anchor_for_position(cursor_position, cx)?;
6642 let (tail_buffer, tail_buffer_position) =
6643 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6644 if cursor_buffer != tail_buffer {
6645 return None;
6646 }
6647
6648 let snapshot = cursor_buffer.read(cx).snapshot();
6649 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6650 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6651 if start_word_range != end_word_range {
6652 self.document_highlights_task.take();
6653 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6654 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6655 return None;
6656 }
6657
6658 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6659 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6660 cx.background_executor()
6661 .timer(Duration::from_millis(debounce))
6662 .await;
6663
6664 let highlights = if let Some(highlights) = cx
6665 .update(|cx| {
6666 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6667 })
6668 .ok()
6669 .flatten()
6670 {
6671 highlights.await.log_err()
6672 } else {
6673 None
6674 };
6675
6676 if let Some(highlights) = highlights {
6677 this.update(cx, |this, cx| {
6678 if this.pending_rename.is_some() {
6679 return;
6680 }
6681
6682 let buffer_id = cursor_position.buffer_id;
6683 let buffer = this.buffer.read(cx);
6684 if !buffer
6685 .text_anchor_for_position(cursor_position, cx)
6686 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6687 {
6688 return;
6689 }
6690
6691 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6692 let mut write_ranges = Vec::new();
6693 let mut read_ranges = Vec::new();
6694 for highlight in highlights {
6695 for (excerpt_id, excerpt_range) in
6696 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6697 {
6698 let start = highlight
6699 .range
6700 .start
6701 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6702 let end = highlight
6703 .range
6704 .end
6705 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6706 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6707 continue;
6708 }
6709
6710 let range = Anchor {
6711 buffer_id,
6712 excerpt_id,
6713 text_anchor: start,
6714 diff_base_anchor: None,
6715 }..Anchor {
6716 buffer_id,
6717 excerpt_id,
6718 text_anchor: end,
6719 diff_base_anchor: None,
6720 };
6721 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6722 write_ranges.push(range);
6723 } else {
6724 read_ranges.push(range);
6725 }
6726 }
6727 }
6728
6729 this.highlight_background::<DocumentHighlightRead>(
6730 &read_ranges,
6731 |theme| theme.colors().editor_document_highlight_read_background,
6732 cx,
6733 );
6734 this.highlight_background::<DocumentHighlightWrite>(
6735 &write_ranges,
6736 |theme| theme.colors().editor_document_highlight_write_background,
6737 cx,
6738 );
6739 cx.notify();
6740 })
6741 .log_err();
6742 }
6743 }));
6744 None
6745 }
6746
6747 fn prepare_highlight_query_from_selection(
6748 &mut self,
6749 cx: &mut Context<Editor>,
6750 ) -> Option<(String, Range<Anchor>)> {
6751 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6752 return None;
6753 }
6754 if !EditorSettings::get_global(cx).selection_highlight {
6755 return None;
6756 }
6757 if self.selections.count() != 1 || self.selections.line_mode {
6758 return None;
6759 }
6760 let selection = self.selections.newest::<Point>(cx);
6761 if selection.is_empty() || selection.start.row != selection.end.row {
6762 return None;
6763 }
6764 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6765 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6766 let query = multi_buffer_snapshot
6767 .text_for_range(selection_anchor_range.clone())
6768 .collect::<String>();
6769 if query.trim().is_empty() {
6770 return None;
6771 }
6772 Some((query, selection_anchor_range))
6773 }
6774
6775 fn update_selection_occurrence_highlights(
6776 &mut self,
6777 query_text: String,
6778 query_range: Range<Anchor>,
6779 multi_buffer_range_to_query: Range<Point>,
6780 use_debounce: bool,
6781 window: &mut Window,
6782 cx: &mut Context<Editor>,
6783 ) -> Task<()> {
6784 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6785 cx.spawn_in(window, async move |editor, cx| {
6786 if use_debounce {
6787 cx.background_executor()
6788 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6789 .await;
6790 }
6791 let match_task = cx.background_spawn(async move {
6792 let buffer_ranges = multi_buffer_snapshot
6793 .range_to_buffer_ranges(multi_buffer_range_to_query)
6794 .into_iter()
6795 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6796 let mut match_ranges = Vec::new();
6797 let Ok(regex) = project::search::SearchQuery::text(
6798 query_text.clone(),
6799 false,
6800 false,
6801 false,
6802 Default::default(),
6803 Default::default(),
6804 false,
6805 None,
6806 ) else {
6807 return Vec::default();
6808 };
6809 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6810 match_ranges.extend(
6811 regex
6812 .search(&buffer_snapshot, Some(search_range.clone()))
6813 .await
6814 .into_iter()
6815 .filter_map(|match_range| {
6816 let match_start = buffer_snapshot
6817 .anchor_after(search_range.start + match_range.start);
6818 let match_end = buffer_snapshot
6819 .anchor_before(search_range.start + match_range.end);
6820 let match_anchor_range = Anchor::range_in_buffer(
6821 excerpt_id,
6822 buffer_snapshot.remote_id(),
6823 match_start..match_end,
6824 );
6825 (match_anchor_range != query_range).then_some(match_anchor_range)
6826 }),
6827 );
6828 }
6829 match_ranges
6830 });
6831 let match_ranges = match_task.await;
6832 editor
6833 .update_in(cx, |editor, _, cx| {
6834 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6835 if !match_ranges.is_empty() {
6836 editor.highlight_background::<SelectedTextHighlight>(
6837 &match_ranges,
6838 |theme| theme.colors().editor_document_highlight_bracket_background,
6839 cx,
6840 )
6841 }
6842 })
6843 .log_err();
6844 })
6845 }
6846
6847 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6848 struct NewlineFold;
6849 let type_id = std::any::TypeId::of::<NewlineFold>();
6850 if !self.mode.is_single_line() {
6851 return;
6852 }
6853 let snapshot = self.snapshot(window, cx);
6854 if snapshot.buffer_snapshot.max_point().row == 0 {
6855 return;
6856 }
6857 let task = cx.background_spawn(async move {
6858 let new_newlines = snapshot
6859 .buffer_chars_at(0)
6860 .filter_map(|(c, i)| {
6861 if c == '\n' {
6862 Some(
6863 snapshot.buffer_snapshot.anchor_after(i)
6864 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6865 )
6866 } else {
6867 None
6868 }
6869 })
6870 .collect::<Vec<_>>();
6871 let existing_newlines = snapshot
6872 .folds_in_range(0..snapshot.buffer_snapshot.len())
6873 .filter_map(|fold| {
6874 if fold.placeholder.type_tag == Some(type_id) {
6875 Some(fold.range.start..fold.range.end)
6876 } else {
6877 None
6878 }
6879 })
6880 .collect::<Vec<_>>();
6881
6882 (new_newlines, existing_newlines)
6883 });
6884 self.folding_newlines = cx.spawn(async move |this, cx| {
6885 let (new_newlines, existing_newlines) = task.await;
6886 if new_newlines == existing_newlines {
6887 return;
6888 }
6889 let placeholder = FoldPlaceholder {
6890 render: Arc::new(move |_, _, cx| {
6891 div()
6892 .bg(cx.theme().status().hint_background)
6893 .border_b_1()
6894 .size_full()
6895 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6896 .border_color(cx.theme().status().hint)
6897 .child("\\n")
6898 .into_any()
6899 }),
6900 constrain_width: false,
6901 merge_adjacent: false,
6902 type_tag: Some(type_id),
6903 };
6904 let creases = new_newlines
6905 .into_iter()
6906 .map(|range| Crease::simple(range, placeholder.clone()))
6907 .collect();
6908 this.update(cx, |this, cx| {
6909 this.display_map.update(cx, |display_map, cx| {
6910 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6911 display_map.fold(creases, cx);
6912 });
6913 })
6914 .ok();
6915 });
6916 }
6917
6918 fn refresh_selected_text_highlights(
6919 &mut self,
6920 on_buffer_edit: bool,
6921 window: &mut Window,
6922 cx: &mut Context<Editor>,
6923 ) {
6924 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6925 else {
6926 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6927 self.quick_selection_highlight_task.take();
6928 self.debounced_selection_highlight_task.take();
6929 return;
6930 };
6931 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6932 if on_buffer_edit
6933 || self
6934 .quick_selection_highlight_task
6935 .as_ref()
6936 .map_or(true, |(prev_anchor_range, _)| {
6937 prev_anchor_range != &query_range
6938 })
6939 {
6940 let multi_buffer_visible_start = self
6941 .scroll_manager
6942 .anchor()
6943 .anchor
6944 .to_point(&multi_buffer_snapshot);
6945 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6946 multi_buffer_visible_start
6947 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6948 Bias::Left,
6949 );
6950 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6951 self.quick_selection_highlight_task = Some((
6952 query_range.clone(),
6953 self.update_selection_occurrence_highlights(
6954 query_text.clone(),
6955 query_range.clone(),
6956 multi_buffer_visible_range,
6957 false,
6958 window,
6959 cx,
6960 ),
6961 ));
6962 }
6963 if on_buffer_edit
6964 || self
6965 .debounced_selection_highlight_task
6966 .as_ref()
6967 .map_or(true, |(prev_anchor_range, _)| {
6968 prev_anchor_range != &query_range
6969 })
6970 {
6971 let multi_buffer_start = multi_buffer_snapshot
6972 .anchor_before(0)
6973 .to_point(&multi_buffer_snapshot);
6974 let multi_buffer_end = multi_buffer_snapshot
6975 .anchor_after(multi_buffer_snapshot.len())
6976 .to_point(&multi_buffer_snapshot);
6977 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6978 self.debounced_selection_highlight_task = Some((
6979 query_range.clone(),
6980 self.update_selection_occurrence_highlights(
6981 query_text,
6982 query_range,
6983 multi_buffer_full_range,
6984 true,
6985 window,
6986 cx,
6987 ),
6988 ));
6989 }
6990 }
6991
6992 pub fn refresh_inline_completion(
6993 &mut self,
6994 debounce: bool,
6995 user_requested: bool,
6996 window: &mut Window,
6997 cx: &mut Context<Self>,
6998 ) -> Option<()> {
6999 let provider = self.edit_prediction_provider()?;
7000 let cursor = self.selections.newest_anchor().head();
7001 let (buffer, cursor_buffer_position) =
7002 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7003
7004 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7005 self.discard_inline_completion(false, cx);
7006 return None;
7007 }
7008
7009 if !user_requested
7010 && (!self.should_show_edit_predictions()
7011 || !self.is_focused(window)
7012 || buffer.read(cx).is_empty())
7013 {
7014 self.discard_inline_completion(false, cx);
7015 return None;
7016 }
7017
7018 self.update_visible_inline_completion(window, cx);
7019 provider.refresh(
7020 self.project.clone(),
7021 buffer,
7022 cursor_buffer_position,
7023 debounce,
7024 cx,
7025 );
7026 Some(())
7027 }
7028
7029 fn show_edit_predictions_in_menu(&self) -> bool {
7030 match self.edit_prediction_settings {
7031 EditPredictionSettings::Disabled => false,
7032 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7033 }
7034 }
7035
7036 pub fn edit_predictions_enabled(&self) -> bool {
7037 match self.edit_prediction_settings {
7038 EditPredictionSettings::Disabled => false,
7039 EditPredictionSettings::Enabled { .. } => true,
7040 }
7041 }
7042
7043 fn edit_prediction_requires_modifier(&self) -> bool {
7044 match self.edit_prediction_settings {
7045 EditPredictionSettings::Disabled => false,
7046 EditPredictionSettings::Enabled {
7047 preview_requires_modifier,
7048 ..
7049 } => preview_requires_modifier,
7050 }
7051 }
7052
7053 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7054 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7055 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7056 } else {
7057 let selection = self.selections.newest_anchor();
7058 let cursor = selection.head();
7059
7060 if let Some((buffer, cursor_buffer_position)) =
7061 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7062 {
7063 self.edit_prediction_settings =
7064 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7065 }
7066 }
7067 }
7068
7069 fn edit_prediction_settings_at_position(
7070 &self,
7071 buffer: &Entity<Buffer>,
7072 buffer_position: language::Anchor,
7073 cx: &App,
7074 ) -> EditPredictionSettings {
7075 if !self.mode.is_full()
7076 || !self.show_inline_completions_override.unwrap_or(true)
7077 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7078 {
7079 return EditPredictionSettings::Disabled;
7080 }
7081
7082 let buffer = buffer.read(cx);
7083
7084 let file = buffer.file();
7085
7086 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7087 return EditPredictionSettings::Disabled;
7088 };
7089
7090 let by_provider = matches!(
7091 self.menu_inline_completions_policy,
7092 MenuInlineCompletionsPolicy::ByProvider
7093 );
7094
7095 let show_in_menu = by_provider
7096 && self
7097 .edit_prediction_provider
7098 .as_ref()
7099 .map_or(false, |provider| {
7100 provider.provider.show_completions_in_menu()
7101 });
7102
7103 let preview_requires_modifier =
7104 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7105
7106 EditPredictionSettings::Enabled {
7107 show_in_menu,
7108 preview_requires_modifier,
7109 }
7110 }
7111
7112 fn should_show_edit_predictions(&self) -> bool {
7113 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7114 }
7115
7116 pub fn edit_prediction_preview_is_active(&self) -> bool {
7117 matches!(
7118 self.edit_prediction_preview,
7119 EditPredictionPreview::Active { .. }
7120 )
7121 }
7122
7123 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7124 let cursor = self.selections.newest_anchor().head();
7125 if let Some((buffer, cursor_position)) =
7126 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7127 {
7128 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7129 } else {
7130 false
7131 }
7132 }
7133
7134 pub fn supports_minimap(&self, cx: &App) -> bool {
7135 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7136 }
7137
7138 fn edit_predictions_enabled_in_buffer(
7139 &self,
7140 buffer: &Entity<Buffer>,
7141 buffer_position: language::Anchor,
7142 cx: &App,
7143 ) -> bool {
7144 maybe!({
7145 if self.read_only(cx) {
7146 return Some(false);
7147 }
7148 let provider = self.edit_prediction_provider()?;
7149 if !provider.is_enabled(&buffer, buffer_position, cx) {
7150 return Some(false);
7151 }
7152 let buffer = buffer.read(cx);
7153 let Some(file) = buffer.file() else {
7154 return Some(true);
7155 };
7156 let settings = all_language_settings(Some(file), cx);
7157 Some(settings.edit_predictions_enabled_for_file(file, cx))
7158 })
7159 .unwrap_or(false)
7160 }
7161
7162 fn cycle_inline_completion(
7163 &mut self,
7164 direction: Direction,
7165 window: &mut Window,
7166 cx: &mut Context<Self>,
7167 ) -> Option<()> {
7168 let provider = self.edit_prediction_provider()?;
7169 let cursor = self.selections.newest_anchor().head();
7170 let (buffer, cursor_buffer_position) =
7171 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7172 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7173 return None;
7174 }
7175
7176 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7177 self.update_visible_inline_completion(window, cx);
7178
7179 Some(())
7180 }
7181
7182 pub fn show_inline_completion(
7183 &mut self,
7184 _: &ShowEditPrediction,
7185 window: &mut Window,
7186 cx: &mut Context<Self>,
7187 ) {
7188 if !self.has_active_inline_completion() {
7189 self.refresh_inline_completion(false, true, window, cx);
7190 return;
7191 }
7192
7193 self.update_visible_inline_completion(window, cx);
7194 }
7195
7196 pub fn display_cursor_names(
7197 &mut self,
7198 _: &DisplayCursorNames,
7199 window: &mut Window,
7200 cx: &mut Context<Self>,
7201 ) {
7202 self.show_cursor_names(window, cx);
7203 }
7204
7205 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7206 self.show_cursor_names = true;
7207 cx.notify();
7208 cx.spawn_in(window, async move |this, cx| {
7209 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7210 this.update(cx, |this, cx| {
7211 this.show_cursor_names = false;
7212 cx.notify()
7213 })
7214 .ok()
7215 })
7216 .detach();
7217 }
7218
7219 pub fn next_edit_prediction(
7220 &mut self,
7221 _: &NextEditPrediction,
7222 window: &mut Window,
7223 cx: &mut Context<Self>,
7224 ) {
7225 if self.has_active_inline_completion() {
7226 self.cycle_inline_completion(Direction::Next, window, cx);
7227 } else {
7228 let is_copilot_disabled = self
7229 .refresh_inline_completion(false, true, window, cx)
7230 .is_none();
7231 if is_copilot_disabled {
7232 cx.propagate();
7233 }
7234 }
7235 }
7236
7237 pub fn previous_edit_prediction(
7238 &mut self,
7239 _: &PreviousEditPrediction,
7240 window: &mut Window,
7241 cx: &mut Context<Self>,
7242 ) {
7243 if self.has_active_inline_completion() {
7244 self.cycle_inline_completion(Direction::Prev, window, cx);
7245 } else {
7246 let is_copilot_disabled = self
7247 .refresh_inline_completion(false, true, window, cx)
7248 .is_none();
7249 if is_copilot_disabled {
7250 cx.propagate();
7251 }
7252 }
7253 }
7254
7255 pub fn accept_edit_prediction(
7256 &mut self,
7257 _: &AcceptEditPrediction,
7258 window: &mut Window,
7259 cx: &mut Context<Self>,
7260 ) {
7261 if self.show_edit_predictions_in_menu() {
7262 self.hide_context_menu(window, cx);
7263 }
7264
7265 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7266 return;
7267 };
7268
7269 self.report_inline_completion_event(
7270 active_inline_completion.completion_id.clone(),
7271 true,
7272 cx,
7273 );
7274
7275 match &active_inline_completion.completion {
7276 InlineCompletion::Move { target, .. } => {
7277 let target = *target;
7278
7279 if let Some(position_map) = &self.last_position_map {
7280 if position_map
7281 .visible_row_range
7282 .contains(&target.to_display_point(&position_map.snapshot).row())
7283 || !self.edit_prediction_requires_modifier()
7284 {
7285 self.unfold_ranges(&[target..target], true, false, cx);
7286 // Note that this is also done in vim's handler of the Tab action.
7287 self.change_selections(
7288 SelectionEffects::scroll(Autoscroll::newest()),
7289 window,
7290 cx,
7291 |selections| {
7292 selections.select_anchor_ranges([target..target]);
7293 },
7294 );
7295 self.clear_row_highlights::<EditPredictionPreview>();
7296
7297 self.edit_prediction_preview
7298 .set_previous_scroll_position(None);
7299 } else {
7300 self.edit_prediction_preview
7301 .set_previous_scroll_position(Some(
7302 position_map.snapshot.scroll_anchor,
7303 ));
7304
7305 self.highlight_rows::<EditPredictionPreview>(
7306 target..target,
7307 cx.theme().colors().editor_highlighted_line_background,
7308 RowHighlightOptions {
7309 autoscroll: true,
7310 ..Default::default()
7311 },
7312 cx,
7313 );
7314 self.request_autoscroll(Autoscroll::fit(), cx);
7315 }
7316 }
7317 }
7318 InlineCompletion::Edit { edits, .. } => {
7319 if let Some(provider) = self.edit_prediction_provider() {
7320 provider.accept(cx);
7321 }
7322
7323 // Store the transaction ID and selections before applying the edit
7324 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7325
7326 let snapshot = self.buffer.read(cx).snapshot(cx);
7327 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7328
7329 self.buffer.update(cx, |buffer, cx| {
7330 buffer.edit(edits.iter().cloned(), None, cx)
7331 });
7332
7333 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7334 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7335 });
7336
7337 let selections = self.selections.disjoint_anchors();
7338 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7339 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7340 if has_new_transaction {
7341 self.selection_history
7342 .insert_transaction(transaction_id_now, selections);
7343 }
7344 }
7345
7346 self.update_visible_inline_completion(window, cx);
7347 if self.active_inline_completion.is_none() {
7348 self.refresh_inline_completion(true, true, window, cx);
7349 }
7350
7351 cx.notify();
7352 }
7353 }
7354
7355 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7356 }
7357
7358 pub fn accept_partial_inline_completion(
7359 &mut self,
7360 _: &AcceptPartialEditPrediction,
7361 window: &mut Window,
7362 cx: &mut Context<Self>,
7363 ) {
7364 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7365 return;
7366 };
7367 if self.selections.count() != 1 {
7368 return;
7369 }
7370
7371 self.report_inline_completion_event(
7372 active_inline_completion.completion_id.clone(),
7373 true,
7374 cx,
7375 );
7376
7377 match &active_inline_completion.completion {
7378 InlineCompletion::Move { target, .. } => {
7379 let target = *target;
7380 self.change_selections(
7381 SelectionEffects::scroll(Autoscroll::newest()),
7382 window,
7383 cx,
7384 |selections| {
7385 selections.select_anchor_ranges([target..target]);
7386 },
7387 );
7388 }
7389 InlineCompletion::Edit { edits, .. } => {
7390 // Find an insertion that starts at the cursor position.
7391 let snapshot = self.buffer.read(cx).snapshot(cx);
7392 let cursor_offset = self.selections.newest::<usize>(cx).head();
7393 let insertion = edits.iter().find_map(|(range, text)| {
7394 let range = range.to_offset(&snapshot);
7395 if range.is_empty() && range.start == cursor_offset {
7396 Some(text)
7397 } else {
7398 None
7399 }
7400 });
7401
7402 if let Some(text) = insertion {
7403 let mut partial_completion = text
7404 .chars()
7405 .by_ref()
7406 .take_while(|c| c.is_alphabetic())
7407 .collect::<String>();
7408 if partial_completion.is_empty() {
7409 partial_completion = text
7410 .chars()
7411 .by_ref()
7412 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7413 .collect::<String>();
7414 }
7415
7416 cx.emit(EditorEvent::InputHandled {
7417 utf16_range_to_replace: None,
7418 text: partial_completion.clone().into(),
7419 });
7420
7421 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7422
7423 self.refresh_inline_completion(true, true, window, cx);
7424 cx.notify();
7425 } else {
7426 self.accept_edit_prediction(&Default::default(), window, cx);
7427 }
7428 }
7429 }
7430 }
7431
7432 fn discard_inline_completion(
7433 &mut self,
7434 should_report_inline_completion_event: bool,
7435 cx: &mut Context<Self>,
7436 ) -> bool {
7437 if should_report_inline_completion_event {
7438 let completion_id = self
7439 .active_inline_completion
7440 .as_ref()
7441 .and_then(|active_completion| active_completion.completion_id.clone());
7442
7443 self.report_inline_completion_event(completion_id, false, cx);
7444 }
7445
7446 if let Some(provider) = self.edit_prediction_provider() {
7447 provider.discard(cx);
7448 }
7449
7450 self.take_active_inline_completion(cx)
7451 }
7452
7453 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7454 let Some(provider) = self.edit_prediction_provider() else {
7455 return;
7456 };
7457
7458 let Some((_, buffer, _)) = self
7459 .buffer
7460 .read(cx)
7461 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7462 else {
7463 return;
7464 };
7465
7466 let extension = buffer
7467 .read(cx)
7468 .file()
7469 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7470
7471 let event_type = match accepted {
7472 true => "Edit Prediction Accepted",
7473 false => "Edit Prediction Discarded",
7474 };
7475 telemetry::event!(
7476 event_type,
7477 provider = provider.name(),
7478 prediction_id = id,
7479 suggestion_accepted = accepted,
7480 file_extension = extension,
7481 );
7482 }
7483
7484 pub fn has_active_inline_completion(&self) -> bool {
7485 self.active_inline_completion.is_some()
7486 }
7487
7488 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7489 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7490 return false;
7491 };
7492
7493 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7494 self.clear_highlights::<InlineCompletionHighlight>(cx);
7495 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7496 true
7497 }
7498
7499 /// Returns true when we're displaying the edit prediction popover below the cursor
7500 /// like we are not previewing and the LSP autocomplete menu is visible
7501 /// or we are in `when_holding_modifier` mode.
7502 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7503 if self.edit_prediction_preview_is_active()
7504 || !self.show_edit_predictions_in_menu()
7505 || !self.edit_predictions_enabled()
7506 {
7507 return false;
7508 }
7509
7510 if self.has_visible_completions_menu() {
7511 return true;
7512 }
7513
7514 has_completion && self.edit_prediction_requires_modifier()
7515 }
7516
7517 fn handle_modifiers_changed(
7518 &mut self,
7519 modifiers: Modifiers,
7520 position_map: &PositionMap,
7521 window: &mut Window,
7522 cx: &mut Context<Self>,
7523 ) {
7524 if self.show_edit_predictions_in_menu() {
7525 self.update_edit_prediction_preview(&modifiers, window, cx);
7526 }
7527
7528 self.update_selection_mode(&modifiers, position_map, window, cx);
7529
7530 let mouse_position = window.mouse_position();
7531 if !position_map.text_hitbox.is_hovered(window) {
7532 return;
7533 }
7534
7535 self.update_hovered_link(
7536 position_map.point_for_position(mouse_position),
7537 &position_map.snapshot,
7538 modifiers,
7539 window,
7540 cx,
7541 )
7542 }
7543
7544 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7545 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7546 if invert {
7547 match multi_cursor_setting {
7548 MultiCursorModifier::Alt => modifiers.alt,
7549 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7550 }
7551 } else {
7552 match multi_cursor_setting {
7553 MultiCursorModifier::Alt => modifiers.secondary(),
7554 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7555 }
7556 }
7557 }
7558
7559 fn columnar_selection_mode(
7560 modifiers: &Modifiers,
7561 cx: &mut Context<Self>,
7562 ) -> Option<ColumnarMode> {
7563 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7564 if Self::multi_cursor_modifier(false, modifiers, cx) {
7565 Some(ColumnarMode::FromMouse)
7566 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7567 Some(ColumnarMode::FromSelection)
7568 } else {
7569 None
7570 }
7571 } else {
7572 None
7573 }
7574 }
7575
7576 fn update_selection_mode(
7577 &mut self,
7578 modifiers: &Modifiers,
7579 position_map: &PositionMap,
7580 window: &mut Window,
7581 cx: &mut Context<Self>,
7582 ) {
7583 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7584 return;
7585 };
7586 if self.selections.pending.is_none() {
7587 return;
7588 }
7589
7590 let mouse_position = window.mouse_position();
7591 let point_for_position = position_map.point_for_position(mouse_position);
7592 let position = point_for_position.previous_valid;
7593
7594 self.select(
7595 SelectPhase::BeginColumnar {
7596 position,
7597 reset: false,
7598 mode,
7599 goal_column: point_for_position.exact_unclipped.column(),
7600 },
7601 window,
7602 cx,
7603 );
7604 }
7605
7606 fn update_edit_prediction_preview(
7607 &mut self,
7608 modifiers: &Modifiers,
7609 window: &mut Window,
7610 cx: &mut Context<Self>,
7611 ) {
7612 let mut modifiers_held = false;
7613 if let Some(accept_keystroke) = self
7614 .accept_edit_prediction_keybind(false, window, cx)
7615 .keystroke()
7616 {
7617 modifiers_held = modifiers_held
7618 || (&accept_keystroke.modifiers == modifiers
7619 && accept_keystroke.modifiers.modified());
7620 };
7621 if let Some(accept_partial_keystroke) = self
7622 .accept_edit_prediction_keybind(true, window, cx)
7623 .keystroke()
7624 {
7625 modifiers_held = modifiers_held
7626 || (&accept_partial_keystroke.modifiers == modifiers
7627 && accept_partial_keystroke.modifiers.modified());
7628 }
7629
7630 if modifiers_held {
7631 if matches!(
7632 self.edit_prediction_preview,
7633 EditPredictionPreview::Inactive { .. }
7634 ) {
7635 self.edit_prediction_preview = EditPredictionPreview::Active {
7636 previous_scroll_position: None,
7637 since: Instant::now(),
7638 };
7639
7640 self.update_visible_inline_completion(window, cx);
7641 cx.notify();
7642 }
7643 } else if let EditPredictionPreview::Active {
7644 previous_scroll_position,
7645 since,
7646 } = self.edit_prediction_preview
7647 {
7648 if let (Some(previous_scroll_position), Some(position_map)) =
7649 (previous_scroll_position, self.last_position_map.as_ref())
7650 {
7651 self.set_scroll_position(
7652 previous_scroll_position
7653 .scroll_position(&position_map.snapshot.display_snapshot),
7654 window,
7655 cx,
7656 );
7657 }
7658
7659 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7660 released_too_fast: since.elapsed() < Duration::from_millis(200),
7661 };
7662 self.clear_row_highlights::<EditPredictionPreview>();
7663 self.update_visible_inline_completion(window, cx);
7664 cx.notify();
7665 }
7666 }
7667
7668 fn update_visible_inline_completion(
7669 &mut self,
7670 _window: &mut Window,
7671 cx: &mut Context<Self>,
7672 ) -> Option<()> {
7673 let selection = self.selections.newest_anchor();
7674 let cursor = selection.head();
7675 let multibuffer = self.buffer.read(cx).snapshot(cx);
7676 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7677 let excerpt_id = cursor.excerpt_id;
7678
7679 let show_in_menu = self.show_edit_predictions_in_menu();
7680 let completions_menu_has_precedence = !show_in_menu
7681 && (self.context_menu.borrow().is_some()
7682 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7683
7684 if completions_menu_has_precedence
7685 || !offset_selection.is_empty()
7686 || self
7687 .active_inline_completion
7688 .as_ref()
7689 .map_or(false, |completion| {
7690 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7691 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7692 !invalidation_range.contains(&offset_selection.head())
7693 })
7694 {
7695 self.discard_inline_completion(false, cx);
7696 return None;
7697 }
7698
7699 self.take_active_inline_completion(cx);
7700 let Some(provider) = self.edit_prediction_provider() else {
7701 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7702 return None;
7703 };
7704
7705 let (buffer, cursor_buffer_position) =
7706 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7707
7708 self.edit_prediction_settings =
7709 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7710
7711 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7712
7713 if self.edit_prediction_indent_conflict {
7714 let cursor_point = cursor.to_point(&multibuffer);
7715
7716 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7717
7718 if let Some((_, indent)) = indents.iter().next() {
7719 if indent.len == cursor_point.column {
7720 self.edit_prediction_indent_conflict = false;
7721 }
7722 }
7723 }
7724
7725 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7726 let edits = inline_completion
7727 .edits
7728 .into_iter()
7729 .flat_map(|(range, new_text)| {
7730 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7731 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7732 Some((start..end, new_text))
7733 })
7734 .collect::<Vec<_>>();
7735 if edits.is_empty() {
7736 return None;
7737 }
7738
7739 let first_edit_start = edits.first().unwrap().0.start;
7740 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7741 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7742
7743 let last_edit_end = edits.last().unwrap().0.end;
7744 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7745 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7746
7747 let cursor_row = cursor.to_point(&multibuffer).row;
7748
7749 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7750
7751 let mut inlay_ids = Vec::new();
7752 let invalidation_row_range;
7753 let move_invalidation_row_range = if cursor_row < edit_start_row {
7754 Some(cursor_row..edit_end_row)
7755 } else if cursor_row > edit_end_row {
7756 Some(edit_start_row..cursor_row)
7757 } else {
7758 None
7759 };
7760 let is_move =
7761 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7762 let completion = if is_move {
7763 invalidation_row_range =
7764 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7765 let target = first_edit_start;
7766 InlineCompletion::Move { target, snapshot }
7767 } else {
7768 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7769 && !self.inline_completions_hidden_for_vim_mode;
7770
7771 if show_completions_in_buffer {
7772 if edits
7773 .iter()
7774 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7775 {
7776 let mut inlays = Vec::new();
7777 for (range, new_text) in &edits {
7778 let inlay = Inlay::inline_completion(
7779 post_inc(&mut self.next_inlay_id),
7780 range.start,
7781 new_text.as_str(),
7782 );
7783 inlay_ids.push(inlay.id);
7784 inlays.push(inlay);
7785 }
7786
7787 self.splice_inlays(&[], inlays, cx);
7788 } else {
7789 let background_color = cx.theme().status().deleted_background;
7790 self.highlight_text::<InlineCompletionHighlight>(
7791 edits.iter().map(|(range, _)| range.clone()).collect(),
7792 HighlightStyle {
7793 background_color: Some(background_color),
7794 ..Default::default()
7795 },
7796 cx,
7797 );
7798 }
7799 }
7800
7801 invalidation_row_range = edit_start_row..edit_end_row;
7802
7803 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7804 if provider.show_tab_accept_marker() {
7805 EditDisplayMode::TabAccept
7806 } else {
7807 EditDisplayMode::Inline
7808 }
7809 } else {
7810 EditDisplayMode::DiffPopover
7811 };
7812
7813 InlineCompletion::Edit {
7814 edits,
7815 edit_preview: inline_completion.edit_preview,
7816 display_mode,
7817 snapshot,
7818 }
7819 };
7820
7821 let invalidation_range = multibuffer
7822 .anchor_before(Point::new(invalidation_row_range.start, 0))
7823 ..multibuffer.anchor_after(Point::new(
7824 invalidation_row_range.end,
7825 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7826 ));
7827
7828 self.stale_inline_completion_in_menu = None;
7829 self.active_inline_completion = Some(InlineCompletionState {
7830 inlay_ids,
7831 completion,
7832 completion_id: inline_completion.id,
7833 invalidation_range,
7834 });
7835
7836 cx.notify();
7837
7838 Some(())
7839 }
7840
7841 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7842 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7843 }
7844
7845 fn clear_tasks(&mut self) {
7846 self.tasks.clear()
7847 }
7848
7849 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7850 if self.tasks.insert(key, value).is_some() {
7851 // This case should hopefully be rare, but just in case...
7852 log::error!(
7853 "multiple different run targets found on a single line, only the last target will be rendered"
7854 )
7855 }
7856 }
7857
7858 /// Get all display points of breakpoints that will be rendered within editor
7859 ///
7860 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7861 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7862 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7863 fn active_breakpoints(
7864 &self,
7865 range: Range<DisplayRow>,
7866 window: &mut Window,
7867 cx: &mut Context<Self>,
7868 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7869 let mut breakpoint_display_points = HashMap::default();
7870
7871 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7872 return breakpoint_display_points;
7873 };
7874
7875 let snapshot = self.snapshot(window, cx);
7876
7877 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7878 let Some(project) = self.project.as_ref() else {
7879 return breakpoint_display_points;
7880 };
7881
7882 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7883 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7884
7885 for (buffer_snapshot, range, excerpt_id) in
7886 multi_buffer_snapshot.range_to_buffer_ranges(range)
7887 {
7888 let Some(buffer) = project
7889 .read(cx)
7890 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7891 else {
7892 continue;
7893 };
7894 let breakpoints = breakpoint_store.read(cx).breakpoints(
7895 &buffer,
7896 Some(
7897 buffer_snapshot.anchor_before(range.start)
7898 ..buffer_snapshot.anchor_after(range.end),
7899 ),
7900 buffer_snapshot,
7901 cx,
7902 );
7903 for (breakpoint, state) in breakpoints {
7904 let multi_buffer_anchor =
7905 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7906 let position = multi_buffer_anchor
7907 .to_point(&multi_buffer_snapshot)
7908 .to_display_point(&snapshot);
7909
7910 breakpoint_display_points.insert(
7911 position.row(),
7912 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7913 );
7914 }
7915 }
7916
7917 breakpoint_display_points
7918 }
7919
7920 fn breakpoint_context_menu(
7921 &self,
7922 anchor: Anchor,
7923 window: &mut Window,
7924 cx: &mut Context<Self>,
7925 ) -> Entity<ui::ContextMenu> {
7926 let weak_editor = cx.weak_entity();
7927 let focus_handle = self.focus_handle(cx);
7928
7929 let row = self
7930 .buffer
7931 .read(cx)
7932 .snapshot(cx)
7933 .summary_for_anchor::<Point>(&anchor)
7934 .row;
7935
7936 let breakpoint = self
7937 .breakpoint_at_row(row, window, cx)
7938 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7939
7940 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7941 "Edit Log Breakpoint"
7942 } else {
7943 "Set Log Breakpoint"
7944 };
7945
7946 let condition_breakpoint_msg = if breakpoint
7947 .as_ref()
7948 .is_some_and(|bp| bp.1.condition.is_some())
7949 {
7950 "Edit Condition Breakpoint"
7951 } else {
7952 "Set Condition Breakpoint"
7953 };
7954
7955 let hit_condition_breakpoint_msg = if breakpoint
7956 .as_ref()
7957 .is_some_and(|bp| bp.1.hit_condition.is_some())
7958 {
7959 "Edit Hit Condition Breakpoint"
7960 } else {
7961 "Set Hit Condition Breakpoint"
7962 };
7963
7964 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7965 "Unset Breakpoint"
7966 } else {
7967 "Set Breakpoint"
7968 };
7969
7970 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7971
7972 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7973 BreakpointState::Enabled => Some("Disable"),
7974 BreakpointState::Disabled => Some("Enable"),
7975 });
7976
7977 let (anchor, breakpoint) =
7978 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7979
7980 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7981 menu.on_blur_subscription(Subscription::new(|| {}))
7982 .context(focus_handle)
7983 .when(run_to_cursor, |this| {
7984 let weak_editor = weak_editor.clone();
7985 this.entry("Run to cursor", None, move |window, cx| {
7986 weak_editor
7987 .update(cx, |editor, cx| {
7988 editor.change_selections(
7989 SelectionEffects::no_scroll(),
7990 window,
7991 cx,
7992 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7993 );
7994 })
7995 .ok();
7996
7997 window.dispatch_action(Box::new(RunToCursor), cx);
7998 })
7999 .separator()
8000 })
8001 .when_some(toggle_state_msg, |this, msg| {
8002 this.entry(msg, None, {
8003 let weak_editor = weak_editor.clone();
8004 let breakpoint = breakpoint.clone();
8005 move |_window, cx| {
8006 weak_editor
8007 .update(cx, |this, cx| {
8008 this.edit_breakpoint_at_anchor(
8009 anchor,
8010 breakpoint.as_ref().clone(),
8011 BreakpointEditAction::InvertState,
8012 cx,
8013 );
8014 })
8015 .log_err();
8016 }
8017 })
8018 })
8019 .entry(set_breakpoint_msg, None, {
8020 let weak_editor = weak_editor.clone();
8021 let breakpoint = breakpoint.clone();
8022 move |_window, cx| {
8023 weak_editor
8024 .update(cx, |this, cx| {
8025 this.edit_breakpoint_at_anchor(
8026 anchor,
8027 breakpoint.as_ref().clone(),
8028 BreakpointEditAction::Toggle,
8029 cx,
8030 );
8031 })
8032 .log_err();
8033 }
8034 })
8035 .entry(log_breakpoint_msg, None, {
8036 let breakpoint = breakpoint.clone();
8037 let weak_editor = weak_editor.clone();
8038 move |window, cx| {
8039 weak_editor
8040 .update(cx, |this, cx| {
8041 this.add_edit_breakpoint_block(
8042 anchor,
8043 breakpoint.as_ref(),
8044 BreakpointPromptEditAction::Log,
8045 window,
8046 cx,
8047 );
8048 })
8049 .log_err();
8050 }
8051 })
8052 .entry(condition_breakpoint_msg, None, {
8053 let breakpoint = breakpoint.clone();
8054 let weak_editor = weak_editor.clone();
8055 move |window, cx| {
8056 weak_editor
8057 .update(cx, |this, cx| {
8058 this.add_edit_breakpoint_block(
8059 anchor,
8060 breakpoint.as_ref(),
8061 BreakpointPromptEditAction::Condition,
8062 window,
8063 cx,
8064 );
8065 })
8066 .log_err();
8067 }
8068 })
8069 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8070 weak_editor
8071 .update(cx, |this, cx| {
8072 this.add_edit_breakpoint_block(
8073 anchor,
8074 breakpoint.as_ref(),
8075 BreakpointPromptEditAction::HitCondition,
8076 window,
8077 cx,
8078 );
8079 })
8080 .log_err();
8081 })
8082 })
8083 }
8084
8085 fn render_breakpoint(
8086 &self,
8087 position: Anchor,
8088 row: DisplayRow,
8089 breakpoint: &Breakpoint,
8090 state: Option<BreakpointSessionState>,
8091 cx: &mut Context<Self>,
8092 ) -> IconButton {
8093 let is_rejected = state.is_some_and(|s| !s.verified);
8094 // Is it a breakpoint that shows up when hovering over gutter?
8095 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8096 (false, false),
8097 |PhantomBreakpointIndicator {
8098 is_active,
8099 display_row,
8100 collides_with_existing_breakpoint,
8101 }| {
8102 (
8103 is_active && display_row == row,
8104 collides_with_existing_breakpoint,
8105 )
8106 },
8107 );
8108
8109 let (color, icon) = {
8110 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8111 (false, false) => ui::IconName::DebugBreakpoint,
8112 (true, false) => ui::IconName::DebugLogBreakpoint,
8113 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8114 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8115 };
8116
8117 let color = if is_phantom {
8118 Color::Hint
8119 } else if is_rejected {
8120 Color::Disabled
8121 } else {
8122 Color::Debugger
8123 };
8124
8125 (color, icon)
8126 };
8127
8128 let breakpoint = Arc::from(breakpoint.clone());
8129
8130 let alt_as_text = gpui::Keystroke {
8131 modifiers: Modifiers::secondary_key(),
8132 ..Default::default()
8133 };
8134 let primary_action_text = if breakpoint.is_disabled() {
8135 "Enable breakpoint"
8136 } else if is_phantom && !collides_with_existing {
8137 "Set breakpoint"
8138 } else {
8139 "Unset breakpoint"
8140 };
8141 let focus_handle = self.focus_handle.clone();
8142
8143 let meta = if is_rejected {
8144 SharedString::from("No executable code is associated with this line.")
8145 } else if collides_with_existing && !breakpoint.is_disabled() {
8146 SharedString::from(format!(
8147 "{alt_as_text}-click to disable,\nright-click for more options."
8148 ))
8149 } else {
8150 SharedString::from("Right-click for more options.")
8151 };
8152 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8153 .icon_size(IconSize::XSmall)
8154 .size(ui::ButtonSize::None)
8155 .when(is_rejected, |this| {
8156 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8157 })
8158 .icon_color(color)
8159 .style(ButtonStyle::Transparent)
8160 .on_click(cx.listener({
8161 let breakpoint = breakpoint.clone();
8162
8163 move |editor, event: &ClickEvent, window, cx| {
8164 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8165 BreakpointEditAction::InvertState
8166 } else {
8167 BreakpointEditAction::Toggle
8168 };
8169
8170 window.focus(&editor.focus_handle(cx));
8171 editor.edit_breakpoint_at_anchor(
8172 position,
8173 breakpoint.as_ref().clone(),
8174 edit_action,
8175 cx,
8176 );
8177 }
8178 }))
8179 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8180 editor.set_breakpoint_context_menu(
8181 row,
8182 Some(position),
8183 event.down.position,
8184 window,
8185 cx,
8186 );
8187 }))
8188 .tooltip(move |window, cx| {
8189 Tooltip::with_meta_in(
8190 primary_action_text,
8191 Some(&ToggleBreakpoint),
8192 meta.clone(),
8193 &focus_handle,
8194 window,
8195 cx,
8196 )
8197 })
8198 }
8199
8200 fn build_tasks_context(
8201 project: &Entity<Project>,
8202 buffer: &Entity<Buffer>,
8203 buffer_row: u32,
8204 tasks: &Arc<RunnableTasks>,
8205 cx: &mut Context<Self>,
8206 ) -> Task<Option<task::TaskContext>> {
8207 let position = Point::new(buffer_row, tasks.column);
8208 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8209 let location = Location {
8210 buffer: buffer.clone(),
8211 range: range_start..range_start,
8212 };
8213 // Fill in the environmental variables from the tree-sitter captures
8214 let mut captured_task_variables = TaskVariables::default();
8215 for (capture_name, value) in tasks.extra_variables.clone() {
8216 captured_task_variables.insert(
8217 task::VariableName::Custom(capture_name.into()),
8218 value.clone(),
8219 );
8220 }
8221 project.update(cx, |project, cx| {
8222 project.task_store().update(cx, |task_store, cx| {
8223 task_store.task_context_for_location(captured_task_variables, location, cx)
8224 })
8225 })
8226 }
8227
8228 pub fn spawn_nearest_task(
8229 &mut self,
8230 action: &SpawnNearestTask,
8231 window: &mut Window,
8232 cx: &mut Context<Self>,
8233 ) {
8234 let Some((workspace, _)) = self.workspace.clone() else {
8235 return;
8236 };
8237 let Some(project) = self.project.clone() else {
8238 return;
8239 };
8240
8241 // Try to find a closest, enclosing node using tree-sitter that has a task
8242 let Some((buffer, buffer_row, tasks)) = self
8243 .find_enclosing_node_task(cx)
8244 // Or find the task that's closest in row-distance.
8245 .or_else(|| self.find_closest_task(cx))
8246 else {
8247 return;
8248 };
8249
8250 let reveal_strategy = action.reveal;
8251 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8252 cx.spawn_in(window, async move |_, cx| {
8253 let context = task_context.await?;
8254 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8255
8256 let resolved = &mut resolved_task.resolved;
8257 resolved.reveal = reveal_strategy;
8258
8259 workspace
8260 .update_in(cx, |workspace, window, cx| {
8261 workspace.schedule_resolved_task(
8262 task_source_kind,
8263 resolved_task,
8264 false,
8265 window,
8266 cx,
8267 );
8268 })
8269 .ok()
8270 })
8271 .detach();
8272 }
8273
8274 fn find_closest_task(
8275 &mut self,
8276 cx: &mut Context<Self>,
8277 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8278 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8279
8280 let ((buffer_id, row), tasks) = self
8281 .tasks
8282 .iter()
8283 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8284
8285 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8286 let tasks = Arc::new(tasks.to_owned());
8287 Some((buffer, *row, tasks))
8288 }
8289
8290 fn find_enclosing_node_task(
8291 &mut self,
8292 cx: &mut Context<Self>,
8293 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8294 let snapshot = self.buffer.read(cx).snapshot(cx);
8295 let offset = self.selections.newest::<usize>(cx).head();
8296 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8297 let buffer_id = excerpt.buffer().remote_id();
8298
8299 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8300 let mut cursor = layer.node().walk();
8301
8302 while cursor.goto_first_child_for_byte(offset).is_some() {
8303 if cursor.node().end_byte() == offset {
8304 cursor.goto_next_sibling();
8305 }
8306 }
8307
8308 // Ascend to the smallest ancestor that contains the range and has a task.
8309 loop {
8310 let node = cursor.node();
8311 let node_range = node.byte_range();
8312 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8313
8314 // Check if this node contains our offset
8315 if node_range.start <= offset && node_range.end >= offset {
8316 // If it contains offset, check for task
8317 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8318 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8319 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8320 }
8321 }
8322
8323 if !cursor.goto_parent() {
8324 break;
8325 }
8326 }
8327 None
8328 }
8329
8330 fn render_run_indicator(
8331 &self,
8332 _style: &EditorStyle,
8333 is_active: bool,
8334 row: DisplayRow,
8335 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8336 cx: &mut Context<Self>,
8337 ) -> IconButton {
8338 let color = Color::Muted;
8339 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8340
8341 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8342 .shape(ui::IconButtonShape::Square)
8343 .icon_size(IconSize::XSmall)
8344 .icon_color(color)
8345 .toggle_state(is_active)
8346 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8347 let quick_launch = e.down.button == MouseButton::Left;
8348 window.focus(&editor.focus_handle(cx));
8349 editor.toggle_code_actions(
8350 &ToggleCodeActions {
8351 deployed_from: Some(CodeActionSource::RunMenu(row)),
8352 quick_launch,
8353 },
8354 window,
8355 cx,
8356 );
8357 }))
8358 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8359 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8360 }))
8361 }
8362
8363 pub fn context_menu_visible(&self) -> bool {
8364 !self.edit_prediction_preview_is_active()
8365 && self
8366 .context_menu
8367 .borrow()
8368 .as_ref()
8369 .map_or(false, |menu| menu.visible())
8370 }
8371
8372 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8373 self.context_menu
8374 .borrow()
8375 .as_ref()
8376 .map(|menu| menu.origin())
8377 }
8378
8379 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8380 self.context_menu_options = Some(options);
8381 }
8382
8383 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8384 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8385
8386 fn render_edit_prediction_popover(
8387 &mut self,
8388 text_bounds: &Bounds<Pixels>,
8389 content_origin: gpui::Point<Pixels>,
8390 right_margin: Pixels,
8391 editor_snapshot: &EditorSnapshot,
8392 visible_row_range: Range<DisplayRow>,
8393 scroll_top: f32,
8394 scroll_bottom: f32,
8395 line_layouts: &[LineWithInvisibles],
8396 line_height: Pixels,
8397 scroll_pixel_position: gpui::Point<Pixels>,
8398 newest_selection_head: Option<DisplayPoint>,
8399 editor_width: Pixels,
8400 style: &EditorStyle,
8401 window: &mut Window,
8402 cx: &mut App,
8403 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8404 if self.mode().is_minimap() {
8405 return None;
8406 }
8407 let active_inline_completion = self.active_inline_completion.as_ref()?;
8408
8409 if self.edit_prediction_visible_in_cursor_popover(true) {
8410 return None;
8411 }
8412
8413 match &active_inline_completion.completion {
8414 InlineCompletion::Move { target, .. } => {
8415 let target_display_point = target.to_display_point(editor_snapshot);
8416
8417 if self.edit_prediction_requires_modifier() {
8418 if !self.edit_prediction_preview_is_active() {
8419 return None;
8420 }
8421
8422 self.render_edit_prediction_modifier_jump_popover(
8423 text_bounds,
8424 content_origin,
8425 visible_row_range,
8426 line_layouts,
8427 line_height,
8428 scroll_pixel_position,
8429 newest_selection_head,
8430 target_display_point,
8431 window,
8432 cx,
8433 )
8434 } else {
8435 self.render_edit_prediction_eager_jump_popover(
8436 text_bounds,
8437 content_origin,
8438 editor_snapshot,
8439 visible_row_range,
8440 scroll_top,
8441 scroll_bottom,
8442 line_height,
8443 scroll_pixel_position,
8444 target_display_point,
8445 editor_width,
8446 window,
8447 cx,
8448 )
8449 }
8450 }
8451 InlineCompletion::Edit {
8452 display_mode: EditDisplayMode::Inline,
8453 ..
8454 } => None,
8455 InlineCompletion::Edit {
8456 display_mode: EditDisplayMode::TabAccept,
8457 edits,
8458 ..
8459 } => {
8460 let range = &edits.first()?.0;
8461 let target_display_point = range.end.to_display_point(editor_snapshot);
8462
8463 self.render_edit_prediction_end_of_line_popover(
8464 "Accept",
8465 editor_snapshot,
8466 visible_row_range,
8467 target_display_point,
8468 line_height,
8469 scroll_pixel_position,
8470 content_origin,
8471 editor_width,
8472 window,
8473 cx,
8474 )
8475 }
8476 InlineCompletion::Edit {
8477 edits,
8478 edit_preview,
8479 display_mode: EditDisplayMode::DiffPopover,
8480 snapshot,
8481 } => self.render_edit_prediction_diff_popover(
8482 text_bounds,
8483 content_origin,
8484 right_margin,
8485 editor_snapshot,
8486 visible_row_range,
8487 line_layouts,
8488 line_height,
8489 scroll_pixel_position,
8490 newest_selection_head,
8491 editor_width,
8492 style,
8493 edits,
8494 edit_preview,
8495 snapshot,
8496 window,
8497 cx,
8498 ),
8499 }
8500 }
8501
8502 fn render_edit_prediction_modifier_jump_popover(
8503 &mut self,
8504 text_bounds: &Bounds<Pixels>,
8505 content_origin: gpui::Point<Pixels>,
8506 visible_row_range: Range<DisplayRow>,
8507 line_layouts: &[LineWithInvisibles],
8508 line_height: Pixels,
8509 scroll_pixel_position: gpui::Point<Pixels>,
8510 newest_selection_head: Option<DisplayPoint>,
8511 target_display_point: DisplayPoint,
8512 window: &mut Window,
8513 cx: &mut App,
8514 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8515 let scrolled_content_origin =
8516 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8517
8518 const SCROLL_PADDING_Y: Pixels = px(12.);
8519
8520 if target_display_point.row() < visible_row_range.start {
8521 return self.render_edit_prediction_scroll_popover(
8522 |_| SCROLL_PADDING_Y,
8523 IconName::ArrowUp,
8524 visible_row_range,
8525 line_layouts,
8526 newest_selection_head,
8527 scrolled_content_origin,
8528 window,
8529 cx,
8530 );
8531 } else if target_display_point.row() >= visible_row_range.end {
8532 return self.render_edit_prediction_scroll_popover(
8533 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8534 IconName::ArrowDown,
8535 visible_row_range,
8536 line_layouts,
8537 newest_selection_head,
8538 scrolled_content_origin,
8539 window,
8540 cx,
8541 );
8542 }
8543
8544 const POLE_WIDTH: Pixels = px(2.);
8545
8546 let line_layout =
8547 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8548 let target_column = target_display_point.column() as usize;
8549
8550 let target_x = line_layout.x_for_index(target_column);
8551 let target_y =
8552 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8553
8554 let flag_on_right = target_x < text_bounds.size.width / 2.;
8555
8556 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8557 border_color.l += 0.001;
8558
8559 let mut element = v_flex()
8560 .items_end()
8561 .when(flag_on_right, |el| el.items_start())
8562 .child(if flag_on_right {
8563 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8564 .rounded_bl(px(0.))
8565 .rounded_tl(px(0.))
8566 .border_l_2()
8567 .border_color(border_color)
8568 } else {
8569 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8570 .rounded_br(px(0.))
8571 .rounded_tr(px(0.))
8572 .border_r_2()
8573 .border_color(border_color)
8574 })
8575 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8576 .into_any();
8577
8578 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8579
8580 let mut origin = scrolled_content_origin + point(target_x, target_y)
8581 - point(
8582 if flag_on_right {
8583 POLE_WIDTH
8584 } else {
8585 size.width - POLE_WIDTH
8586 },
8587 size.height - line_height,
8588 );
8589
8590 origin.x = origin.x.max(content_origin.x);
8591
8592 element.prepaint_at(origin, window, cx);
8593
8594 Some((element, origin))
8595 }
8596
8597 fn render_edit_prediction_scroll_popover(
8598 &mut self,
8599 to_y: impl Fn(Size<Pixels>) -> Pixels,
8600 scroll_icon: IconName,
8601 visible_row_range: Range<DisplayRow>,
8602 line_layouts: &[LineWithInvisibles],
8603 newest_selection_head: Option<DisplayPoint>,
8604 scrolled_content_origin: gpui::Point<Pixels>,
8605 window: &mut Window,
8606 cx: &mut App,
8607 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8608 let mut element = self
8609 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8610 .into_any();
8611
8612 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8613
8614 let cursor = newest_selection_head?;
8615 let cursor_row_layout =
8616 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8617 let cursor_column = cursor.column() as usize;
8618
8619 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8620
8621 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8622
8623 element.prepaint_at(origin, window, cx);
8624 Some((element, origin))
8625 }
8626
8627 fn render_edit_prediction_eager_jump_popover(
8628 &mut self,
8629 text_bounds: &Bounds<Pixels>,
8630 content_origin: gpui::Point<Pixels>,
8631 editor_snapshot: &EditorSnapshot,
8632 visible_row_range: Range<DisplayRow>,
8633 scroll_top: f32,
8634 scroll_bottom: f32,
8635 line_height: Pixels,
8636 scroll_pixel_position: gpui::Point<Pixels>,
8637 target_display_point: DisplayPoint,
8638 editor_width: Pixels,
8639 window: &mut Window,
8640 cx: &mut App,
8641 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8642 if target_display_point.row().as_f32() < scroll_top {
8643 let mut element = self
8644 .render_edit_prediction_line_popover(
8645 "Jump to Edit",
8646 Some(IconName::ArrowUp),
8647 window,
8648 cx,
8649 )?
8650 .into_any();
8651
8652 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8653 let offset = point(
8654 (text_bounds.size.width - size.width) / 2.,
8655 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8656 );
8657
8658 let origin = text_bounds.origin + offset;
8659 element.prepaint_at(origin, window, cx);
8660 Some((element, origin))
8661 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8662 let mut element = self
8663 .render_edit_prediction_line_popover(
8664 "Jump to Edit",
8665 Some(IconName::ArrowDown),
8666 window,
8667 cx,
8668 )?
8669 .into_any();
8670
8671 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8672 let offset = point(
8673 (text_bounds.size.width - size.width) / 2.,
8674 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8675 );
8676
8677 let origin = text_bounds.origin + offset;
8678 element.prepaint_at(origin, window, cx);
8679 Some((element, origin))
8680 } else {
8681 self.render_edit_prediction_end_of_line_popover(
8682 "Jump to Edit",
8683 editor_snapshot,
8684 visible_row_range,
8685 target_display_point,
8686 line_height,
8687 scroll_pixel_position,
8688 content_origin,
8689 editor_width,
8690 window,
8691 cx,
8692 )
8693 }
8694 }
8695
8696 fn render_edit_prediction_end_of_line_popover(
8697 self: &mut Editor,
8698 label: &'static str,
8699 editor_snapshot: &EditorSnapshot,
8700 visible_row_range: Range<DisplayRow>,
8701 target_display_point: DisplayPoint,
8702 line_height: Pixels,
8703 scroll_pixel_position: gpui::Point<Pixels>,
8704 content_origin: gpui::Point<Pixels>,
8705 editor_width: Pixels,
8706 window: &mut Window,
8707 cx: &mut App,
8708 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8709 let target_line_end = DisplayPoint::new(
8710 target_display_point.row(),
8711 editor_snapshot.line_len(target_display_point.row()),
8712 );
8713
8714 let mut element = self
8715 .render_edit_prediction_line_popover(label, None, window, cx)?
8716 .into_any();
8717
8718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8719
8720 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8721
8722 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8723 let mut origin = start_point
8724 + line_origin
8725 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8726 origin.x = origin.x.max(content_origin.x);
8727
8728 let max_x = content_origin.x + editor_width - size.width;
8729
8730 if origin.x > max_x {
8731 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8732
8733 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8734 origin.y += offset;
8735 IconName::ArrowUp
8736 } else {
8737 origin.y -= offset;
8738 IconName::ArrowDown
8739 };
8740
8741 element = self
8742 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8743 .into_any();
8744
8745 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8746
8747 origin.x = content_origin.x + editor_width - size.width - px(2.);
8748 }
8749
8750 element.prepaint_at(origin, window, cx);
8751 Some((element, origin))
8752 }
8753
8754 fn render_edit_prediction_diff_popover(
8755 self: &Editor,
8756 text_bounds: &Bounds<Pixels>,
8757 content_origin: gpui::Point<Pixels>,
8758 right_margin: Pixels,
8759 editor_snapshot: &EditorSnapshot,
8760 visible_row_range: Range<DisplayRow>,
8761 line_layouts: &[LineWithInvisibles],
8762 line_height: Pixels,
8763 scroll_pixel_position: gpui::Point<Pixels>,
8764 newest_selection_head: Option<DisplayPoint>,
8765 editor_width: Pixels,
8766 style: &EditorStyle,
8767 edits: &Vec<(Range<Anchor>, String)>,
8768 edit_preview: &Option<language::EditPreview>,
8769 snapshot: &language::BufferSnapshot,
8770 window: &mut Window,
8771 cx: &mut App,
8772 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8773 let edit_start = edits
8774 .first()
8775 .unwrap()
8776 .0
8777 .start
8778 .to_display_point(editor_snapshot);
8779 let edit_end = edits
8780 .last()
8781 .unwrap()
8782 .0
8783 .end
8784 .to_display_point(editor_snapshot);
8785
8786 let is_visible = visible_row_range.contains(&edit_start.row())
8787 || visible_row_range.contains(&edit_end.row());
8788 if !is_visible {
8789 return None;
8790 }
8791
8792 let highlighted_edits =
8793 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8794
8795 let styled_text = highlighted_edits.to_styled_text(&style.text);
8796 let line_count = highlighted_edits.text.lines().count();
8797
8798 const BORDER_WIDTH: Pixels = px(1.);
8799
8800 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8801 let has_keybind = keybind.is_some();
8802
8803 let mut element = h_flex()
8804 .items_start()
8805 .child(
8806 h_flex()
8807 .bg(cx.theme().colors().editor_background)
8808 .border(BORDER_WIDTH)
8809 .shadow_xs()
8810 .border_color(cx.theme().colors().border)
8811 .rounded_l_lg()
8812 .when(line_count > 1, |el| el.rounded_br_lg())
8813 .pr_1()
8814 .child(styled_text),
8815 )
8816 .child(
8817 h_flex()
8818 .h(line_height + BORDER_WIDTH * 2.)
8819 .px_1p5()
8820 .gap_1()
8821 // Workaround: For some reason, there's a gap if we don't do this
8822 .ml(-BORDER_WIDTH)
8823 .shadow(vec![gpui::BoxShadow {
8824 color: gpui::black().opacity(0.05),
8825 offset: point(px(1.), px(1.)),
8826 blur_radius: px(2.),
8827 spread_radius: px(0.),
8828 }])
8829 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8830 .border(BORDER_WIDTH)
8831 .border_color(cx.theme().colors().border)
8832 .rounded_r_lg()
8833 .id("edit_prediction_diff_popover_keybind")
8834 .when(!has_keybind, |el| {
8835 let status_colors = cx.theme().status();
8836
8837 el.bg(status_colors.error_background)
8838 .border_color(status_colors.error.opacity(0.6))
8839 .child(Icon::new(IconName::Info).color(Color::Error))
8840 .cursor_default()
8841 .hoverable_tooltip(move |_window, cx| {
8842 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8843 })
8844 })
8845 .children(keybind),
8846 )
8847 .into_any();
8848
8849 let longest_row =
8850 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8851 let longest_line_width = if visible_row_range.contains(&longest_row) {
8852 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8853 } else {
8854 layout_line(
8855 longest_row,
8856 editor_snapshot,
8857 style,
8858 editor_width,
8859 |_| false,
8860 window,
8861 cx,
8862 )
8863 .width
8864 };
8865
8866 let viewport_bounds =
8867 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8868 right: -right_margin,
8869 ..Default::default()
8870 });
8871
8872 let x_after_longest =
8873 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8874 - scroll_pixel_position.x;
8875
8876 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8877
8878 // Fully visible if it can be displayed within the window (allow overlapping other
8879 // panes). However, this is only allowed if the popover starts within text_bounds.
8880 let can_position_to_the_right = x_after_longest < text_bounds.right()
8881 && x_after_longest + element_bounds.width < viewport_bounds.right();
8882
8883 let mut origin = if can_position_to_the_right {
8884 point(
8885 x_after_longest,
8886 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8887 - scroll_pixel_position.y,
8888 )
8889 } else {
8890 let cursor_row = newest_selection_head.map(|head| head.row());
8891 let above_edit = edit_start
8892 .row()
8893 .0
8894 .checked_sub(line_count as u32)
8895 .map(DisplayRow);
8896 let below_edit = Some(edit_end.row() + 1);
8897 let above_cursor =
8898 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8899 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8900
8901 // Place the edit popover adjacent to the edit if there is a location
8902 // available that is onscreen and does not obscure the cursor. Otherwise,
8903 // place it adjacent to the cursor.
8904 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8905 .into_iter()
8906 .flatten()
8907 .find(|&start_row| {
8908 let end_row = start_row + line_count as u32;
8909 visible_row_range.contains(&start_row)
8910 && visible_row_range.contains(&end_row)
8911 && cursor_row.map_or(true, |cursor_row| {
8912 !((start_row..end_row).contains(&cursor_row))
8913 })
8914 })?;
8915
8916 content_origin
8917 + point(
8918 -scroll_pixel_position.x,
8919 row_target.as_f32() * line_height - scroll_pixel_position.y,
8920 )
8921 };
8922
8923 origin.x -= BORDER_WIDTH;
8924
8925 window.defer_draw(element, origin, 1);
8926
8927 // Do not return an element, since it will already be drawn due to defer_draw.
8928 None
8929 }
8930
8931 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8932 px(30.)
8933 }
8934
8935 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8936 if self.read_only(cx) {
8937 cx.theme().players().read_only()
8938 } else {
8939 self.style.as_ref().unwrap().local_player
8940 }
8941 }
8942
8943 fn render_edit_prediction_accept_keybind(
8944 &self,
8945 window: &mut Window,
8946 cx: &App,
8947 ) -> Option<AnyElement> {
8948 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8949 let accept_keystroke = accept_binding.keystroke()?;
8950
8951 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8952
8953 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8954 Color::Accent
8955 } else {
8956 Color::Muted
8957 };
8958
8959 h_flex()
8960 .px_0p5()
8961 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8962 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8963 .text_size(TextSize::XSmall.rems(cx))
8964 .child(h_flex().children(ui::render_modifiers(
8965 &accept_keystroke.modifiers,
8966 PlatformStyle::platform(),
8967 Some(modifiers_color),
8968 Some(IconSize::XSmall.rems().into()),
8969 true,
8970 )))
8971 .when(is_platform_style_mac, |parent| {
8972 parent.child(accept_keystroke.key.clone())
8973 })
8974 .when(!is_platform_style_mac, |parent| {
8975 parent.child(
8976 Key::new(
8977 util::capitalize(&accept_keystroke.key),
8978 Some(Color::Default),
8979 )
8980 .size(Some(IconSize::XSmall.rems().into())),
8981 )
8982 })
8983 .into_any()
8984 .into()
8985 }
8986
8987 fn render_edit_prediction_line_popover(
8988 &self,
8989 label: impl Into<SharedString>,
8990 icon: Option<IconName>,
8991 window: &mut Window,
8992 cx: &App,
8993 ) -> Option<Stateful<Div>> {
8994 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8995
8996 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8997 let has_keybind = keybind.is_some();
8998
8999 let result = h_flex()
9000 .id("ep-line-popover")
9001 .py_0p5()
9002 .pl_1()
9003 .pr(padding_right)
9004 .gap_1()
9005 .rounded_md()
9006 .border_1()
9007 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9008 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9009 .shadow_xs()
9010 .when(!has_keybind, |el| {
9011 let status_colors = cx.theme().status();
9012
9013 el.bg(status_colors.error_background)
9014 .border_color(status_colors.error.opacity(0.6))
9015 .pl_2()
9016 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9017 .cursor_default()
9018 .hoverable_tooltip(move |_window, cx| {
9019 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9020 })
9021 })
9022 .children(keybind)
9023 .child(
9024 Label::new(label)
9025 .size(LabelSize::Small)
9026 .when(!has_keybind, |el| {
9027 el.color(cx.theme().status().error.into()).strikethrough()
9028 }),
9029 )
9030 .when(!has_keybind, |el| {
9031 el.child(
9032 h_flex().ml_1().child(
9033 Icon::new(IconName::Info)
9034 .size(IconSize::Small)
9035 .color(cx.theme().status().error.into()),
9036 ),
9037 )
9038 })
9039 .when_some(icon, |element, icon| {
9040 element.child(
9041 div()
9042 .mt(px(1.5))
9043 .child(Icon::new(icon).size(IconSize::Small)),
9044 )
9045 });
9046
9047 Some(result)
9048 }
9049
9050 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9051 let accent_color = cx.theme().colors().text_accent;
9052 let editor_bg_color = cx.theme().colors().editor_background;
9053 editor_bg_color.blend(accent_color.opacity(0.1))
9054 }
9055
9056 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9057 let accent_color = cx.theme().colors().text_accent;
9058 let editor_bg_color = cx.theme().colors().editor_background;
9059 editor_bg_color.blend(accent_color.opacity(0.6))
9060 }
9061
9062 fn render_edit_prediction_cursor_popover(
9063 &self,
9064 min_width: Pixels,
9065 max_width: Pixels,
9066 cursor_point: Point,
9067 style: &EditorStyle,
9068 accept_keystroke: Option<&gpui::Keystroke>,
9069 _window: &Window,
9070 cx: &mut Context<Editor>,
9071 ) -> Option<AnyElement> {
9072 let provider = self.edit_prediction_provider.as_ref()?;
9073
9074 if provider.provider.needs_terms_acceptance(cx) {
9075 return Some(
9076 h_flex()
9077 .min_w(min_width)
9078 .flex_1()
9079 .px_2()
9080 .py_1()
9081 .gap_3()
9082 .elevation_2(cx)
9083 .hover(|style| style.bg(cx.theme().colors().element_hover))
9084 .id("accept-terms")
9085 .cursor_pointer()
9086 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9087 .on_click(cx.listener(|this, _event, window, cx| {
9088 cx.stop_propagation();
9089 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9090 window.dispatch_action(
9091 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9092 cx,
9093 );
9094 }))
9095 .child(
9096 h_flex()
9097 .flex_1()
9098 .gap_2()
9099 .child(Icon::new(IconName::ZedPredict))
9100 .child(Label::new("Accept Terms of Service"))
9101 .child(div().w_full())
9102 .child(
9103 Icon::new(IconName::ArrowUpRight)
9104 .color(Color::Muted)
9105 .size(IconSize::Small),
9106 )
9107 .into_any_element(),
9108 )
9109 .into_any(),
9110 );
9111 }
9112
9113 let is_refreshing = provider.provider.is_refreshing(cx);
9114
9115 fn pending_completion_container() -> Div {
9116 h_flex()
9117 .h_full()
9118 .flex_1()
9119 .gap_2()
9120 .child(Icon::new(IconName::ZedPredict))
9121 }
9122
9123 let completion = match &self.active_inline_completion {
9124 Some(prediction) => {
9125 if !self.has_visible_completions_menu() {
9126 const RADIUS: Pixels = px(6.);
9127 const BORDER_WIDTH: Pixels = px(1.);
9128
9129 return Some(
9130 h_flex()
9131 .elevation_2(cx)
9132 .border(BORDER_WIDTH)
9133 .border_color(cx.theme().colors().border)
9134 .when(accept_keystroke.is_none(), |el| {
9135 el.border_color(cx.theme().status().error)
9136 })
9137 .rounded(RADIUS)
9138 .rounded_tl(px(0.))
9139 .overflow_hidden()
9140 .child(div().px_1p5().child(match &prediction.completion {
9141 InlineCompletion::Move { target, snapshot } => {
9142 use text::ToPoint as _;
9143 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9144 {
9145 Icon::new(IconName::ZedPredictDown)
9146 } else {
9147 Icon::new(IconName::ZedPredictUp)
9148 }
9149 }
9150 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9151 }))
9152 .child(
9153 h_flex()
9154 .gap_1()
9155 .py_1()
9156 .px_2()
9157 .rounded_r(RADIUS - BORDER_WIDTH)
9158 .border_l_1()
9159 .border_color(cx.theme().colors().border)
9160 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9161 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9162 el.child(
9163 Label::new("Hold")
9164 .size(LabelSize::Small)
9165 .when(accept_keystroke.is_none(), |el| {
9166 el.strikethrough()
9167 })
9168 .line_height_style(LineHeightStyle::UiLabel),
9169 )
9170 })
9171 .id("edit_prediction_cursor_popover_keybind")
9172 .when(accept_keystroke.is_none(), |el| {
9173 let status_colors = cx.theme().status();
9174
9175 el.bg(status_colors.error_background)
9176 .border_color(status_colors.error.opacity(0.6))
9177 .child(Icon::new(IconName::Info).color(Color::Error))
9178 .cursor_default()
9179 .hoverable_tooltip(move |_window, cx| {
9180 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9181 .into()
9182 })
9183 })
9184 .when_some(
9185 accept_keystroke.as_ref(),
9186 |el, accept_keystroke| {
9187 el.child(h_flex().children(ui::render_modifiers(
9188 &accept_keystroke.modifiers,
9189 PlatformStyle::platform(),
9190 Some(Color::Default),
9191 Some(IconSize::XSmall.rems().into()),
9192 false,
9193 )))
9194 },
9195 ),
9196 )
9197 .into_any(),
9198 );
9199 }
9200
9201 self.render_edit_prediction_cursor_popover_preview(
9202 prediction,
9203 cursor_point,
9204 style,
9205 cx,
9206 )?
9207 }
9208
9209 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9210 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9211 stale_completion,
9212 cursor_point,
9213 style,
9214 cx,
9215 )?,
9216
9217 None => {
9218 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9219 }
9220 },
9221
9222 None => pending_completion_container().child(Label::new("No Prediction")),
9223 };
9224
9225 let completion = if is_refreshing {
9226 completion
9227 .with_animation(
9228 "loading-completion",
9229 Animation::new(Duration::from_secs(2))
9230 .repeat()
9231 .with_easing(pulsating_between(0.4, 0.8)),
9232 |label, delta| label.opacity(delta),
9233 )
9234 .into_any_element()
9235 } else {
9236 completion.into_any_element()
9237 };
9238
9239 let has_completion = self.active_inline_completion.is_some();
9240
9241 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9242 Some(
9243 h_flex()
9244 .min_w(min_width)
9245 .max_w(max_width)
9246 .flex_1()
9247 .elevation_2(cx)
9248 .border_color(cx.theme().colors().border)
9249 .child(
9250 div()
9251 .flex_1()
9252 .py_1()
9253 .px_2()
9254 .overflow_hidden()
9255 .child(completion),
9256 )
9257 .when_some(accept_keystroke, |el, accept_keystroke| {
9258 if !accept_keystroke.modifiers.modified() {
9259 return el;
9260 }
9261
9262 el.child(
9263 h_flex()
9264 .h_full()
9265 .border_l_1()
9266 .rounded_r_lg()
9267 .border_color(cx.theme().colors().border)
9268 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9269 .gap_1()
9270 .py_1()
9271 .px_2()
9272 .child(
9273 h_flex()
9274 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9275 .when(is_platform_style_mac, |parent| parent.gap_1())
9276 .child(h_flex().children(ui::render_modifiers(
9277 &accept_keystroke.modifiers,
9278 PlatformStyle::platform(),
9279 Some(if !has_completion {
9280 Color::Muted
9281 } else {
9282 Color::Default
9283 }),
9284 None,
9285 false,
9286 ))),
9287 )
9288 .child(Label::new("Preview").into_any_element())
9289 .opacity(if has_completion { 1.0 } else { 0.4 }),
9290 )
9291 })
9292 .into_any(),
9293 )
9294 }
9295
9296 fn render_edit_prediction_cursor_popover_preview(
9297 &self,
9298 completion: &InlineCompletionState,
9299 cursor_point: Point,
9300 style: &EditorStyle,
9301 cx: &mut Context<Editor>,
9302 ) -> Option<Div> {
9303 use text::ToPoint as _;
9304
9305 fn render_relative_row_jump(
9306 prefix: impl Into<String>,
9307 current_row: u32,
9308 target_row: u32,
9309 ) -> Div {
9310 let (row_diff, arrow) = if target_row < current_row {
9311 (current_row - target_row, IconName::ArrowUp)
9312 } else {
9313 (target_row - current_row, IconName::ArrowDown)
9314 };
9315
9316 h_flex()
9317 .child(
9318 Label::new(format!("{}{}", prefix.into(), row_diff))
9319 .color(Color::Muted)
9320 .size(LabelSize::Small),
9321 )
9322 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9323 }
9324
9325 match &completion.completion {
9326 InlineCompletion::Move {
9327 target, snapshot, ..
9328 } => Some(
9329 h_flex()
9330 .px_2()
9331 .gap_2()
9332 .flex_1()
9333 .child(
9334 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9335 Icon::new(IconName::ZedPredictDown)
9336 } else {
9337 Icon::new(IconName::ZedPredictUp)
9338 },
9339 )
9340 .child(Label::new("Jump to Edit")),
9341 ),
9342
9343 InlineCompletion::Edit {
9344 edits,
9345 edit_preview,
9346 snapshot,
9347 display_mode: _,
9348 } => {
9349 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9350
9351 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9352 &snapshot,
9353 &edits,
9354 edit_preview.as_ref()?,
9355 true,
9356 cx,
9357 )
9358 .first_line_preview();
9359
9360 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9361 .with_default_highlights(&style.text, highlighted_edits.highlights);
9362
9363 let preview = h_flex()
9364 .gap_1()
9365 .min_w_16()
9366 .child(styled_text)
9367 .when(has_more_lines, |parent| parent.child("…"));
9368
9369 let left = if first_edit_row != cursor_point.row {
9370 render_relative_row_jump("", cursor_point.row, first_edit_row)
9371 .into_any_element()
9372 } else {
9373 Icon::new(IconName::ZedPredict).into_any_element()
9374 };
9375
9376 Some(
9377 h_flex()
9378 .h_full()
9379 .flex_1()
9380 .gap_2()
9381 .pr_1()
9382 .overflow_x_hidden()
9383 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9384 .child(left)
9385 .child(preview),
9386 )
9387 }
9388 }
9389 }
9390
9391 pub fn render_context_menu(
9392 &self,
9393 style: &EditorStyle,
9394 max_height_in_lines: u32,
9395 window: &mut Window,
9396 cx: &mut Context<Editor>,
9397 ) -> Option<AnyElement> {
9398 let menu = self.context_menu.borrow();
9399 let menu = menu.as_ref()?;
9400 if !menu.visible() {
9401 return None;
9402 };
9403 Some(menu.render(style, max_height_in_lines, window, cx))
9404 }
9405
9406 fn render_context_menu_aside(
9407 &mut self,
9408 max_size: Size<Pixels>,
9409 window: &mut Window,
9410 cx: &mut Context<Editor>,
9411 ) -> Option<AnyElement> {
9412 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9413 if menu.visible() {
9414 menu.render_aside(max_size, window, cx)
9415 } else {
9416 None
9417 }
9418 })
9419 }
9420
9421 fn hide_context_menu(
9422 &mut self,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) -> Option<CodeContextMenu> {
9426 cx.notify();
9427 self.completion_tasks.clear();
9428 let context_menu = self.context_menu.borrow_mut().take();
9429 self.stale_inline_completion_in_menu.take();
9430 self.update_visible_inline_completion(window, cx);
9431 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9432 if let Some(completion_provider) = &self.completion_provider {
9433 completion_provider.selection_changed(None, window, cx);
9434 }
9435 }
9436 context_menu
9437 }
9438
9439 fn show_snippet_choices(
9440 &mut self,
9441 choices: &Vec<String>,
9442 selection: Range<Anchor>,
9443 cx: &mut Context<Self>,
9444 ) {
9445 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9446 (Some(a), Some(b)) if a == b => a,
9447 _ => {
9448 log::error!("expected anchor range to have matching buffer IDs");
9449 return;
9450 }
9451 };
9452 let multi_buffer = self.buffer().read(cx);
9453 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9454 return;
9455 };
9456
9457 let id = post_inc(&mut self.next_completion_id);
9458 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9459 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9460 CompletionsMenu::new_snippet_choices(
9461 id,
9462 true,
9463 choices,
9464 selection,
9465 buffer,
9466 snippet_sort_order,
9467 ),
9468 ));
9469 }
9470
9471 pub fn insert_snippet(
9472 &mut self,
9473 insertion_ranges: &[Range<usize>],
9474 snippet: Snippet,
9475 window: &mut Window,
9476 cx: &mut Context<Self>,
9477 ) -> Result<()> {
9478 struct Tabstop<T> {
9479 is_end_tabstop: bool,
9480 ranges: Vec<Range<T>>,
9481 choices: Option<Vec<String>>,
9482 }
9483
9484 let tabstops = self.buffer.update(cx, |buffer, cx| {
9485 let snippet_text: Arc<str> = snippet.text.clone().into();
9486 let edits = insertion_ranges
9487 .iter()
9488 .cloned()
9489 .map(|range| (range, snippet_text.clone()));
9490 let autoindent_mode = AutoindentMode::Block {
9491 original_indent_columns: Vec::new(),
9492 };
9493 buffer.edit(edits, Some(autoindent_mode), cx);
9494
9495 let snapshot = &*buffer.read(cx);
9496 let snippet = &snippet;
9497 snippet
9498 .tabstops
9499 .iter()
9500 .map(|tabstop| {
9501 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9502 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9503 });
9504 let mut tabstop_ranges = tabstop
9505 .ranges
9506 .iter()
9507 .flat_map(|tabstop_range| {
9508 let mut delta = 0_isize;
9509 insertion_ranges.iter().map(move |insertion_range| {
9510 let insertion_start = insertion_range.start as isize + delta;
9511 delta +=
9512 snippet.text.len() as isize - insertion_range.len() as isize;
9513
9514 let start = ((insertion_start + tabstop_range.start) as usize)
9515 .min(snapshot.len());
9516 let end = ((insertion_start + tabstop_range.end) as usize)
9517 .min(snapshot.len());
9518 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9519 })
9520 })
9521 .collect::<Vec<_>>();
9522 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9523
9524 Tabstop {
9525 is_end_tabstop,
9526 ranges: tabstop_ranges,
9527 choices: tabstop.choices.clone(),
9528 }
9529 })
9530 .collect::<Vec<_>>()
9531 });
9532 if let Some(tabstop) = tabstops.first() {
9533 self.change_selections(Default::default(), window, cx, |s| {
9534 // Reverse order so that the first range is the newest created selection.
9535 // Completions will use it and autoscroll will prioritize it.
9536 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9537 });
9538
9539 if let Some(choices) = &tabstop.choices {
9540 if let Some(selection) = tabstop.ranges.first() {
9541 self.show_snippet_choices(choices, selection.clone(), cx)
9542 }
9543 }
9544
9545 // If we're already at the last tabstop and it's at the end of the snippet,
9546 // we're done, we don't need to keep the state around.
9547 if !tabstop.is_end_tabstop {
9548 let choices = tabstops
9549 .iter()
9550 .map(|tabstop| tabstop.choices.clone())
9551 .collect();
9552
9553 let ranges = tabstops
9554 .into_iter()
9555 .map(|tabstop| tabstop.ranges)
9556 .collect::<Vec<_>>();
9557
9558 self.snippet_stack.push(SnippetState {
9559 active_index: 0,
9560 ranges,
9561 choices,
9562 });
9563 }
9564
9565 // Check whether the just-entered snippet ends with an auto-closable bracket.
9566 if self.autoclose_regions.is_empty() {
9567 let snapshot = self.buffer.read(cx).snapshot(cx);
9568 let mut all_selections = self.selections.all::<Point>(cx);
9569 for selection in &mut all_selections {
9570 let selection_head = selection.head();
9571 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9572 continue;
9573 };
9574
9575 let mut bracket_pair = None;
9576 let max_lookup_length = scope
9577 .brackets()
9578 .map(|(pair, _)| {
9579 pair.start
9580 .as_str()
9581 .chars()
9582 .count()
9583 .max(pair.end.as_str().chars().count())
9584 })
9585 .max();
9586 if let Some(max_lookup_length) = max_lookup_length {
9587 let next_text = snapshot
9588 .chars_at(selection_head)
9589 .take(max_lookup_length)
9590 .collect::<String>();
9591 let prev_text = snapshot
9592 .reversed_chars_at(selection_head)
9593 .take(max_lookup_length)
9594 .collect::<String>();
9595
9596 for (pair, enabled) in scope.brackets() {
9597 if enabled
9598 && pair.close
9599 && prev_text.starts_with(pair.start.as_str())
9600 && next_text.starts_with(pair.end.as_str())
9601 {
9602 bracket_pair = Some(pair.clone());
9603 break;
9604 }
9605 }
9606 }
9607
9608 if let Some(pair) = bracket_pair {
9609 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9610 let autoclose_enabled =
9611 self.use_autoclose && snapshot_settings.use_autoclose;
9612 if autoclose_enabled {
9613 let start = snapshot.anchor_after(selection_head);
9614 let end = snapshot.anchor_after(selection_head);
9615 self.autoclose_regions.push(AutocloseRegion {
9616 selection_id: selection.id,
9617 range: start..end,
9618 pair,
9619 });
9620 }
9621 }
9622 }
9623 }
9624 }
9625 Ok(())
9626 }
9627
9628 pub fn move_to_next_snippet_tabstop(
9629 &mut self,
9630 window: &mut Window,
9631 cx: &mut Context<Self>,
9632 ) -> bool {
9633 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9634 }
9635
9636 pub fn move_to_prev_snippet_tabstop(
9637 &mut self,
9638 window: &mut Window,
9639 cx: &mut Context<Self>,
9640 ) -> bool {
9641 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9642 }
9643
9644 pub fn move_to_snippet_tabstop(
9645 &mut self,
9646 bias: Bias,
9647 window: &mut Window,
9648 cx: &mut Context<Self>,
9649 ) -> bool {
9650 if let Some(mut snippet) = self.snippet_stack.pop() {
9651 match bias {
9652 Bias::Left => {
9653 if snippet.active_index > 0 {
9654 snippet.active_index -= 1;
9655 } else {
9656 self.snippet_stack.push(snippet);
9657 return false;
9658 }
9659 }
9660 Bias::Right => {
9661 if snippet.active_index + 1 < snippet.ranges.len() {
9662 snippet.active_index += 1;
9663 } else {
9664 self.snippet_stack.push(snippet);
9665 return false;
9666 }
9667 }
9668 }
9669 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9670 self.change_selections(Default::default(), window, cx, |s| {
9671 // Reverse order so that the first range is the newest created selection.
9672 // Completions will use it and autoscroll will prioritize it.
9673 s.select_ranges(current_ranges.iter().rev().cloned())
9674 });
9675
9676 if let Some(choices) = &snippet.choices[snippet.active_index] {
9677 if let Some(selection) = current_ranges.first() {
9678 self.show_snippet_choices(&choices, selection.clone(), cx);
9679 }
9680 }
9681
9682 // If snippet state is not at the last tabstop, push it back on the stack
9683 if snippet.active_index + 1 < snippet.ranges.len() {
9684 self.snippet_stack.push(snippet);
9685 }
9686 return true;
9687 }
9688 }
9689
9690 false
9691 }
9692
9693 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9694 self.transact(window, cx, |this, window, cx| {
9695 this.select_all(&SelectAll, window, cx);
9696 this.insert("", window, cx);
9697 });
9698 }
9699
9700 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9702 self.transact(window, cx, |this, window, cx| {
9703 this.select_autoclose_pair(window, cx);
9704 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9705 if !this.linked_edit_ranges.is_empty() {
9706 let selections = this.selections.all::<MultiBufferPoint>(cx);
9707 let snapshot = this.buffer.read(cx).snapshot(cx);
9708
9709 for selection in selections.iter() {
9710 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9711 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9712 if selection_start.buffer_id != selection_end.buffer_id {
9713 continue;
9714 }
9715 if let Some(ranges) =
9716 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9717 {
9718 for (buffer, entries) in ranges {
9719 linked_ranges.entry(buffer).or_default().extend(entries);
9720 }
9721 }
9722 }
9723 }
9724
9725 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9726 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9727 for selection in &mut selections {
9728 if selection.is_empty() {
9729 let old_head = selection.head();
9730 let mut new_head =
9731 movement::left(&display_map, old_head.to_display_point(&display_map))
9732 .to_point(&display_map);
9733 if let Some((buffer, line_buffer_range)) = display_map
9734 .buffer_snapshot
9735 .buffer_line_for_row(MultiBufferRow(old_head.row))
9736 {
9737 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9738 let indent_len = match indent_size.kind {
9739 IndentKind::Space => {
9740 buffer.settings_at(line_buffer_range.start, cx).tab_size
9741 }
9742 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9743 };
9744 if old_head.column <= indent_size.len && old_head.column > 0 {
9745 let indent_len = indent_len.get();
9746 new_head = cmp::min(
9747 new_head,
9748 MultiBufferPoint::new(
9749 old_head.row,
9750 ((old_head.column - 1) / indent_len) * indent_len,
9751 ),
9752 );
9753 }
9754 }
9755
9756 selection.set_head(new_head, SelectionGoal::None);
9757 }
9758 }
9759
9760 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9761 this.insert("", window, cx);
9762 let empty_str: Arc<str> = Arc::from("");
9763 for (buffer, edits) in linked_ranges {
9764 let snapshot = buffer.read(cx).snapshot();
9765 use text::ToPoint as TP;
9766
9767 let edits = edits
9768 .into_iter()
9769 .map(|range| {
9770 let end_point = TP::to_point(&range.end, &snapshot);
9771 let mut start_point = TP::to_point(&range.start, &snapshot);
9772
9773 if end_point == start_point {
9774 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9775 .saturating_sub(1);
9776 start_point =
9777 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9778 };
9779
9780 (start_point..end_point, empty_str.clone())
9781 })
9782 .sorted_by_key(|(range, _)| range.start)
9783 .collect::<Vec<_>>();
9784 buffer.update(cx, |this, cx| {
9785 this.edit(edits, None, cx);
9786 })
9787 }
9788 this.refresh_inline_completion(true, false, window, cx);
9789 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9790 });
9791 }
9792
9793 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9795 self.transact(window, cx, |this, window, cx| {
9796 this.change_selections(Default::default(), window, cx, |s| {
9797 s.move_with(|map, selection| {
9798 if selection.is_empty() {
9799 let cursor = movement::right(map, selection.head());
9800 selection.end = cursor;
9801 selection.reversed = true;
9802 selection.goal = SelectionGoal::None;
9803 }
9804 })
9805 });
9806 this.insert("", window, cx);
9807 this.refresh_inline_completion(true, false, window, cx);
9808 });
9809 }
9810
9811 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9812 if self.mode.is_single_line() {
9813 cx.propagate();
9814 return;
9815 }
9816
9817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9818 if self.move_to_prev_snippet_tabstop(window, cx) {
9819 return;
9820 }
9821 self.outdent(&Outdent, window, cx);
9822 }
9823
9824 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9825 if self.mode.is_single_line() {
9826 cx.propagate();
9827 return;
9828 }
9829
9830 if self.move_to_next_snippet_tabstop(window, cx) {
9831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9832 return;
9833 }
9834 if self.read_only(cx) {
9835 return;
9836 }
9837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9838 let mut selections = self.selections.all_adjusted(cx);
9839 let buffer = self.buffer.read(cx);
9840 let snapshot = buffer.snapshot(cx);
9841 let rows_iter = selections.iter().map(|s| s.head().row);
9842 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9843
9844 let has_some_cursor_in_whitespace = selections
9845 .iter()
9846 .filter(|selection| selection.is_empty())
9847 .any(|selection| {
9848 let cursor = selection.head();
9849 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9850 cursor.column < current_indent.len
9851 });
9852
9853 let mut edits = Vec::new();
9854 let mut prev_edited_row = 0;
9855 let mut row_delta = 0;
9856 for selection in &mut selections {
9857 if selection.start.row != prev_edited_row {
9858 row_delta = 0;
9859 }
9860 prev_edited_row = selection.end.row;
9861
9862 // If the selection is non-empty, then increase the indentation of the selected lines.
9863 if !selection.is_empty() {
9864 row_delta =
9865 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9866 continue;
9867 }
9868
9869 let cursor = selection.head();
9870 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9871 if let Some(suggested_indent) =
9872 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9873 {
9874 // Don't do anything if already at suggested indent
9875 // and there is any other cursor which is not
9876 if has_some_cursor_in_whitespace
9877 && cursor.column == current_indent.len
9878 && current_indent.len == suggested_indent.len
9879 {
9880 continue;
9881 }
9882
9883 // Adjust line and move cursor to suggested indent
9884 // if cursor is not at suggested indent
9885 if cursor.column < suggested_indent.len
9886 && cursor.column <= current_indent.len
9887 && current_indent.len <= suggested_indent.len
9888 {
9889 selection.start = Point::new(cursor.row, suggested_indent.len);
9890 selection.end = selection.start;
9891 if row_delta == 0 {
9892 edits.extend(Buffer::edit_for_indent_size_adjustment(
9893 cursor.row,
9894 current_indent,
9895 suggested_indent,
9896 ));
9897 row_delta = suggested_indent.len - current_indent.len;
9898 }
9899 continue;
9900 }
9901
9902 // If current indent is more than suggested indent
9903 // only move cursor to current indent and skip indent
9904 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9905 selection.start = Point::new(cursor.row, current_indent.len);
9906 selection.end = selection.start;
9907 continue;
9908 }
9909 }
9910
9911 // Otherwise, insert a hard or soft tab.
9912 let settings = buffer.language_settings_at(cursor, cx);
9913 let tab_size = if settings.hard_tabs {
9914 IndentSize::tab()
9915 } else {
9916 let tab_size = settings.tab_size.get();
9917 let indent_remainder = snapshot
9918 .text_for_range(Point::new(cursor.row, 0)..cursor)
9919 .flat_map(str::chars)
9920 .fold(row_delta % tab_size, |counter: u32, c| {
9921 if c == '\t' {
9922 0
9923 } else {
9924 (counter + 1) % tab_size
9925 }
9926 });
9927
9928 let chars_to_next_tab_stop = tab_size - indent_remainder;
9929 IndentSize::spaces(chars_to_next_tab_stop)
9930 };
9931 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9932 selection.end = selection.start;
9933 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9934 row_delta += tab_size.len;
9935 }
9936
9937 self.transact(window, cx, |this, window, cx| {
9938 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9939 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9940 this.refresh_inline_completion(true, false, window, cx);
9941 });
9942 }
9943
9944 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9945 if self.read_only(cx) {
9946 return;
9947 }
9948 if self.mode.is_single_line() {
9949 cx.propagate();
9950 return;
9951 }
9952
9953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9954 let mut selections = self.selections.all::<Point>(cx);
9955 let mut prev_edited_row = 0;
9956 let mut row_delta = 0;
9957 let mut edits = Vec::new();
9958 let buffer = self.buffer.read(cx);
9959 let snapshot = buffer.snapshot(cx);
9960 for selection in &mut selections {
9961 if selection.start.row != prev_edited_row {
9962 row_delta = 0;
9963 }
9964 prev_edited_row = selection.end.row;
9965
9966 row_delta =
9967 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9968 }
9969
9970 self.transact(window, cx, |this, window, cx| {
9971 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9972 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9973 });
9974 }
9975
9976 fn indent_selection(
9977 buffer: &MultiBuffer,
9978 snapshot: &MultiBufferSnapshot,
9979 selection: &mut Selection<Point>,
9980 edits: &mut Vec<(Range<Point>, String)>,
9981 delta_for_start_row: u32,
9982 cx: &App,
9983 ) -> u32 {
9984 let settings = buffer.language_settings_at(selection.start, cx);
9985 let tab_size = settings.tab_size.get();
9986 let indent_kind = if settings.hard_tabs {
9987 IndentKind::Tab
9988 } else {
9989 IndentKind::Space
9990 };
9991 let mut start_row = selection.start.row;
9992 let mut end_row = selection.end.row + 1;
9993
9994 // If a selection ends at the beginning of a line, don't indent
9995 // that last line.
9996 if selection.end.column == 0 && selection.end.row > selection.start.row {
9997 end_row -= 1;
9998 }
9999
10000 // Avoid re-indenting a row that has already been indented by a
10001 // previous selection, but still update this selection's column
10002 // to reflect that indentation.
10003 if delta_for_start_row > 0 {
10004 start_row += 1;
10005 selection.start.column += delta_for_start_row;
10006 if selection.end.row == selection.start.row {
10007 selection.end.column += delta_for_start_row;
10008 }
10009 }
10010
10011 let mut delta_for_end_row = 0;
10012 let has_multiple_rows = start_row + 1 != end_row;
10013 for row in start_row..end_row {
10014 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10015 let indent_delta = match (current_indent.kind, indent_kind) {
10016 (IndentKind::Space, IndentKind::Space) => {
10017 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10018 IndentSize::spaces(columns_to_next_tab_stop)
10019 }
10020 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10021 (_, IndentKind::Tab) => IndentSize::tab(),
10022 };
10023
10024 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10025 0
10026 } else {
10027 selection.start.column
10028 };
10029 let row_start = Point::new(row, start);
10030 edits.push((
10031 row_start..row_start,
10032 indent_delta.chars().collect::<String>(),
10033 ));
10034
10035 // Update this selection's endpoints to reflect the indentation.
10036 if row == selection.start.row {
10037 selection.start.column += indent_delta.len;
10038 }
10039 if row == selection.end.row {
10040 selection.end.column += indent_delta.len;
10041 delta_for_end_row = indent_delta.len;
10042 }
10043 }
10044
10045 if selection.start.row == selection.end.row {
10046 delta_for_start_row + delta_for_end_row
10047 } else {
10048 delta_for_end_row
10049 }
10050 }
10051
10052 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10053 if self.read_only(cx) {
10054 return;
10055 }
10056 if self.mode.is_single_line() {
10057 cx.propagate();
10058 return;
10059 }
10060
10061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10063 let selections = self.selections.all::<Point>(cx);
10064 let mut deletion_ranges = Vec::new();
10065 let mut last_outdent = None;
10066 {
10067 let buffer = self.buffer.read(cx);
10068 let snapshot = buffer.snapshot(cx);
10069 for selection in &selections {
10070 let settings = buffer.language_settings_at(selection.start, cx);
10071 let tab_size = settings.tab_size.get();
10072 let mut rows = selection.spanned_rows(false, &display_map);
10073
10074 // Avoid re-outdenting a row that has already been outdented by a
10075 // previous selection.
10076 if let Some(last_row) = last_outdent {
10077 if last_row == rows.start {
10078 rows.start = rows.start.next_row();
10079 }
10080 }
10081 let has_multiple_rows = rows.len() > 1;
10082 for row in rows.iter_rows() {
10083 let indent_size = snapshot.indent_size_for_line(row);
10084 if indent_size.len > 0 {
10085 let deletion_len = match indent_size.kind {
10086 IndentKind::Space => {
10087 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10088 if columns_to_prev_tab_stop == 0 {
10089 tab_size
10090 } else {
10091 columns_to_prev_tab_stop
10092 }
10093 }
10094 IndentKind::Tab => 1,
10095 };
10096 let start = if has_multiple_rows
10097 || deletion_len > selection.start.column
10098 || indent_size.len < selection.start.column
10099 {
10100 0
10101 } else {
10102 selection.start.column - deletion_len
10103 };
10104 deletion_ranges.push(
10105 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10106 );
10107 last_outdent = Some(row);
10108 }
10109 }
10110 }
10111 }
10112
10113 self.transact(window, cx, |this, window, cx| {
10114 this.buffer.update(cx, |buffer, cx| {
10115 let empty_str: Arc<str> = Arc::default();
10116 buffer.edit(
10117 deletion_ranges
10118 .into_iter()
10119 .map(|range| (range, empty_str.clone())),
10120 None,
10121 cx,
10122 );
10123 });
10124 let selections = this.selections.all::<usize>(cx);
10125 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10126 });
10127 }
10128
10129 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10130 if self.read_only(cx) {
10131 return;
10132 }
10133 if self.mode.is_single_line() {
10134 cx.propagate();
10135 return;
10136 }
10137
10138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10139 let selections = self
10140 .selections
10141 .all::<usize>(cx)
10142 .into_iter()
10143 .map(|s| s.range());
10144
10145 self.transact(window, cx, |this, window, cx| {
10146 this.buffer.update(cx, |buffer, cx| {
10147 buffer.autoindent_ranges(selections, cx);
10148 });
10149 let selections = this.selections.all::<usize>(cx);
10150 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10151 });
10152 }
10153
10154 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10155 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10157 let selections = self.selections.all::<Point>(cx);
10158
10159 let mut new_cursors = Vec::new();
10160 let mut edit_ranges = Vec::new();
10161 let mut selections = selections.iter().peekable();
10162 while let Some(selection) = selections.next() {
10163 let mut rows = selection.spanned_rows(false, &display_map);
10164 let goal_display_column = selection.head().to_display_point(&display_map).column();
10165
10166 // Accumulate contiguous regions of rows that we want to delete.
10167 while let Some(next_selection) = selections.peek() {
10168 let next_rows = next_selection.spanned_rows(false, &display_map);
10169 if next_rows.start <= rows.end {
10170 rows.end = next_rows.end;
10171 selections.next().unwrap();
10172 } else {
10173 break;
10174 }
10175 }
10176
10177 let buffer = &display_map.buffer_snapshot;
10178 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10179 let edit_end;
10180 let cursor_buffer_row;
10181 if buffer.max_point().row >= rows.end.0 {
10182 // If there's a line after the range, delete the \n from the end of the row range
10183 // and position the cursor on the next line.
10184 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10185 cursor_buffer_row = rows.end;
10186 } else {
10187 // If there isn't a line after the range, delete the \n from the line before the
10188 // start of the row range and position the cursor there.
10189 edit_start = edit_start.saturating_sub(1);
10190 edit_end = buffer.len();
10191 cursor_buffer_row = rows.start.previous_row();
10192 }
10193
10194 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10195 *cursor.column_mut() =
10196 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10197
10198 new_cursors.push((
10199 selection.id,
10200 buffer.anchor_after(cursor.to_point(&display_map)),
10201 ));
10202 edit_ranges.push(edit_start..edit_end);
10203 }
10204
10205 self.transact(window, cx, |this, window, cx| {
10206 let buffer = this.buffer.update(cx, |buffer, cx| {
10207 let empty_str: Arc<str> = Arc::default();
10208 buffer.edit(
10209 edit_ranges
10210 .into_iter()
10211 .map(|range| (range, empty_str.clone())),
10212 None,
10213 cx,
10214 );
10215 buffer.snapshot(cx)
10216 });
10217 let new_selections = new_cursors
10218 .into_iter()
10219 .map(|(id, cursor)| {
10220 let cursor = cursor.to_point(&buffer);
10221 Selection {
10222 id,
10223 start: cursor,
10224 end: cursor,
10225 reversed: false,
10226 goal: SelectionGoal::None,
10227 }
10228 })
10229 .collect();
10230
10231 this.change_selections(Default::default(), window, cx, |s| {
10232 s.select(new_selections);
10233 });
10234 });
10235 }
10236
10237 pub fn join_lines_impl(
10238 &mut self,
10239 insert_whitespace: bool,
10240 window: &mut Window,
10241 cx: &mut Context<Self>,
10242 ) {
10243 if self.read_only(cx) {
10244 return;
10245 }
10246 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10247 for selection in self.selections.all::<Point>(cx) {
10248 let start = MultiBufferRow(selection.start.row);
10249 // Treat single line selections as if they include the next line. Otherwise this action
10250 // would do nothing for single line selections individual cursors.
10251 let end = if selection.start.row == selection.end.row {
10252 MultiBufferRow(selection.start.row + 1)
10253 } else {
10254 MultiBufferRow(selection.end.row)
10255 };
10256
10257 if let Some(last_row_range) = row_ranges.last_mut() {
10258 if start <= last_row_range.end {
10259 last_row_range.end = end;
10260 continue;
10261 }
10262 }
10263 row_ranges.push(start..end);
10264 }
10265
10266 let snapshot = self.buffer.read(cx).snapshot(cx);
10267 let mut cursor_positions = Vec::new();
10268 for row_range in &row_ranges {
10269 let anchor = snapshot.anchor_before(Point::new(
10270 row_range.end.previous_row().0,
10271 snapshot.line_len(row_range.end.previous_row()),
10272 ));
10273 cursor_positions.push(anchor..anchor);
10274 }
10275
10276 self.transact(window, cx, |this, window, cx| {
10277 for row_range in row_ranges.into_iter().rev() {
10278 for row in row_range.iter_rows().rev() {
10279 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10280 let next_line_row = row.next_row();
10281 let indent = snapshot.indent_size_for_line(next_line_row);
10282 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10283
10284 let replace =
10285 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10286 " "
10287 } else {
10288 ""
10289 };
10290
10291 this.buffer.update(cx, |buffer, cx| {
10292 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10293 });
10294 }
10295 }
10296
10297 this.change_selections(Default::default(), window, cx, |s| {
10298 s.select_anchor_ranges(cursor_positions)
10299 });
10300 });
10301 }
10302
10303 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10305 self.join_lines_impl(true, window, cx);
10306 }
10307
10308 pub fn sort_lines_case_sensitive(
10309 &mut self,
10310 _: &SortLinesCaseSensitive,
10311 window: &mut Window,
10312 cx: &mut Context<Self>,
10313 ) {
10314 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10315 }
10316
10317 pub fn sort_lines_by_length(
10318 &mut self,
10319 _: &SortLinesByLength,
10320 window: &mut Window,
10321 cx: &mut Context<Self>,
10322 ) {
10323 self.manipulate_immutable_lines(window, cx, |lines| {
10324 lines.sort_by_key(|&line| line.chars().count())
10325 })
10326 }
10327
10328 pub fn sort_lines_case_insensitive(
10329 &mut self,
10330 _: &SortLinesCaseInsensitive,
10331 window: &mut Window,
10332 cx: &mut Context<Self>,
10333 ) {
10334 self.manipulate_immutable_lines(window, cx, |lines| {
10335 lines.sort_by_key(|line| line.to_lowercase())
10336 })
10337 }
10338
10339 pub fn unique_lines_case_insensitive(
10340 &mut self,
10341 _: &UniqueLinesCaseInsensitive,
10342 window: &mut Window,
10343 cx: &mut Context<Self>,
10344 ) {
10345 self.manipulate_immutable_lines(window, cx, |lines| {
10346 let mut seen = HashSet::default();
10347 lines.retain(|line| seen.insert(line.to_lowercase()));
10348 })
10349 }
10350
10351 pub fn unique_lines_case_sensitive(
10352 &mut self,
10353 _: &UniqueLinesCaseSensitive,
10354 window: &mut Window,
10355 cx: &mut Context<Self>,
10356 ) {
10357 self.manipulate_immutable_lines(window, cx, |lines| {
10358 let mut seen = HashSet::default();
10359 lines.retain(|line| seen.insert(*line));
10360 })
10361 }
10362
10363 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10364 let Some(project) = self.project.clone() else {
10365 return;
10366 };
10367 self.reload(project, window, cx)
10368 .detach_and_notify_err(window, cx);
10369 }
10370
10371 pub fn restore_file(
10372 &mut self,
10373 _: &::git::RestoreFile,
10374 window: &mut Window,
10375 cx: &mut Context<Self>,
10376 ) {
10377 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10378 let mut buffer_ids = HashSet::default();
10379 let snapshot = self.buffer().read(cx).snapshot(cx);
10380 for selection in self.selections.all::<usize>(cx) {
10381 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10382 }
10383
10384 let buffer = self.buffer().read(cx);
10385 let ranges = buffer_ids
10386 .into_iter()
10387 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10388 .collect::<Vec<_>>();
10389
10390 self.restore_hunks_in_ranges(ranges, window, cx);
10391 }
10392
10393 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10395 let selections = self
10396 .selections
10397 .all(cx)
10398 .into_iter()
10399 .map(|s| s.range())
10400 .collect();
10401 self.restore_hunks_in_ranges(selections, window, cx);
10402 }
10403
10404 pub fn restore_hunks_in_ranges(
10405 &mut self,
10406 ranges: Vec<Range<Point>>,
10407 window: &mut Window,
10408 cx: &mut Context<Editor>,
10409 ) {
10410 let mut revert_changes = HashMap::default();
10411 let chunk_by = self
10412 .snapshot(window, cx)
10413 .hunks_for_ranges(ranges)
10414 .into_iter()
10415 .chunk_by(|hunk| hunk.buffer_id);
10416 for (buffer_id, hunks) in &chunk_by {
10417 let hunks = hunks.collect::<Vec<_>>();
10418 for hunk in &hunks {
10419 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10420 }
10421 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10422 }
10423 drop(chunk_by);
10424 if !revert_changes.is_empty() {
10425 self.transact(window, cx, |editor, window, cx| {
10426 editor.restore(revert_changes, window, cx);
10427 });
10428 }
10429 }
10430
10431 pub fn open_active_item_in_terminal(
10432 &mut self,
10433 _: &OpenInTerminal,
10434 window: &mut Window,
10435 cx: &mut Context<Self>,
10436 ) {
10437 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10438 let project_path = buffer.read(cx).project_path(cx)?;
10439 let project = self.project.as_ref()?.read(cx);
10440 let entry = project.entry_for_path(&project_path, cx)?;
10441 let parent = match &entry.canonical_path {
10442 Some(canonical_path) => canonical_path.to_path_buf(),
10443 None => project.absolute_path(&project_path, cx)?,
10444 }
10445 .parent()?
10446 .to_path_buf();
10447 Some(parent)
10448 }) {
10449 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10450 }
10451 }
10452
10453 fn set_breakpoint_context_menu(
10454 &mut self,
10455 display_row: DisplayRow,
10456 position: Option<Anchor>,
10457 clicked_point: gpui::Point<Pixels>,
10458 window: &mut Window,
10459 cx: &mut Context<Self>,
10460 ) {
10461 let source = self
10462 .buffer
10463 .read(cx)
10464 .snapshot(cx)
10465 .anchor_before(Point::new(display_row.0, 0u32));
10466
10467 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10468
10469 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10470 self,
10471 source,
10472 clicked_point,
10473 context_menu,
10474 window,
10475 cx,
10476 );
10477 }
10478
10479 fn add_edit_breakpoint_block(
10480 &mut self,
10481 anchor: Anchor,
10482 breakpoint: &Breakpoint,
10483 edit_action: BreakpointPromptEditAction,
10484 window: &mut Window,
10485 cx: &mut Context<Self>,
10486 ) {
10487 let weak_editor = cx.weak_entity();
10488 let bp_prompt = cx.new(|cx| {
10489 BreakpointPromptEditor::new(
10490 weak_editor,
10491 anchor,
10492 breakpoint.clone(),
10493 edit_action,
10494 window,
10495 cx,
10496 )
10497 });
10498
10499 let height = bp_prompt.update(cx, |this, cx| {
10500 this.prompt
10501 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10502 });
10503 let cloned_prompt = bp_prompt.clone();
10504 let blocks = vec![BlockProperties {
10505 style: BlockStyle::Sticky,
10506 placement: BlockPlacement::Above(anchor),
10507 height: Some(height),
10508 render: Arc::new(move |cx| {
10509 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10510 cloned_prompt.clone().into_any_element()
10511 }),
10512 priority: 0,
10513 }];
10514
10515 let focus_handle = bp_prompt.focus_handle(cx);
10516 window.focus(&focus_handle);
10517
10518 let block_ids = self.insert_blocks(blocks, None, cx);
10519 bp_prompt.update(cx, |prompt, _| {
10520 prompt.add_block_ids(block_ids);
10521 });
10522 }
10523
10524 pub(crate) fn breakpoint_at_row(
10525 &self,
10526 row: u32,
10527 window: &mut Window,
10528 cx: &mut Context<Self>,
10529 ) -> Option<(Anchor, Breakpoint)> {
10530 let snapshot = self.snapshot(window, cx);
10531 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10532
10533 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10534 }
10535
10536 pub(crate) fn breakpoint_at_anchor(
10537 &self,
10538 breakpoint_position: Anchor,
10539 snapshot: &EditorSnapshot,
10540 cx: &mut Context<Self>,
10541 ) -> Option<(Anchor, Breakpoint)> {
10542 let project = self.project.clone()?;
10543
10544 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10545 snapshot
10546 .buffer_snapshot
10547 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10548 })?;
10549
10550 let enclosing_excerpt = breakpoint_position.excerpt_id;
10551 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10552 let buffer_snapshot = buffer.read(cx).snapshot();
10553
10554 let row = buffer_snapshot
10555 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10556 .row;
10557
10558 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10559 let anchor_end = snapshot
10560 .buffer_snapshot
10561 .anchor_after(Point::new(row, line_len));
10562
10563 let bp = self
10564 .breakpoint_store
10565 .as_ref()?
10566 .read_with(cx, |breakpoint_store, cx| {
10567 breakpoint_store
10568 .breakpoints(
10569 &buffer,
10570 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10571 &buffer_snapshot,
10572 cx,
10573 )
10574 .next()
10575 .and_then(|(bp, _)| {
10576 let breakpoint_row = buffer_snapshot
10577 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10578 .row;
10579
10580 if breakpoint_row == row {
10581 snapshot
10582 .buffer_snapshot
10583 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10584 .map(|position| (position, bp.bp.clone()))
10585 } else {
10586 None
10587 }
10588 })
10589 });
10590 bp
10591 }
10592
10593 pub fn edit_log_breakpoint(
10594 &mut self,
10595 _: &EditLogBreakpoint,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10600 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10601 message: None,
10602 state: BreakpointState::Enabled,
10603 condition: None,
10604 hit_condition: None,
10605 });
10606
10607 self.add_edit_breakpoint_block(
10608 anchor,
10609 &breakpoint,
10610 BreakpointPromptEditAction::Log,
10611 window,
10612 cx,
10613 );
10614 }
10615 }
10616
10617 fn breakpoints_at_cursors(
10618 &self,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10622 let snapshot = self.snapshot(window, cx);
10623 let cursors = self
10624 .selections
10625 .disjoint_anchors()
10626 .into_iter()
10627 .map(|selection| {
10628 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10629
10630 let breakpoint_position = self
10631 .breakpoint_at_row(cursor_position.row, window, cx)
10632 .map(|bp| bp.0)
10633 .unwrap_or_else(|| {
10634 snapshot
10635 .display_snapshot
10636 .buffer_snapshot
10637 .anchor_after(Point::new(cursor_position.row, 0))
10638 });
10639
10640 let breakpoint = self
10641 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10642 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10643
10644 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10645 })
10646 // 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.
10647 .collect::<HashMap<Anchor, _>>();
10648
10649 cursors.into_iter().collect()
10650 }
10651
10652 pub fn enable_breakpoint(
10653 &mut self,
10654 _: &crate::actions::EnableBreakpoint,
10655 window: &mut Window,
10656 cx: &mut Context<Self>,
10657 ) {
10658 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10659 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10660 continue;
10661 };
10662 self.edit_breakpoint_at_anchor(
10663 anchor,
10664 breakpoint,
10665 BreakpointEditAction::InvertState,
10666 cx,
10667 );
10668 }
10669 }
10670
10671 pub fn disable_breakpoint(
10672 &mut self,
10673 _: &crate::actions::DisableBreakpoint,
10674 window: &mut Window,
10675 cx: &mut Context<Self>,
10676 ) {
10677 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10678 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10679 continue;
10680 };
10681 self.edit_breakpoint_at_anchor(
10682 anchor,
10683 breakpoint,
10684 BreakpointEditAction::InvertState,
10685 cx,
10686 );
10687 }
10688 }
10689
10690 pub fn toggle_breakpoint(
10691 &mut self,
10692 _: &crate::actions::ToggleBreakpoint,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10697 if let Some(breakpoint) = breakpoint {
10698 self.edit_breakpoint_at_anchor(
10699 anchor,
10700 breakpoint,
10701 BreakpointEditAction::Toggle,
10702 cx,
10703 );
10704 } else {
10705 self.edit_breakpoint_at_anchor(
10706 anchor,
10707 Breakpoint::new_standard(),
10708 BreakpointEditAction::Toggle,
10709 cx,
10710 );
10711 }
10712 }
10713 }
10714
10715 pub fn edit_breakpoint_at_anchor(
10716 &mut self,
10717 breakpoint_position: Anchor,
10718 breakpoint: Breakpoint,
10719 edit_action: BreakpointEditAction,
10720 cx: &mut Context<Self>,
10721 ) {
10722 let Some(breakpoint_store) = &self.breakpoint_store else {
10723 return;
10724 };
10725
10726 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10727 if breakpoint_position == Anchor::min() {
10728 self.buffer()
10729 .read(cx)
10730 .excerpt_buffer_ids()
10731 .into_iter()
10732 .next()
10733 } else {
10734 None
10735 }
10736 }) else {
10737 return;
10738 };
10739
10740 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10741 return;
10742 };
10743
10744 breakpoint_store.update(cx, |breakpoint_store, cx| {
10745 breakpoint_store.toggle_breakpoint(
10746 buffer,
10747 BreakpointWithPosition {
10748 position: breakpoint_position.text_anchor,
10749 bp: breakpoint,
10750 },
10751 edit_action,
10752 cx,
10753 );
10754 });
10755
10756 cx.notify();
10757 }
10758
10759 #[cfg(any(test, feature = "test-support"))]
10760 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10761 self.breakpoint_store.clone()
10762 }
10763
10764 pub fn prepare_restore_change(
10765 &self,
10766 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10767 hunk: &MultiBufferDiffHunk,
10768 cx: &mut App,
10769 ) -> Option<()> {
10770 if hunk.is_created_file() {
10771 return None;
10772 }
10773 let buffer = self.buffer.read(cx);
10774 let diff = buffer.diff_for(hunk.buffer_id)?;
10775 let buffer = buffer.buffer(hunk.buffer_id)?;
10776 let buffer = buffer.read(cx);
10777 let original_text = diff
10778 .read(cx)
10779 .base_text()
10780 .as_rope()
10781 .slice(hunk.diff_base_byte_range.clone());
10782 let buffer_snapshot = buffer.snapshot();
10783 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10784 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10785 probe
10786 .0
10787 .start
10788 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10789 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10790 }) {
10791 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10792 Some(())
10793 } else {
10794 None
10795 }
10796 }
10797
10798 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10799 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10800 }
10801
10802 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10803 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10804 }
10805
10806 fn manipulate_lines<M>(
10807 &mut self,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 mut manipulate: M,
10811 ) where
10812 M: FnMut(&str) -> LineManipulationResult,
10813 {
10814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10815
10816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10817 let buffer = self.buffer.read(cx).snapshot(cx);
10818
10819 let mut edits = Vec::new();
10820
10821 let selections = self.selections.all::<Point>(cx);
10822 let mut selections = selections.iter().peekable();
10823 let mut contiguous_row_selections = Vec::new();
10824 let mut new_selections = Vec::new();
10825 let mut added_lines = 0;
10826 let mut removed_lines = 0;
10827
10828 while let Some(selection) = selections.next() {
10829 let (start_row, end_row) = consume_contiguous_rows(
10830 &mut contiguous_row_selections,
10831 selection,
10832 &display_map,
10833 &mut selections,
10834 );
10835
10836 let start_point = Point::new(start_row.0, 0);
10837 let end_point = Point::new(
10838 end_row.previous_row().0,
10839 buffer.line_len(end_row.previous_row()),
10840 );
10841 let text = buffer
10842 .text_for_range(start_point..end_point)
10843 .collect::<String>();
10844
10845 let LineManipulationResult {
10846 new_text,
10847 line_count_before,
10848 line_count_after,
10849 } = manipulate(&text);
10850
10851 edits.push((start_point..end_point, new_text));
10852
10853 // Selections must change based on added and removed line count
10854 let start_row =
10855 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10856 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10857 new_selections.push(Selection {
10858 id: selection.id,
10859 start: start_row,
10860 end: end_row,
10861 goal: SelectionGoal::None,
10862 reversed: selection.reversed,
10863 });
10864
10865 if line_count_after > line_count_before {
10866 added_lines += line_count_after - line_count_before;
10867 } else if line_count_before > line_count_after {
10868 removed_lines += line_count_before - line_count_after;
10869 }
10870 }
10871
10872 self.transact(window, cx, |this, window, cx| {
10873 let buffer = this.buffer.update(cx, |buffer, cx| {
10874 buffer.edit(edits, None, cx);
10875 buffer.snapshot(cx)
10876 });
10877
10878 // Recalculate offsets on newly edited buffer
10879 let new_selections = new_selections
10880 .iter()
10881 .map(|s| {
10882 let start_point = Point::new(s.start.0, 0);
10883 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10884 Selection {
10885 id: s.id,
10886 start: buffer.point_to_offset(start_point),
10887 end: buffer.point_to_offset(end_point),
10888 goal: s.goal,
10889 reversed: s.reversed,
10890 }
10891 })
10892 .collect();
10893
10894 this.change_selections(Default::default(), window, cx, |s| {
10895 s.select(new_selections);
10896 });
10897
10898 this.request_autoscroll(Autoscroll::fit(), cx);
10899 });
10900 }
10901
10902 fn manipulate_immutable_lines<Fn>(
10903 &mut self,
10904 window: &mut Window,
10905 cx: &mut Context<Self>,
10906 mut callback: Fn,
10907 ) where
10908 Fn: FnMut(&mut Vec<&str>),
10909 {
10910 self.manipulate_lines(window, cx, |text| {
10911 let mut lines: Vec<&str> = text.split('\n').collect();
10912 let line_count_before = lines.len();
10913
10914 callback(&mut lines);
10915
10916 LineManipulationResult {
10917 new_text: lines.join("\n"),
10918 line_count_before,
10919 line_count_after: lines.len(),
10920 }
10921 });
10922 }
10923
10924 fn manipulate_mutable_lines<Fn>(
10925 &mut self,
10926 window: &mut Window,
10927 cx: &mut Context<Self>,
10928 mut callback: Fn,
10929 ) where
10930 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10931 {
10932 self.manipulate_lines(window, cx, |text| {
10933 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10934 let line_count_before = lines.len();
10935
10936 callback(&mut lines);
10937
10938 LineManipulationResult {
10939 new_text: lines.join("\n"),
10940 line_count_before,
10941 line_count_after: lines.len(),
10942 }
10943 });
10944 }
10945
10946 pub fn convert_indentation_to_spaces(
10947 &mut self,
10948 _: &ConvertIndentationToSpaces,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 let settings = self.buffer.read(cx).language_settings(cx);
10953 let tab_size = settings.tab_size.get() as usize;
10954
10955 self.manipulate_mutable_lines(window, cx, |lines| {
10956 // Allocates a reasonably sized scratch buffer once for the whole loop
10957 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10958 // Avoids recomputing spaces that could be inserted many times
10959 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10960 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10961 .collect();
10962
10963 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10964 let mut chars = line.as_ref().chars();
10965 let mut col = 0;
10966 let mut changed = false;
10967
10968 while let Some(ch) = chars.next() {
10969 match ch {
10970 ' ' => {
10971 reindented_line.push(' ');
10972 col += 1;
10973 }
10974 '\t' => {
10975 // \t are converted to spaces depending on the current column
10976 let spaces_len = tab_size - (col % tab_size);
10977 reindented_line.extend(&space_cache[spaces_len - 1]);
10978 col += spaces_len;
10979 changed = true;
10980 }
10981 _ => {
10982 // If we dont append before break, the character is consumed
10983 reindented_line.push(ch);
10984 break;
10985 }
10986 }
10987 }
10988
10989 if !changed {
10990 reindented_line.clear();
10991 continue;
10992 }
10993 // Append the rest of the line and replace old reference with new one
10994 reindented_line.extend(chars);
10995 *line = Cow::Owned(reindented_line.clone());
10996 reindented_line.clear();
10997 }
10998 });
10999 }
11000
11001 pub fn convert_indentation_to_tabs(
11002 &mut self,
11003 _: &ConvertIndentationToTabs,
11004 window: &mut Window,
11005 cx: &mut Context<Self>,
11006 ) {
11007 let settings = self.buffer.read(cx).language_settings(cx);
11008 let tab_size = settings.tab_size.get() as usize;
11009
11010 self.manipulate_mutable_lines(window, cx, |lines| {
11011 // Allocates a reasonably sized buffer once for the whole loop
11012 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11013 // Avoids recomputing spaces that could be inserted many times
11014 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11015 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11016 .collect();
11017
11018 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11019 let mut chars = line.chars();
11020 let mut spaces_count = 0;
11021 let mut first_non_indent_char = None;
11022 let mut changed = false;
11023
11024 while let Some(ch) = chars.next() {
11025 match ch {
11026 ' ' => {
11027 // Keep track of spaces. Append \t when we reach tab_size
11028 spaces_count += 1;
11029 changed = true;
11030 if spaces_count == tab_size {
11031 reindented_line.push('\t');
11032 spaces_count = 0;
11033 }
11034 }
11035 '\t' => {
11036 reindented_line.push('\t');
11037 spaces_count = 0;
11038 }
11039 _ => {
11040 // Dont append it yet, we might have remaining spaces
11041 first_non_indent_char = Some(ch);
11042 break;
11043 }
11044 }
11045 }
11046
11047 if !changed {
11048 reindented_line.clear();
11049 continue;
11050 }
11051 // Remaining spaces that didn't make a full tab stop
11052 if spaces_count > 0 {
11053 reindented_line.extend(&space_cache[spaces_count - 1]);
11054 }
11055 // If we consume an extra character that was not indentation, add it back
11056 if let Some(extra_char) = first_non_indent_char {
11057 reindented_line.push(extra_char);
11058 }
11059 // Append the rest of the line and replace old reference with new one
11060 reindented_line.extend(chars);
11061 *line = Cow::Owned(reindented_line.clone());
11062 reindented_line.clear();
11063 }
11064 });
11065 }
11066
11067 pub fn convert_to_upper_case(
11068 &mut self,
11069 _: &ConvertToUpperCase,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 self.manipulate_text(window, cx, |text| text.to_uppercase())
11074 }
11075
11076 pub fn convert_to_lower_case(
11077 &mut self,
11078 _: &ConvertToLowerCase,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 self.manipulate_text(window, cx, |text| text.to_lowercase())
11083 }
11084
11085 pub fn convert_to_title_case(
11086 &mut self,
11087 _: &ConvertToTitleCase,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.manipulate_text(window, cx, |text| {
11092 text.split('\n')
11093 .map(|line| line.to_case(Case::Title))
11094 .join("\n")
11095 })
11096 }
11097
11098 pub fn convert_to_snake_case(
11099 &mut self,
11100 _: &ConvertToSnakeCase,
11101 window: &mut Window,
11102 cx: &mut Context<Self>,
11103 ) {
11104 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11105 }
11106
11107 pub fn convert_to_kebab_case(
11108 &mut self,
11109 _: &ConvertToKebabCase,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) {
11113 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11114 }
11115
11116 pub fn convert_to_upper_camel_case(
11117 &mut self,
11118 _: &ConvertToUpperCamelCase,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 self.manipulate_text(window, cx, |text| {
11123 text.split('\n')
11124 .map(|line| line.to_case(Case::UpperCamel))
11125 .join("\n")
11126 })
11127 }
11128
11129 pub fn convert_to_lower_camel_case(
11130 &mut self,
11131 _: &ConvertToLowerCamelCase,
11132 window: &mut Window,
11133 cx: &mut Context<Self>,
11134 ) {
11135 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11136 }
11137
11138 pub fn convert_to_opposite_case(
11139 &mut self,
11140 _: &ConvertToOppositeCase,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 ) {
11144 self.manipulate_text(window, cx, |text| {
11145 text.chars()
11146 .fold(String::with_capacity(text.len()), |mut t, c| {
11147 if c.is_uppercase() {
11148 t.extend(c.to_lowercase());
11149 } else {
11150 t.extend(c.to_uppercase());
11151 }
11152 t
11153 })
11154 })
11155 }
11156
11157 pub fn convert_to_sentence_case(
11158 &mut self,
11159 _: &ConvertToSentenceCase,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 ) {
11163 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11164 }
11165
11166 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11167 self.manipulate_text(window, cx, |text| {
11168 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11169 if has_upper_case_characters {
11170 text.to_lowercase()
11171 } else {
11172 text.to_uppercase()
11173 }
11174 })
11175 }
11176
11177 pub fn convert_to_rot13(
11178 &mut self,
11179 _: &ConvertToRot13,
11180 window: &mut Window,
11181 cx: &mut Context<Self>,
11182 ) {
11183 self.manipulate_text(window, cx, |text| {
11184 text.chars()
11185 .map(|c| match c {
11186 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11187 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11188 _ => c,
11189 })
11190 .collect()
11191 })
11192 }
11193
11194 pub fn convert_to_rot47(
11195 &mut self,
11196 _: &ConvertToRot47,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| {
11201 text.chars()
11202 .map(|c| {
11203 let code_point = c as u32;
11204 if code_point >= 33 && code_point <= 126 {
11205 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11206 }
11207 c
11208 })
11209 .collect()
11210 })
11211 }
11212
11213 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11214 where
11215 Fn: FnMut(&str) -> String,
11216 {
11217 let buffer = self.buffer.read(cx).snapshot(cx);
11218
11219 let mut new_selections = Vec::new();
11220 let mut edits = Vec::new();
11221 let mut selection_adjustment = 0i32;
11222
11223 for selection in self.selections.all::<usize>(cx) {
11224 let selection_is_empty = selection.is_empty();
11225
11226 let (start, end) = if selection_is_empty {
11227 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11228 (word_range.start, word_range.end)
11229 } else {
11230 (selection.start, selection.end)
11231 };
11232
11233 let text = buffer.text_for_range(start..end).collect::<String>();
11234 let old_length = text.len() as i32;
11235 let text = callback(&text);
11236
11237 new_selections.push(Selection {
11238 start: (start as i32 - selection_adjustment) as usize,
11239 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11240 goal: SelectionGoal::None,
11241 ..selection
11242 });
11243
11244 selection_adjustment += old_length - text.len() as i32;
11245
11246 edits.push((start..end, text));
11247 }
11248
11249 self.transact(window, cx, |this, window, cx| {
11250 this.buffer.update(cx, |buffer, cx| {
11251 buffer.edit(edits, None, cx);
11252 });
11253
11254 this.change_selections(Default::default(), window, cx, |s| {
11255 s.select(new_selections);
11256 });
11257
11258 this.request_autoscroll(Autoscroll::fit(), cx);
11259 });
11260 }
11261
11262 pub fn move_selection_on_drop(
11263 &mut self,
11264 selection: &Selection<Anchor>,
11265 target: DisplayPoint,
11266 is_cut: bool,
11267 window: &mut Window,
11268 cx: &mut Context<Self>,
11269 ) {
11270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11271 let buffer = &display_map.buffer_snapshot;
11272 let mut edits = Vec::new();
11273 let insert_point = display_map
11274 .clip_point(target, Bias::Left)
11275 .to_point(&display_map);
11276 let text = buffer
11277 .text_for_range(selection.start..selection.end)
11278 .collect::<String>();
11279 if is_cut {
11280 edits.push(((selection.start..selection.end), String::new()));
11281 }
11282 let insert_anchor = buffer.anchor_before(insert_point);
11283 edits.push(((insert_anchor..insert_anchor), text));
11284 let last_edit_start = insert_anchor.bias_left(buffer);
11285 let last_edit_end = insert_anchor.bias_right(buffer);
11286 self.transact(window, cx, |this, window, cx| {
11287 this.buffer.update(cx, |buffer, cx| {
11288 buffer.edit(edits, None, cx);
11289 });
11290 this.change_selections(Default::default(), window, cx, |s| {
11291 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11292 });
11293 });
11294 }
11295
11296 pub fn clear_selection_drag_state(&mut self) {
11297 self.selection_drag_state = SelectionDragState::None;
11298 }
11299
11300 pub fn duplicate(
11301 &mut self,
11302 upwards: bool,
11303 whole_lines: bool,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11308
11309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11310 let buffer = &display_map.buffer_snapshot;
11311 let selections = self.selections.all::<Point>(cx);
11312
11313 let mut edits = Vec::new();
11314 let mut selections_iter = selections.iter().peekable();
11315 while let Some(selection) = selections_iter.next() {
11316 let mut rows = selection.spanned_rows(false, &display_map);
11317 // duplicate line-wise
11318 if whole_lines || selection.start == selection.end {
11319 // Avoid duplicating the same lines twice.
11320 while let Some(next_selection) = selections_iter.peek() {
11321 let next_rows = next_selection.spanned_rows(false, &display_map);
11322 if next_rows.start < rows.end {
11323 rows.end = next_rows.end;
11324 selections_iter.next().unwrap();
11325 } else {
11326 break;
11327 }
11328 }
11329
11330 // Copy the text from the selected row region and splice it either at the start
11331 // or end of the region.
11332 let start = Point::new(rows.start.0, 0);
11333 let end = Point::new(
11334 rows.end.previous_row().0,
11335 buffer.line_len(rows.end.previous_row()),
11336 );
11337 let text = buffer
11338 .text_for_range(start..end)
11339 .chain(Some("\n"))
11340 .collect::<String>();
11341 let insert_location = if upwards {
11342 Point::new(rows.end.0, 0)
11343 } else {
11344 start
11345 };
11346 edits.push((insert_location..insert_location, text));
11347 } else {
11348 // duplicate character-wise
11349 let start = selection.start;
11350 let end = selection.end;
11351 let text = buffer.text_for_range(start..end).collect::<String>();
11352 edits.push((selection.end..selection.end, text));
11353 }
11354 }
11355
11356 self.transact(window, cx, |this, _, cx| {
11357 this.buffer.update(cx, |buffer, cx| {
11358 buffer.edit(edits, None, cx);
11359 });
11360
11361 this.request_autoscroll(Autoscroll::fit(), cx);
11362 });
11363 }
11364
11365 pub fn duplicate_line_up(
11366 &mut self,
11367 _: &DuplicateLineUp,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) {
11371 self.duplicate(true, true, window, cx);
11372 }
11373
11374 pub fn duplicate_line_down(
11375 &mut self,
11376 _: &DuplicateLineDown,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 self.duplicate(false, true, window, cx);
11381 }
11382
11383 pub fn duplicate_selection(
11384 &mut self,
11385 _: &DuplicateSelection,
11386 window: &mut Window,
11387 cx: &mut Context<Self>,
11388 ) {
11389 self.duplicate(false, false, window, cx);
11390 }
11391
11392 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11394 if self.mode.is_single_line() {
11395 cx.propagate();
11396 return;
11397 }
11398
11399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11400 let buffer = self.buffer.read(cx).snapshot(cx);
11401
11402 let mut edits = Vec::new();
11403 let mut unfold_ranges = Vec::new();
11404 let mut refold_creases = Vec::new();
11405
11406 let selections = self.selections.all::<Point>(cx);
11407 let mut selections = selections.iter().peekable();
11408 let mut contiguous_row_selections = Vec::new();
11409 let mut new_selections = Vec::new();
11410
11411 while let Some(selection) = selections.next() {
11412 // Find all the selections that span a contiguous row range
11413 let (start_row, end_row) = consume_contiguous_rows(
11414 &mut contiguous_row_selections,
11415 selection,
11416 &display_map,
11417 &mut selections,
11418 );
11419
11420 // Move the text spanned by the row range to be before the line preceding the row range
11421 if start_row.0 > 0 {
11422 let range_to_move = Point::new(
11423 start_row.previous_row().0,
11424 buffer.line_len(start_row.previous_row()),
11425 )
11426 ..Point::new(
11427 end_row.previous_row().0,
11428 buffer.line_len(end_row.previous_row()),
11429 );
11430 let insertion_point = display_map
11431 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11432 .0;
11433
11434 // Don't move lines across excerpts
11435 if buffer
11436 .excerpt_containing(insertion_point..range_to_move.end)
11437 .is_some()
11438 {
11439 let text = buffer
11440 .text_for_range(range_to_move.clone())
11441 .flat_map(|s| s.chars())
11442 .skip(1)
11443 .chain(['\n'])
11444 .collect::<String>();
11445
11446 edits.push((
11447 buffer.anchor_after(range_to_move.start)
11448 ..buffer.anchor_before(range_to_move.end),
11449 String::new(),
11450 ));
11451 let insertion_anchor = buffer.anchor_after(insertion_point);
11452 edits.push((insertion_anchor..insertion_anchor, text));
11453
11454 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11455
11456 // Move selections up
11457 new_selections.extend(contiguous_row_selections.drain(..).map(
11458 |mut selection| {
11459 selection.start.row -= row_delta;
11460 selection.end.row -= row_delta;
11461 selection
11462 },
11463 ));
11464
11465 // Move folds up
11466 unfold_ranges.push(range_to_move.clone());
11467 for fold in display_map.folds_in_range(
11468 buffer.anchor_before(range_to_move.start)
11469 ..buffer.anchor_after(range_to_move.end),
11470 ) {
11471 let mut start = fold.range.start.to_point(&buffer);
11472 let mut end = fold.range.end.to_point(&buffer);
11473 start.row -= row_delta;
11474 end.row -= row_delta;
11475 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11476 }
11477 }
11478 }
11479
11480 // If we didn't move line(s), preserve the existing selections
11481 new_selections.append(&mut contiguous_row_selections);
11482 }
11483
11484 self.transact(window, cx, |this, window, cx| {
11485 this.unfold_ranges(&unfold_ranges, true, true, cx);
11486 this.buffer.update(cx, |buffer, cx| {
11487 for (range, text) in edits {
11488 buffer.edit([(range, text)], None, cx);
11489 }
11490 });
11491 this.fold_creases(refold_creases, true, window, cx);
11492 this.change_selections(Default::default(), window, cx, |s| {
11493 s.select(new_selections);
11494 })
11495 });
11496 }
11497
11498 pub fn move_line_down(
11499 &mut self,
11500 _: &MoveLineDown,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505 if self.mode.is_single_line() {
11506 cx.propagate();
11507 return;
11508 }
11509
11510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11511 let buffer = self.buffer.read(cx).snapshot(cx);
11512
11513 let mut edits = Vec::new();
11514 let mut unfold_ranges = Vec::new();
11515 let mut refold_creases = Vec::new();
11516
11517 let selections = self.selections.all::<Point>(cx);
11518 let mut selections = selections.iter().peekable();
11519 let mut contiguous_row_selections = Vec::new();
11520 let mut new_selections = Vec::new();
11521
11522 while let Some(selection) = selections.next() {
11523 // Find all the selections that span a contiguous row range
11524 let (start_row, end_row) = consume_contiguous_rows(
11525 &mut contiguous_row_selections,
11526 selection,
11527 &display_map,
11528 &mut selections,
11529 );
11530
11531 // Move the text spanned by the row range to be after the last line of the row range
11532 if end_row.0 <= buffer.max_point().row {
11533 let range_to_move =
11534 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11535 let insertion_point = display_map
11536 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11537 .0;
11538
11539 // Don't move lines across excerpt boundaries
11540 if buffer
11541 .excerpt_containing(range_to_move.start..insertion_point)
11542 .is_some()
11543 {
11544 let mut text = String::from("\n");
11545 text.extend(buffer.text_for_range(range_to_move.clone()));
11546 text.pop(); // Drop trailing newline
11547 edits.push((
11548 buffer.anchor_after(range_to_move.start)
11549 ..buffer.anchor_before(range_to_move.end),
11550 String::new(),
11551 ));
11552 let insertion_anchor = buffer.anchor_after(insertion_point);
11553 edits.push((insertion_anchor..insertion_anchor, text));
11554
11555 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11556
11557 // Move selections down
11558 new_selections.extend(contiguous_row_selections.drain(..).map(
11559 |mut selection| {
11560 selection.start.row += row_delta;
11561 selection.end.row += row_delta;
11562 selection
11563 },
11564 ));
11565
11566 // Move folds down
11567 unfold_ranges.push(range_to_move.clone());
11568 for fold in display_map.folds_in_range(
11569 buffer.anchor_before(range_to_move.start)
11570 ..buffer.anchor_after(range_to_move.end),
11571 ) {
11572 let mut start = fold.range.start.to_point(&buffer);
11573 let mut end = fold.range.end.to_point(&buffer);
11574 start.row += row_delta;
11575 end.row += row_delta;
11576 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11577 }
11578 }
11579 }
11580
11581 // If we didn't move line(s), preserve the existing selections
11582 new_selections.append(&mut contiguous_row_selections);
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 this.unfold_ranges(&unfold_ranges, true, true, cx);
11587 this.buffer.update(cx, |buffer, cx| {
11588 for (range, text) in edits {
11589 buffer.edit([(range, text)], None, cx);
11590 }
11591 });
11592 this.fold_creases(refold_creases, true, window, cx);
11593 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11594 });
11595 }
11596
11597 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11599 let text_layout_details = &self.text_layout_details(window);
11600 self.transact(window, cx, |this, window, cx| {
11601 let edits = this.change_selections(Default::default(), window, cx, |s| {
11602 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11603 s.move_with(|display_map, selection| {
11604 if !selection.is_empty() {
11605 return;
11606 }
11607
11608 let mut head = selection.head();
11609 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11610 if head.column() == display_map.line_len(head.row()) {
11611 transpose_offset = display_map
11612 .buffer_snapshot
11613 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11614 }
11615
11616 if transpose_offset == 0 {
11617 return;
11618 }
11619
11620 *head.column_mut() += 1;
11621 head = display_map.clip_point(head, Bias::Right);
11622 let goal = SelectionGoal::HorizontalPosition(
11623 display_map
11624 .x_for_display_point(head, text_layout_details)
11625 .into(),
11626 );
11627 selection.collapse_to(head, goal);
11628
11629 let transpose_start = display_map
11630 .buffer_snapshot
11631 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11632 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11633 let transpose_end = display_map
11634 .buffer_snapshot
11635 .clip_offset(transpose_offset + 1, Bias::Right);
11636 if let Some(ch) =
11637 display_map.buffer_snapshot.chars_at(transpose_start).next()
11638 {
11639 edits.push((transpose_start..transpose_offset, String::new()));
11640 edits.push((transpose_end..transpose_end, ch.to_string()));
11641 }
11642 }
11643 });
11644 edits
11645 });
11646 this.buffer
11647 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11648 let selections = this.selections.all::<usize>(cx);
11649 this.change_selections(Default::default(), window, cx, |s| {
11650 s.select(selections);
11651 });
11652 });
11653 }
11654
11655 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11657 if self.mode.is_single_line() {
11658 cx.propagate();
11659 return;
11660 }
11661
11662 self.rewrap_impl(RewrapOptions::default(), cx)
11663 }
11664
11665 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11666 let buffer = self.buffer.read(cx).snapshot(cx);
11667 let selections = self.selections.all::<Point>(cx);
11668
11669 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11670 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11671 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11672 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11673 .peekable();
11674
11675 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11676 row
11677 } else {
11678 return Vec::new();
11679 };
11680
11681 let language_settings = buffer.language_settings_at(selection.head(), cx);
11682 let language_scope = buffer.language_scope_at(selection.head());
11683
11684 let indent_and_prefix_for_row =
11685 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11686 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11687 let (comment_prefix, rewrap_prefix) =
11688 if let Some(language_scope) = &language_scope {
11689 let indent_end = Point::new(row, indent.len);
11690 let comment_prefix = language_scope
11691 .line_comment_prefixes()
11692 .iter()
11693 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11694 .map(|prefix| prefix.to_string());
11695 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11696 let line_text_after_indent = buffer
11697 .text_for_range(indent_end..line_end)
11698 .collect::<String>();
11699 let rewrap_prefix = language_scope
11700 .rewrap_prefixes()
11701 .iter()
11702 .find_map(|prefix_regex| {
11703 prefix_regex.find(&line_text_after_indent).map(|mat| {
11704 if mat.start() == 0 {
11705 Some(mat.as_str().to_string())
11706 } else {
11707 None
11708 }
11709 })
11710 })
11711 .flatten();
11712 (comment_prefix, rewrap_prefix)
11713 } else {
11714 (None, None)
11715 };
11716 (indent, comment_prefix, rewrap_prefix)
11717 };
11718
11719 let mut ranges = Vec::new();
11720 let from_empty_selection = selection.is_empty();
11721
11722 let mut current_range_start = first_row;
11723 let mut prev_row = first_row;
11724 let (
11725 mut current_range_indent,
11726 mut current_range_comment_prefix,
11727 mut current_range_rewrap_prefix,
11728 ) = indent_and_prefix_for_row(first_row);
11729
11730 for row in non_blank_rows_iter.skip(1) {
11731 let has_paragraph_break = row > prev_row + 1;
11732
11733 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11734 indent_and_prefix_for_row(row);
11735
11736 let has_indent_change = row_indent != current_range_indent;
11737 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11738
11739 let has_boundary_change = has_comment_change
11740 || row_rewrap_prefix.is_some()
11741 || (has_indent_change && current_range_comment_prefix.is_some());
11742
11743 if has_paragraph_break || has_boundary_change {
11744 ranges.push((
11745 language_settings.clone(),
11746 Point::new(current_range_start, 0)
11747 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11748 current_range_indent,
11749 current_range_comment_prefix.clone(),
11750 current_range_rewrap_prefix.clone(),
11751 from_empty_selection,
11752 ));
11753 current_range_start = row;
11754 current_range_indent = row_indent;
11755 current_range_comment_prefix = row_comment_prefix;
11756 current_range_rewrap_prefix = row_rewrap_prefix;
11757 }
11758 prev_row = row;
11759 }
11760
11761 ranges.push((
11762 language_settings.clone(),
11763 Point::new(current_range_start, 0)
11764 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11765 current_range_indent,
11766 current_range_comment_prefix,
11767 current_range_rewrap_prefix,
11768 from_empty_selection,
11769 ));
11770
11771 ranges
11772 });
11773
11774 let mut edits = Vec::new();
11775 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11776
11777 for (
11778 language_settings,
11779 wrap_range,
11780 indent_size,
11781 comment_prefix,
11782 rewrap_prefix,
11783 from_empty_selection,
11784 ) in wrap_ranges
11785 {
11786 let mut start_row = wrap_range.start.row;
11787 let mut end_row = wrap_range.end.row;
11788
11789 // Skip selections that overlap with a range that has already been rewrapped.
11790 let selection_range = start_row..end_row;
11791 if rewrapped_row_ranges
11792 .iter()
11793 .any(|range| range.overlaps(&selection_range))
11794 {
11795 continue;
11796 }
11797
11798 let tab_size = language_settings.tab_size;
11799
11800 let indent_prefix = indent_size.chars().collect::<String>();
11801 let mut line_prefix = indent_prefix.clone();
11802 let mut inside_comment = false;
11803 if let Some(prefix) = &comment_prefix {
11804 line_prefix.push_str(prefix);
11805 inside_comment = true;
11806 }
11807 if let Some(prefix) = &rewrap_prefix {
11808 line_prefix.push_str(prefix);
11809 }
11810
11811 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11812 RewrapBehavior::InComments => inside_comment,
11813 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11814 RewrapBehavior::Anywhere => true,
11815 };
11816
11817 let should_rewrap = options.override_language_settings
11818 || allow_rewrap_based_on_language
11819 || self.hard_wrap.is_some();
11820 if !should_rewrap {
11821 continue;
11822 }
11823
11824 if from_empty_selection {
11825 'expand_upwards: while start_row > 0 {
11826 let prev_row = start_row - 1;
11827 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11828 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11829 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11830 {
11831 start_row = prev_row;
11832 } else {
11833 break 'expand_upwards;
11834 }
11835 }
11836
11837 'expand_downwards: while end_row < buffer.max_point().row {
11838 let next_row = end_row + 1;
11839 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11840 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11841 && !buffer.is_line_blank(MultiBufferRow(next_row))
11842 {
11843 end_row = next_row;
11844 } else {
11845 break 'expand_downwards;
11846 }
11847 }
11848 }
11849
11850 let start = Point::new(start_row, 0);
11851 let start_offset = start.to_offset(&buffer);
11852 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11853 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11854 let Some(lines_without_prefixes) = selection_text
11855 .lines()
11856 .enumerate()
11857 .map(|(ix, line)| {
11858 let line_trimmed = line.trim_start();
11859 if rewrap_prefix.is_some() && ix > 0 {
11860 Ok(line_trimmed)
11861 } else {
11862 line_trimmed
11863 .strip_prefix(&line_prefix.trim_start())
11864 .with_context(|| {
11865 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11866 })
11867 }
11868 })
11869 .collect::<Result<Vec<_>, _>>()
11870 .log_err()
11871 else {
11872 continue;
11873 };
11874
11875 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11876 buffer
11877 .language_settings_at(Point::new(start_row, 0), cx)
11878 .preferred_line_length as usize
11879 });
11880
11881 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11882 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11883 } else {
11884 line_prefix.clone()
11885 };
11886
11887 let wrapped_text = wrap_with_prefix(
11888 line_prefix,
11889 subsequent_lines_prefix,
11890 lines_without_prefixes.join("\n"),
11891 wrap_column,
11892 tab_size,
11893 options.preserve_existing_whitespace,
11894 );
11895
11896 // TODO: should always use char-based diff while still supporting cursor behavior that
11897 // matches vim.
11898 let mut diff_options = DiffOptions::default();
11899 if options.override_language_settings {
11900 diff_options.max_word_diff_len = 0;
11901 diff_options.max_word_diff_line_count = 0;
11902 } else {
11903 diff_options.max_word_diff_len = usize::MAX;
11904 diff_options.max_word_diff_line_count = usize::MAX;
11905 }
11906
11907 for (old_range, new_text) in
11908 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11909 {
11910 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11911 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11912 edits.push((edit_start..edit_end, new_text));
11913 }
11914
11915 rewrapped_row_ranges.push(start_row..=end_row);
11916 }
11917
11918 self.buffer
11919 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11920 }
11921
11922 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11923 let mut text = String::new();
11924 let buffer = self.buffer.read(cx).snapshot(cx);
11925 let mut selections = self.selections.all::<Point>(cx);
11926 let mut clipboard_selections = Vec::with_capacity(selections.len());
11927 {
11928 let max_point = buffer.max_point();
11929 let mut is_first = true;
11930 for selection in &mut selections {
11931 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11932 if is_entire_line {
11933 selection.start = Point::new(selection.start.row, 0);
11934 if !selection.is_empty() && selection.end.column == 0 {
11935 selection.end = cmp::min(max_point, selection.end);
11936 } else {
11937 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11938 }
11939 selection.goal = SelectionGoal::None;
11940 }
11941 if is_first {
11942 is_first = false;
11943 } else {
11944 text += "\n";
11945 }
11946 let mut len = 0;
11947 for chunk in buffer.text_for_range(selection.start..selection.end) {
11948 text.push_str(chunk);
11949 len += chunk.len();
11950 }
11951 clipboard_selections.push(ClipboardSelection {
11952 len,
11953 is_entire_line,
11954 first_line_indent: buffer
11955 .indent_size_for_line(MultiBufferRow(selection.start.row))
11956 .len,
11957 });
11958 }
11959 }
11960
11961 self.transact(window, cx, |this, window, cx| {
11962 this.change_selections(Default::default(), window, cx, |s| {
11963 s.select(selections);
11964 });
11965 this.insert("", window, cx);
11966 });
11967 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11968 }
11969
11970 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11972 let item = self.cut_common(window, cx);
11973 cx.write_to_clipboard(item);
11974 }
11975
11976 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11978 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11979 s.move_with(|snapshot, sel| {
11980 if sel.is_empty() {
11981 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11982 }
11983 });
11984 });
11985 let item = self.cut_common(window, cx);
11986 cx.set_global(KillRing(item))
11987 }
11988
11989 pub fn kill_ring_yank(
11990 &mut self,
11991 _: &KillRingYank,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11996 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11997 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11998 (kill_ring.text().to_string(), kill_ring.metadata_json())
11999 } else {
12000 return;
12001 }
12002 } else {
12003 return;
12004 };
12005 self.do_paste(&text, metadata, false, window, cx);
12006 }
12007
12008 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12009 self.do_copy(true, cx);
12010 }
12011
12012 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12013 self.do_copy(false, cx);
12014 }
12015
12016 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12017 let selections = self.selections.all::<Point>(cx);
12018 let buffer = self.buffer.read(cx).read(cx);
12019 let mut text = String::new();
12020
12021 let mut clipboard_selections = Vec::with_capacity(selections.len());
12022 {
12023 let max_point = buffer.max_point();
12024 let mut is_first = true;
12025 for selection in &selections {
12026 let mut start = selection.start;
12027 let mut end = selection.end;
12028 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12029 if is_entire_line {
12030 start = Point::new(start.row, 0);
12031 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12032 }
12033
12034 let mut trimmed_selections = Vec::new();
12035 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12036 let row = MultiBufferRow(start.row);
12037 let first_indent = buffer.indent_size_for_line(row);
12038 if first_indent.len == 0 || start.column > first_indent.len {
12039 trimmed_selections.push(start..end);
12040 } else {
12041 trimmed_selections.push(
12042 Point::new(row.0, first_indent.len)
12043 ..Point::new(row.0, buffer.line_len(row)),
12044 );
12045 for row in start.row + 1..=end.row {
12046 let mut line_len = buffer.line_len(MultiBufferRow(row));
12047 if row == end.row {
12048 line_len = end.column;
12049 }
12050 if line_len == 0 {
12051 trimmed_selections
12052 .push(Point::new(row, 0)..Point::new(row, line_len));
12053 continue;
12054 }
12055 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12056 if row_indent_size.len >= first_indent.len {
12057 trimmed_selections.push(
12058 Point::new(row, first_indent.len)..Point::new(row, line_len),
12059 );
12060 } else {
12061 trimmed_selections.clear();
12062 trimmed_selections.push(start..end);
12063 break;
12064 }
12065 }
12066 }
12067 } else {
12068 trimmed_selections.push(start..end);
12069 }
12070
12071 for trimmed_range in trimmed_selections {
12072 if is_first {
12073 is_first = false;
12074 } else {
12075 text += "\n";
12076 }
12077 let mut len = 0;
12078 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12079 text.push_str(chunk);
12080 len += chunk.len();
12081 }
12082 clipboard_selections.push(ClipboardSelection {
12083 len,
12084 is_entire_line,
12085 first_line_indent: buffer
12086 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12087 .len,
12088 });
12089 }
12090 }
12091 }
12092
12093 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12094 text,
12095 clipboard_selections,
12096 ));
12097 }
12098
12099 pub fn do_paste(
12100 &mut self,
12101 text: &String,
12102 clipboard_selections: Option<Vec<ClipboardSelection>>,
12103 handle_entire_lines: bool,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 if self.read_only(cx) {
12108 return;
12109 }
12110
12111 let clipboard_text = Cow::Borrowed(text);
12112
12113 self.transact(window, cx, |this, window, cx| {
12114 if let Some(mut clipboard_selections) = clipboard_selections {
12115 let old_selections = this.selections.all::<usize>(cx);
12116 let all_selections_were_entire_line =
12117 clipboard_selections.iter().all(|s| s.is_entire_line);
12118 let first_selection_indent_column =
12119 clipboard_selections.first().map(|s| s.first_line_indent);
12120 if clipboard_selections.len() != old_selections.len() {
12121 clipboard_selections.drain(..);
12122 }
12123 let cursor_offset = this.selections.last::<usize>(cx).head();
12124 let mut auto_indent_on_paste = true;
12125
12126 this.buffer.update(cx, |buffer, cx| {
12127 let snapshot = buffer.read(cx);
12128 auto_indent_on_paste = snapshot
12129 .language_settings_at(cursor_offset, cx)
12130 .auto_indent_on_paste;
12131
12132 let mut start_offset = 0;
12133 let mut edits = Vec::new();
12134 let mut original_indent_columns = Vec::new();
12135 for (ix, selection) in old_selections.iter().enumerate() {
12136 let to_insert;
12137 let entire_line;
12138 let original_indent_column;
12139 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12140 let end_offset = start_offset + clipboard_selection.len;
12141 to_insert = &clipboard_text[start_offset..end_offset];
12142 entire_line = clipboard_selection.is_entire_line;
12143 start_offset = end_offset + 1;
12144 original_indent_column = Some(clipboard_selection.first_line_indent);
12145 } else {
12146 to_insert = clipboard_text.as_str();
12147 entire_line = all_selections_were_entire_line;
12148 original_indent_column = first_selection_indent_column
12149 }
12150
12151 // If the corresponding selection was empty when this slice of the
12152 // clipboard text was written, then the entire line containing the
12153 // selection was copied. If this selection is also currently empty,
12154 // then paste the line before the current line of the buffer.
12155 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12156 let column = selection.start.to_point(&snapshot).column as usize;
12157 let line_start = selection.start - column;
12158 line_start..line_start
12159 } else {
12160 selection.range()
12161 };
12162
12163 edits.push((range, to_insert));
12164 original_indent_columns.push(original_indent_column);
12165 }
12166 drop(snapshot);
12167
12168 buffer.edit(
12169 edits,
12170 if auto_indent_on_paste {
12171 Some(AutoindentMode::Block {
12172 original_indent_columns,
12173 })
12174 } else {
12175 None
12176 },
12177 cx,
12178 );
12179 });
12180
12181 let selections = this.selections.all::<usize>(cx);
12182 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12183 } else {
12184 this.insert(&clipboard_text, window, cx);
12185 }
12186 });
12187 }
12188
12189 pub fn diff_clipboard_with_selection(
12190 &mut self,
12191 _: &DiffClipboardWithSelection,
12192 window: &mut Window,
12193 cx: &mut Context<Self>,
12194 ) {
12195 let selections = self.selections.all::<usize>(cx);
12196
12197 if selections.is_empty() {
12198 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12199 return;
12200 };
12201
12202 let clipboard_text = match cx.read_from_clipboard() {
12203 Some(item) => match item.entries().first() {
12204 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12205 _ => None,
12206 },
12207 None => None,
12208 };
12209
12210 let Some(clipboard_text) = clipboard_text else {
12211 log::warn!("Clipboard doesn't contain text.");
12212 return;
12213 };
12214
12215 window.dispatch_action(
12216 Box::new(DiffClipboardWithSelectionData {
12217 clipboard_text,
12218 editor: cx.entity(),
12219 }),
12220 cx,
12221 );
12222 }
12223
12224 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12226 if let Some(item) = cx.read_from_clipboard() {
12227 let entries = item.entries();
12228
12229 match entries.first() {
12230 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12231 // of all the pasted entries.
12232 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12233 .do_paste(
12234 clipboard_string.text(),
12235 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12236 true,
12237 window,
12238 cx,
12239 ),
12240 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12241 }
12242 }
12243 }
12244
12245 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12246 if self.read_only(cx) {
12247 return;
12248 }
12249
12250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12251
12252 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12253 if let Some((selections, _)) =
12254 self.selection_history.transaction(transaction_id).cloned()
12255 {
12256 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12257 s.select_anchors(selections.to_vec());
12258 });
12259 } else {
12260 log::error!(
12261 "No entry in selection_history found for undo. \
12262 This may correspond to a bug where undo does not update the selection. \
12263 If this is occurring, please add details to \
12264 https://github.com/zed-industries/zed/issues/22692"
12265 );
12266 }
12267 self.request_autoscroll(Autoscroll::fit(), cx);
12268 self.unmark_text(window, cx);
12269 self.refresh_inline_completion(true, false, window, cx);
12270 cx.emit(EditorEvent::Edited { transaction_id });
12271 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12272 }
12273 }
12274
12275 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12276 if self.read_only(cx) {
12277 return;
12278 }
12279
12280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12281
12282 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12283 if let Some((_, Some(selections))) =
12284 self.selection_history.transaction(transaction_id).cloned()
12285 {
12286 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12287 s.select_anchors(selections.to_vec());
12288 });
12289 } else {
12290 log::error!(
12291 "No entry in selection_history found for redo. \
12292 This may correspond to a bug where undo does not update the selection. \
12293 If this is occurring, please add details to \
12294 https://github.com/zed-industries/zed/issues/22692"
12295 );
12296 }
12297 self.request_autoscroll(Autoscroll::fit(), cx);
12298 self.unmark_text(window, cx);
12299 self.refresh_inline_completion(true, false, window, cx);
12300 cx.emit(EditorEvent::Edited { transaction_id });
12301 }
12302 }
12303
12304 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12305 self.buffer
12306 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12307 }
12308
12309 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12310 self.buffer
12311 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12312 }
12313
12314 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12316 self.change_selections(Default::default(), window, cx, |s| {
12317 s.move_with(|map, selection| {
12318 let cursor = if selection.is_empty() {
12319 movement::left(map, selection.start)
12320 } else {
12321 selection.start
12322 };
12323 selection.collapse_to(cursor, SelectionGoal::None);
12324 });
12325 })
12326 }
12327
12328 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12330 self.change_selections(Default::default(), window, cx, |s| {
12331 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12332 })
12333 }
12334
12335 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12337 self.change_selections(Default::default(), window, cx, |s| {
12338 s.move_with(|map, selection| {
12339 let cursor = if selection.is_empty() {
12340 movement::right(map, selection.end)
12341 } else {
12342 selection.end
12343 };
12344 selection.collapse_to(cursor, SelectionGoal::None)
12345 });
12346 })
12347 }
12348
12349 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12351 self.change_selections(Default::default(), window, cx, |s| {
12352 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12353 })
12354 }
12355
12356 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12357 if self.take_rename(true, window, cx).is_some() {
12358 return;
12359 }
12360
12361 if self.mode.is_single_line() {
12362 cx.propagate();
12363 return;
12364 }
12365
12366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12367
12368 let text_layout_details = &self.text_layout_details(window);
12369 let selection_count = self.selections.count();
12370 let first_selection = self.selections.first_anchor();
12371
12372 self.change_selections(Default::default(), window, cx, |s| {
12373 s.move_with(|map, selection| {
12374 if !selection.is_empty() {
12375 selection.goal = SelectionGoal::None;
12376 }
12377 let (cursor, goal) = movement::up(
12378 map,
12379 selection.start,
12380 selection.goal,
12381 false,
12382 text_layout_details,
12383 );
12384 selection.collapse_to(cursor, goal);
12385 });
12386 });
12387
12388 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12389 {
12390 cx.propagate();
12391 }
12392 }
12393
12394 pub fn move_up_by_lines(
12395 &mut self,
12396 action: &MoveUpByLines,
12397 window: &mut Window,
12398 cx: &mut Context<Self>,
12399 ) {
12400 if self.take_rename(true, window, cx).is_some() {
12401 return;
12402 }
12403
12404 if self.mode.is_single_line() {
12405 cx.propagate();
12406 return;
12407 }
12408
12409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12410
12411 let text_layout_details = &self.text_layout_details(window);
12412
12413 self.change_selections(Default::default(), window, cx, |s| {
12414 s.move_with(|map, selection| {
12415 if !selection.is_empty() {
12416 selection.goal = SelectionGoal::None;
12417 }
12418 let (cursor, goal) = movement::up_by_rows(
12419 map,
12420 selection.start,
12421 action.lines,
12422 selection.goal,
12423 false,
12424 text_layout_details,
12425 );
12426 selection.collapse_to(cursor, goal);
12427 });
12428 })
12429 }
12430
12431 pub fn move_down_by_lines(
12432 &mut self,
12433 action: &MoveDownByLines,
12434 window: &mut Window,
12435 cx: &mut Context<Self>,
12436 ) {
12437 if self.take_rename(true, window, cx).is_some() {
12438 return;
12439 }
12440
12441 if self.mode.is_single_line() {
12442 cx.propagate();
12443 return;
12444 }
12445
12446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12447
12448 let text_layout_details = &self.text_layout_details(window);
12449
12450 self.change_selections(Default::default(), window, cx, |s| {
12451 s.move_with(|map, selection| {
12452 if !selection.is_empty() {
12453 selection.goal = SelectionGoal::None;
12454 }
12455 let (cursor, goal) = movement::down_by_rows(
12456 map,
12457 selection.start,
12458 action.lines,
12459 selection.goal,
12460 false,
12461 text_layout_details,
12462 );
12463 selection.collapse_to(cursor, goal);
12464 });
12465 })
12466 }
12467
12468 pub fn select_down_by_lines(
12469 &mut self,
12470 action: &SelectDownByLines,
12471 window: &mut Window,
12472 cx: &mut Context<Self>,
12473 ) {
12474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12475 let text_layout_details = &self.text_layout_details(window);
12476 self.change_selections(Default::default(), window, cx, |s| {
12477 s.move_heads_with(|map, head, goal| {
12478 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12479 })
12480 })
12481 }
12482
12483 pub fn select_up_by_lines(
12484 &mut self,
12485 action: &SelectUpByLines,
12486 window: &mut Window,
12487 cx: &mut Context<Self>,
12488 ) {
12489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12490 let text_layout_details = &self.text_layout_details(window);
12491 self.change_selections(Default::default(), window, cx, |s| {
12492 s.move_heads_with(|map, head, goal| {
12493 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12494 })
12495 })
12496 }
12497
12498 pub fn select_page_up(
12499 &mut self,
12500 _: &SelectPageUp,
12501 window: &mut Window,
12502 cx: &mut Context<Self>,
12503 ) {
12504 let Some(row_count) = self.visible_row_count() else {
12505 return;
12506 };
12507
12508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12509
12510 let text_layout_details = &self.text_layout_details(window);
12511
12512 self.change_selections(Default::default(), window, cx, |s| {
12513 s.move_heads_with(|map, head, goal| {
12514 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12515 })
12516 })
12517 }
12518
12519 pub fn move_page_up(
12520 &mut self,
12521 action: &MovePageUp,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 if self.take_rename(true, window, cx).is_some() {
12526 return;
12527 }
12528
12529 if self
12530 .context_menu
12531 .borrow_mut()
12532 .as_mut()
12533 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12534 .unwrap_or(false)
12535 {
12536 return;
12537 }
12538
12539 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12540 cx.propagate();
12541 return;
12542 }
12543
12544 let Some(row_count) = self.visible_row_count() else {
12545 return;
12546 };
12547
12548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12549
12550 let effects = if action.center_cursor {
12551 SelectionEffects::scroll(Autoscroll::center())
12552 } else {
12553 SelectionEffects::default()
12554 };
12555
12556 let text_layout_details = &self.text_layout_details(window);
12557
12558 self.change_selections(effects, window, cx, |s| {
12559 s.move_with(|map, selection| {
12560 if !selection.is_empty() {
12561 selection.goal = SelectionGoal::None;
12562 }
12563 let (cursor, goal) = movement::up_by_rows(
12564 map,
12565 selection.end,
12566 row_count,
12567 selection.goal,
12568 false,
12569 text_layout_details,
12570 );
12571 selection.collapse_to(cursor, goal);
12572 });
12573 });
12574 }
12575
12576 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12578 let text_layout_details = &self.text_layout_details(window);
12579 self.change_selections(Default::default(), window, cx, |s| {
12580 s.move_heads_with(|map, head, goal| {
12581 movement::up(map, head, goal, false, text_layout_details)
12582 })
12583 })
12584 }
12585
12586 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12587 self.take_rename(true, window, cx);
12588
12589 if self.mode.is_single_line() {
12590 cx.propagate();
12591 return;
12592 }
12593
12594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12595
12596 let text_layout_details = &self.text_layout_details(window);
12597 let selection_count = self.selections.count();
12598 let first_selection = self.selections.first_anchor();
12599
12600 self.change_selections(Default::default(), window, cx, |s| {
12601 s.move_with(|map, selection| {
12602 if !selection.is_empty() {
12603 selection.goal = SelectionGoal::None;
12604 }
12605 let (cursor, goal) = movement::down(
12606 map,
12607 selection.end,
12608 selection.goal,
12609 false,
12610 text_layout_details,
12611 );
12612 selection.collapse_to(cursor, goal);
12613 });
12614 });
12615
12616 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12617 {
12618 cx.propagate();
12619 }
12620 }
12621
12622 pub fn select_page_down(
12623 &mut self,
12624 _: &SelectPageDown,
12625 window: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 let Some(row_count) = self.visible_row_count() else {
12629 return;
12630 };
12631
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12633
12634 let text_layout_details = &self.text_layout_details(window);
12635
12636 self.change_selections(Default::default(), window, cx, |s| {
12637 s.move_heads_with(|map, head, goal| {
12638 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12639 })
12640 })
12641 }
12642
12643 pub fn move_page_down(
12644 &mut self,
12645 action: &MovePageDown,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) {
12649 if self.take_rename(true, window, cx).is_some() {
12650 return;
12651 }
12652
12653 if self
12654 .context_menu
12655 .borrow_mut()
12656 .as_mut()
12657 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12658 .unwrap_or(false)
12659 {
12660 return;
12661 }
12662
12663 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12664 cx.propagate();
12665 return;
12666 }
12667
12668 let Some(row_count) = self.visible_row_count() else {
12669 return;
12670 };
12671
12672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12673
12674 let effects = if action.center_cursor {
12675 SelectionEffects::scroll(Autoscroll::center())
12676 } else {
12677 SelectionEffects::default()
12678 };
12679
12680 let text_layout_details = &self.text_layout_details(window);
12681 self.change_selections(effects, window, cx, |s| {
12682 s.move_with(|map, selection| {
12683 if !selection.is_empty() {
12684 selection.goal = SelectionGoal::None;
12685 }
12686 let (cursor, goal) = movement::down_by_rows(
12687 map,
12688 selection.end,
12689 row_count,
12690 selection.goal,
12691 false,
12692 text_layout_details,
12693 );
12694 selection.collapse_to(cursor, goal);
12695 });
12696 });
12697 }
12698
12699 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12701 let text_layout_details = &self.text_layout_details(window);
12702 self.change_selections(Default::default(), window, cx, |s| {
12703 s.move_heads_with(|map, head, goal| {
12704 movement::down(map, head, goal, false, text_layout_details)
12705 })
12706 });
12707 }
12708
12709 pub fn context_menu_first(
12710 &mut self,
12711 _: &ContextMenuFirst,
12712 window: &mut Window,
12713 cx: &mut Context<Self>,
12714 ) {
12715 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12716 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12717 }
12718 }
12719
12720 pub fn context_menu_prev(
12721 &mut self,
12722 _: &ContextMenuPrevious,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12727 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12728 }
12729 }
12730
12731 pub fn context_menu_next(
12732 &mut self,
12733 _: &ContextMenuNext,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12738 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12739 }
12740 }
12741
12742 pub fn context_menu_last(
12743 &mut self,
12744 _: &ContextMenuLast,
12745 window: &mut Window,
12746 cx: &mut Context<Self>,
12747 ) {
12748 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12749 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12750 }
12751 }
12752
12753 pub fn signature_help_prev(
12754 &mut self,
12755 _: &SignatureHelpPrevious,
12756 _: &mut Window,
12757 cx: &mut Context<Self>,
12758 ) {
12759 if let Some(popover) = self.signature_help_state.popover_mut() {
12760 if popover.current_signature == 0 {
12761 popover.current_signature = popover.signatures.len() - 1;
12762 } else {
12763 popover.current_signature -= 1;
12764 }
12765 cx.notify();
12766 }
12767 }
12768
12769 pub fn signature_help_next(
12770 &mut self,
12771 _: &SignatureHelpNext,
12772 _: &mut Window,
12773 cx: &mut Context<Self>,
12774 ) {
12775 if let Some(popover) = self.signature_help_state.popover_mut() {
12776 if popover.current_signature + 1 == popover.signatures.len() {
12777 popover.current_signature = 0;
12778 } else {
12779 popover.current_signature += 1;
12780 }
12781 cx.notify();
12782 }
12783 }
12784
12785 pub fn move_to_previous_word_start(
12786 &mut self,
12787 _: &MoveToPreviousWordStart,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12792 self.change_selections(Default::default(), window, cx, |s| {
12793 s.move_cursors_with(|map, head, _| {
12794 (
12795 movement::previous_word_start(map, head),
12796 SelectionGoal::None,
12797 )
12798 });
12799 })
12800 }
12801
12802 pub fn move_to_previous_subword_start(
12803 &mut self,
12804 _: &MoveToPreviousSubwordStart,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12809 self.change_selections(Default::default(), window, cx, |s| {
12810 s.move_cursors_with(|map, head, _| {
12811 (
12812 movement::previous_subword_start(map, head),
12813 SelectionGoal::None,
12814 )
12815 });
12816 })
12817 }
12818
12819 pub fn select_to_previous_word_start(
12820 &mut self,
12821 _: &SelectToPreviousWordStart,
12822 window: &mut Window,
12823 cx: &mut Context<Self>,
12824 ) {
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826 self.change_selections(Default::default(), window, cx, |s| {
12827 s.move_heads_with(|map, head, _| {
12828 (
12829 movement::previous_word_start(map, head),
12830 SelectionGoal::None,
12831 )
12832 });
12833 })
12834 }
12835
12836 pub fn select_to_previous_subword_start(
12837 &mut self,
12838 _: &SelectToPreviousSubwordStart,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12843 self.change_selections(Default::default(), window, cx, |s| {
12844 s.move_heads_with(|map, head, _| {
12845 (
12846 movement::previous_subword_start(map, head),
12847 SelectionGoal::None,
12848 )
12849 });
12850 })
12851 }
12852
12853 pub fn delete_to_previous_word_start(
12854 &mut self,
12855 action: &DeleteToPreviousWordStart,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12860 self.transact(window, cx, |this, window, cx| {
12861 this.select_autoclose_pair(window, cx);
12862 this.change_selections(Default::default(), window, cx, |s| {
12863 s.move_with(|map, selection| {
12864 if selection.is_empty() {
12865 let cursor = if action.ignore_newlines {
12866 movement::previous_word_start(map, selection.head())
12867 } else {
12868 movement::previous_word_start_or_newline(map, selection.head())
12869 };
12870 selection.set_head(cursor, SelectionGoal::None);
12871 }
12872 });
12873 });
12874 this.insert("", window, cx);
12875 });
12876 }
12877
12878 pub fn delete_to_previous_subword_start(
12879 &mut self,
12880 _: &DeleteToPreviousSubwordStart,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12885 self.transact(window, cx, |this, window, cx| {
12886 this.select_autoclose_pair(window, cx);
12887 this.change_selections(Default::default(), window, cx, |s| {
12888 s.move_with(|map, selection| {
12889 if selection.is_empty() {
12890 let cursor = movement::previous_subword_start(map, selection.head());
12891 selection.set_head(cursor, SelectionGoal::None);
12892 }
12893 });
12894 });
12895 this.insert("", window, cx);
12896 });
12897 }
12898
12899 pub fn move_to_next_word_end(
12900 &mut self,
12901 _: &MoveToNextWordEnd,
12902 window: &mut Window,
12903 cx: &mut Context<Self>,
12904 ) {
12905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12906 self.change_selections(Default::default(), window, cx, |s| {
12907 s.move_cursors_with(|map, head, _| {
12908 (movement::next_word_end(map, head), SelectionGoal::None)
12909 });
12910 })
12911 }
12912
12913 pub fn move_to_next_subword_end(
12914 &mut self,
12915 _: &MoveToNextSubwordEnd,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920 self.change_selections(Default::default(), window, cx, |s| {
12921 s.move_cursors_with(|map, head, _| {
12922 (movement::next_subword_end(map, head), SelectionGoal::None)
12923 });
12924 })
12925 }
12926
12927 pub fn select_to_next_word_end(
12928 &mut self,
12929 _: &SelectToNextWordEnd,
12930 window: &mut Window,
12931 cx: &mut Context<Self>,
12932 ) {
12933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_heads_with(|map, head, _| {
12936 (movement::next_word_end(map, head), SelectionGoal::None)
12937 });
12938 })
12939 }
12940
12941 pub fn select_to_next_subword_end(
12942 &mut self,
12943 _: &SelectToNextSubwordEnd,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_heads_with(|map, head, _| {
12950 (movement::next_subword_end(map, head), SelectionGoal::None)
12951 });
12952 })
12953 }
12954
12955 pub fn delete_to_next_word_end(
12956 &mut self,
12957 action: &DeleteToNextWordEnd,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12962 self.transact(window, cx, |this, window, cx| {
12963 this.change_selections(Default::default(), window, cx, |s| {
12964 s.move_with(|map, selection| {
12965 if selection.is_empty() {
12966 let cursor = if action.ignore_newlines {
12967 movement::next_word_end(map, selection.head())
12968 } else {
12969 movement::next_word_end_or_newline(map, selection.head())
12970 };
12971 selection.set_head(cursor, SelectionGoal::None);
12972 }
12973 });
12974 });
12975 this.insert("", window, cx);
12976 });
12977 }
12978
12979 pub fn delete_to_next_subword_end(
12980 &mut self,
12981 _: &DeleteToNextSubwordEnd,
12982 window: &mut Window,
12983 cx: &mut Context<Self>,
12984 ) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12986 self.transact(window, cx, |this, window, cx| {
12987 this.change_selections(Default::default(), window, cx, |s| {
12988 s.move_with(|map, selection| {
12989 if selection.is_empty() {
12990 let cursor = movement::next_subword_end(map, selection.head());
12991 selection.set_head(cursor, SelectionGoal::None);
12992 }
12993 });
12994 });
12995 this.insert("", window, cx);
12996 });
12997 }
12998
12999 pub fn move_to_beginning_of_line(
13000 &mut self,
13001 action: &MoveToBeginningOfLine,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_cursors_with(|map, head, _| {
13008 (
13009 movement::indented_line_beginning(
13010 map,
13011 head,
13012 action.stop_at_soft_wraps,
13013 action.stop_at_indent,
13014 ),
13015 SelectionGoal::None,
13016 )
13017 });
13018 })
13019 }
13020
13021 pub fn select_to_beginning_of_line(
13022 &mut self,
13023 action: &SelectToBeginningOfLine,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13028 self.change_selections(Default::default(), window, cx, |s| {
13029 s.move_heads_with(|map, head, _| {
13030 (
13031 movement::indented_line_beginning(
13032 map,
13033 head,
13034 action.stop_at_soft_wraps,
13035 action.stop_at_indent,
13036 ),
13037 SelectionGoal::None,
13038 )
13039 });
13040 });
13041 }
13042
13043 pub fn delete_to_beginning_of_line(
13044 &mut self,
13045 action: &DeleteToBeginningOfLine,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13050 self.transact(window, cx, |this, window, cx| {
13051 this.change_selections(Default::default(), window, cx, |s| {
13052 s.move_with(|_, selection| {
13053 selection.reversed = true;
13054 });
13055 });
13056
13057 this.select_to_beginning_of_line(
13058 &SelectToBeginningOfLine {
13059 stop_at_soft_wraps: false,
13060 stop_at_indent: action.stop_at_indent,
13061 },
13062 window,
13063 cx,
13064 );
13065 this.backspace(&Backspace, window, cx);
13066 });
13067 }
13068
13069 pub fn move_to_end_of_line(
13070 &mut self,
13071 action: &MoveToEndOfLine,
13072 window: &mut Window,
13073 cx: &mut Context<Self>,
13074 ) {
13075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13076 self.change_selections(Default::default(), window, cx, |s| {
13077 s.move_cursors_with(|map, head, _| {
13078 (
13079 movement::line_end(map, head, action.stop_at_soft_wraps),
13080 SelectionGoal::None,
13081 )
13082 });
13083 })
13084 }
13085
13086 pub fn select_to_end_of_line(
13087 &mut self,
13088 action: &SelectToEndOfLine,
13089 window: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13093 self.change_selections(Default::default(), window, cx, |s| {
13094 s.move_heads_with(|map, head, _| {
13095 (
13096 movement::line_end(map, head, action.stop_at_soft_wraps),
13097 SelectionGoal::None,
13098 )
13099 });
13100 })
13101 }
13102
13103 pub fn delete_to_end_of_line(
13104 &mut self,
13105 _: &DeleteToEndOfLine,
13106 window: &mut Window,
13107 cx: &mut Context<Self>,
13108 ) {
13109 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13110 self.transact(window, cx, |this, window, cx| {
13111 this.select_to_end_of_line(
13112 &SelectToEndOfLine {
13113 stop_at_soft_wraps: false,
13114 },
13115 window,
13116 cx,
13117 );
13118 this.delete(&Delete, window, cx);
13119 });
13120 }
13121
13122 pub fn cut_to_end_of_line(
13123 &mut self,
13124 _: &CutToEndOfLine,
13125 window: &mut Window,
13126 cx: &mut Context<Self>,
13127 ) {
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13129 self.transact(window, cx, |this, window, cx| {
13130 this.select_to_end_of_line(
13131 &SelectToEndOfLine {
13132 stop_at_soft_wraps: false,
13133 },
13134 window,
13135 cx,
13136 );
13137 this.cut(&Cut, window, cx);
13138 });
13139 }
13140
13141 pub fn move_to_start_of_paragraph(
13142 &mut self,
13143 _: &MoveToStartOfParagraph,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13148 cx.propagate();
13149 return;
13150 }
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13152 self.change_selections(Default::default(), window, cx, |s| {
13153 s.move_with(|map, selection| {
13154 selection.collapse_to(
13155 movement::start_of_paragraph(map, selection.head(), 1),
13156 SelectionGoal::None,
13157 )
13158 });
13159 })
13160 }
13161
13162 pub fn move_to_end_of_paragraph(
13163 &mut self,
13164 _: &MoveToEndOfParagraph,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13169 cx.propagate();
13170 return;
13171 }
13172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13173 self.change_selections(Default::default(), window, cx, |s| {
13174 s.move_with(|map, selection| {
13175 selection.collapse_to(
13176 movement::end_of_paragraph(map, selection.head(), 1),
13177 SelectionGoal::None,
13178 )
13179 });
13180 })
13181 }
13182
13183 pub fn select_to_start_of_paragraph(
13184 &mut self,
13185 _: &SelectToStartOfParagraph,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13190 cx.propagate();
13191 return;
13192 }
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194 self.change_selections(Default::default(), window, cx, |s| {
13195 s.move_heads_with(|map, head, _| {
13196 (
13197 movement::start_of_paragraph(map, head, 1),
13198 SelectionGoal::None,
13199 )
13200 });
13201 })
13202 }
13203
13204 pub fn select_to_end_of_paragraph(
13205 &mut self,
13206 _: &SelectToEndOfParagraph,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) {
13210 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13211 cx.propagate();
13212 return;
13213 }
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215 self.change_selections(Default::default(), window, cx, |s| {
13216 s.move_heads_with(|map, head, _| {
13217 (
13218 movement::end_of_paragraph(map, head, 1),
13219 SelectionGoal::None,
13220 )
13221 });
13222 })
13223 }
13224
13225 pub fn move_to_start_of_excerpt(
13226 &mut self,
13227 _: &MoveToStartOfExcerpt,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13232 cx.propagate();
13233 return;
13234 }
13235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13236 self.change_selections(Default::default(), window, cx, |s| {
13237 s.move_with(|map, selection| {
13238 selection.collapse_to(
13239 movement::start_of_excerpt(
13240 map,
13241 selection.head(),
13242 workspace::searchable::Direction::Prev,
13243 ),
13244 SelectionGoal::None,
13245 )
13246 });
13247 })
13248 }
13249
13250 pub fn move_to_start_of_next_excerpt(
13251 &mut self,
13252 _: &MoveToStartOfNextExcerpt,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) {
13256 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13257 cx.propagate();
13258 return;
13259 }
13260
13261 self.change_selections(Default::default(), window, cx, |s| {
13262 s.move_with(|map, selection| {
13263 selection.collapse_to(
13264 movement::start_of_excerpt(
13265 map,
13266 selection.head(),
13267 workspace::searchable::Direction::Next,
13268 ),
13269 SelectionGoal::None,
13270 )
13271 });
13272 })
13273 }
13274
13275 pub fn move_to_end_of_excerpt(
13276 &mut self,
13277 _: &MoveToEndOfExcerpt,
13278 window: &mut Window,
13279 cx: &mut Context<Self>,
13280 ) {
13281 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13282 cx.propagate();
13283 return;
13284 }
13285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13286 self.change_selections(Default::default(), window, cx, |s| {
13287 s.move_with(|map, selection| {
13288 selection.collapse_to(
13289 movement::end_of_excerpt(
13290 map,
13291 selection.head(),
13292 workspace::searchable::Direction::Next,
13293 ),
13294 SelectionGoal::None,
13295 )
13296 });
13297 })
13298 }
13299
13300 pub fn move_to_end_of_previous_excerpt(
13301 &mut self,
13302 _: &MoveToEndOfPreviousExcerpt,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13307 cx.propagate();
13308 return;
13309 }
13310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13311 self.change_selections(Default::default(), window, cx, |s| {
13312 s.move_with(|map, selection| {
13313 selection.collapse_to(
13314 movement::end_of_excerpt(
13315 map,
13316 selection.head(),
13317 workspace::searchable::Direction::Prev,
13318 ),
13319 SelectionGoal::None,
13320 )
13321 });
13322 })
13323 }
13324
13325 pub fn select_to_start_of_excerpt(
13326 &mut self,
13327 _: &SelectToStartOfExcerpt,
13328 window: &mut Window,
13329 cx: &mut Context<Self>,
13330 ) {
13331 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13332 cx.propagate();
13333 return;
13334 }
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_heads_with(|map, head, _| {
13338 (
13339 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13340 SelectionGoal::None,
13341 )
13342 });
13343 })
13344 }
13345
13346 pub fn select_to_start_of_next_excerpt(
13347 &mut self,
13348 _: &SelectToStartOfNextExcerpt,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13353 cx.propagate();
13354 return;
13355 }
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 self.change_selections(Default::default(), window, cx, |s| {
13358 s.move_heads_with(|map, head, _| {
13359 (
13360 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13361 SelectionGoal::None,
13362 )
13363 });
13364 })
13365 }
13366
13367 pub fn select_to_end_of_excerpt(
13368 &mut self,
13369 _: &SelectToEndOfExcerpt,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13374 cx.propagate();
13375 return;
13376 }
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (
13381 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn select_to_end_of_previous_excerpt(
13389 &mut self,
13390 _: &SelectToEndOfPreviousExcerpt,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13395 cx.propagate();
13396 return;
13397 }
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399 self.change_selections(Default::default(), window, cx, |s| {
13400 s.move_heads_with(|map, head, _| {
13401 (
13402 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13403 SelectionGoal::None,
13404 )
13405 });
13406 })
13407 }
13408
13409 pub fn move_to_beginning(
13410 &mut self,
13411 _: &MoveToBeginning,
13412 window: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13416 cx.propagate();
13417 return;
13418 }
13419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13420 self.change_selections(Default::default(), window, cx, |s| {
13421 s.select_ranges(vec![0..0]);
13422 });
13423 }
13424
13425 pub fn select_to_beginning(
13426 &mut self,
13427 _: &SelectToBeginning,
13428 window: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) {
13431 let mut selection = self.selections.last::<Point>(cx);
13432 selection.set_head(Point::zero(), SelectionGoal::None);
13433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13434 self.change_selections(Default::default(), window, cx, |s| {
13435 s.select(vec![selection]);
13436 });
13437 }
13438
13439 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13440 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13441 cx.propagate();
13442 return;
13443 }
13444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13445 let cursor = self.buffer.read(cx).read(cx).len();
13446 self.change_selections(Default::default(), window, cx, |s| {
13447 s.select_ranges(vec![cursor..cursor])
13448 });
13449 }
13450
13451 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13452 self.nav_history = nav_history;
13453 }
13454
13455 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13456 self.nav_history.as_ref()
13457 }
13458
13459 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13460 self.push_to_nav_history(
13461 self.selections.newest_anchor().head(),
13462 None,
13463 false,
13464 true,
13465 cx,
13466 );
13467 }
13468
13469 fn push_to_nav_history(
13470 &mut self,
13471 cursor_anchor: Anchor,
13472 new_position: Option<Point>,
13473 is_deactivate: bool,
13474 always: bool,
13475 cx: &mut Context<Self>,
13476 ) {
13477 if let Some(nav_history) = self.nav_history.as_mut() {
13478 let buffer = self.buffer.read(cx).read(cx);
13479 let cursor_position = cursor_anchor.to_point(&buffer);
13480 let scroll_state = self.scroll_manager.anchor();
13481 let scroll_top_row = scroll_state.top_row(&buffer);
13482 drop(buffer);
13483
13484 if let Some(new_position) = new_position {
13485 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13486 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13487 return;
13488 }
13489 }
13490
13491 nav_history.push(
13492 Some(NavigationData {
13493 cursor_anchor,
13494 cursor_position,
13495 scroll_anchor: scroll_state,
13496 scroll_top_row,
13497 }),
13498 cx,
13499 );
13500 cx.emit(EditorEvent::PushedToNavHistory {
13501 anchor: cursor_anchor,
13502 is_deactivate,
13503 })
13504 }
13505 }
13506
13507 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13509 let buffer = self.buffer.read(cx).snapshot(cx);
13510 let mut selection = self.selections.first::<usize>(cx);
13511 selection.set_head(buffer.len(), SelectionGoal::None);
13512 self.change_selections(Default::default(), window, cx, |s| {
13513 s.select(vec![selection]);
13514 });
13515 }
13516
13517 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 let end = self.buffer.read(cx).read(cx).len();
13520 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13521 s.select_ranges(vec![0..end]);
13522 });
13523 }
13524
13525 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13528 let mut selections = self.selections.all::<Point>(cx);
13529 let max_point = display_map.buffer_snapshot.max_point();
13530 for selection in &mut selections {
13531 let rows = selection.spanned_rows(true, &display_map);
13532 selection.start = Point::new(rows.start.0, 0);
13533 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13534 selection.reversed = false;
13535 }
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.select(selections);
13538 });
13539 }
13540
13541 pub fn split_selection_into_lines(
13542 &mut self,
13543 _: &SplitSelectionIntoLines,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 let selections = self
13548 .selections
13549 .all::<Point>(cx)
13550 .into_iter()
13551 .map(|selection| selection.start..selection.end)
13552 .collect::<Vec<_>>();
13553 self.unfold_ranges(&selections, true, true, cx);
13554
13555 let mut new_selection_ranges = Vec::new();
13556 {
13557 let buffer = self.buffer.read(cx).read(cx);
13558 for selection in selections {
13559 for row in selection.start.row..selection.end.row {
13560 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13561 new_selection_ranges.push(cursor..cursor);
13562 }
13563
13564 let is_multiline_selection = selection.start.row != selection.end.row;
13565 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13566 // so this action feels more ergonomic when paired with other selection operations
13567 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13568 if !should_skip_last {
13569 new_selection_ranges.push(selection.end..selection.end);
13570 }
13571 }
13572 }
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.select_ranges(new_selection_ranges);
13575 });
13576 }
13577
13578 pub fn add_selection_above(
13579 &mut self,
13580 _: &AddSelectionAbove,
13581 window: &mut Window,
13582 cx: &mut Context<Self>,
13583 ) {
13584 self.add_selection(true, window, cx);
13585 }
13586
13587 pub fn add_selection_below(
13588 &mut self,
13589 _: &AddSelectionBelow,
13590 window: &mut Window,
13591 cx: &mut Context<Self>,
13592 ) {
13593 self.add_selection(false, window, cx);
13594 }
13595
13596 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13598
13599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13600 let all_selections = self.selections.all::<Point>(cx);
13601 let text_layout_details = self.text_layout_details(window);
13602
13603 let (mut columnar_selections, new_selections_to_columnarize) = {
13604 if let Some(state) = self.add_selections_state.as_ref() {
13605 let columnar_selection_ids: HashSet<_> = state
13606 .groups
13607 .iter()
13608 .flat_map(|group| group.stack.iter())
13609 .copied()
13610 .collect();
13611
13612 all_selections
13613 .into_iter()
13614 .partition(|s| columnar_selection_ids.contains(&s.id))
13615 } else {
13616 (Vec::new(), all_selections)
13617 }
13618 };
13619
13620 let mut state = self
13621 .add_selections_state
13622 .take()
13623 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13624
13625 for selection in new_selections_to_columnarize {
13626 let range = selection.display_range(&display_map).sorted();
13627 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13628 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13629 let positions = start_x.min(end_x)..start_x.max(end_x);
13630 let mut stack = Vec::new();
13631 for row in range.start.row().0..=range.end.row().0 {
13632 if let Some(selection) = self.selections.build_columnar_selection(
13633 &display_map,
13634 DisplayRow(row),
13635 &positions,
13636 selection.reversed,
13637 &text_layout_details,
13638 ) {
13639 stack.push(selection.id);
13640 columnar_selections.push(selection);
13641 }
13642 }
13643 if !stack.is_empty() {
13644 if above {
13645 stack.reverse();
13646 }
13647 state.groups.push(AddSelectionsGroup { above, stack });
13648 }
13649 }
13650
13651 let mut final_selections = Vec::new();
13652 let end_row = if above {
13653 DisplayRow(0)
13654 } else {
13655 display_map.max_point().row()
13656 };
13657
13658 let mut last_added_item_per_group = HashMap::default();
13659 for group in state.groups.iter_mut() {
13660 if let Some(last_id) = group.stack.last() {
13661 last_added_item_per_group.insert(*last_id, group);
13662 }
13663 }
13664
13665 for selection in columnar_selections {
13666 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13667 if above == group.above {
13668 let range = selection.display_range(&display_map).sorted();
13669 debug_assert_eq!(range.start.row(), range.end.row());
13670 let mut row = range.start.row();
13671 let positions =
13672 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13673 px(start)..px(end)
13674 } else {
13675 let start_x =
13676 display_map.x_for_display_point(range.start, &text_layout_details);
13677 let end_x =
13678 display_map.x_for_display_point(range.end, &text_layout_details);
13679 start_x.min(end_x)..start_x.max(end_x)
13680 };
13681
13682 let mut maybe_new_selection = None;
13683 while row != end_row {
13684 if above {
13685 row.0 -= 1;
13686 } else {
13687 row.0 += 1;
13688 }
13689 if let Some(new_selection) = self.selections.build_columnar_selection(
13690 &display_map,
13691 row,
13692 &positions,
13693 selection.reversed,
13694 &text_layout_details,
13695 ) {
13696 maybe_new_selection = Some(new_selection);
13697 break;
13698 }
13699 }
13700
13701 if let Some(new_selection) = maybe_new_selection {
13702 group.stack.push(new_selection.id);
13703 if above {
13704 final_selections.push(new_selection);
13705 final_selections.push(selection);
13706 } else {
13707 final_selections.push(selection);
13708 final_selections.push(new_selection);
13709 }
13710 } else {
13711 final_selections.push(selection);
13712 }
13713 } else {
13714 group.stack.pop();
13715 }
13716 } else {
13717 final_selections.push(selection);
13718 }
13719 }
13720
13721 self.change_selections(Default::default(), window, cx, |s| {
13722 s.select(final_selections);
13723 });
13724
13725 let final_selection_ids: HashSet<_> = self
13726 .selections
13727 .all::<Point>(cx)
13728 .iter()
13729 .map(|s| s.id)
13730 .collect();
13731 state.groups.retain_mut(|group| {
13732 // selections might get merged above so we remove invalid items from stacks
13733 group.stack.retain(|id| final_selection_ids.contains(id));
13734
13735 // single selection in stack can be treated as initial state
13736 group.stack.len() > 1
13737 });
13738
13739 if !state.groups.is_empty() {
13740 self.add_selections_state = Some(state);
13741 }
13742 }
13743
13744 fn select_match_ranges(
13745 &mut self,
13746 range: Range<usize>,
13747 reversed: bool,
13748 replace_newest: bool,
13749 auto_scroll: Option<Autoscroll>,
13750 window: &mut Window,
13751 cx: &mut Context<Editor>,
13752 ) {
13753 self.unfold_ranges(
13754 std::slice::from_ref(&range),
13755 false,
13756 auto_scroll.is_some(),
13757 cx,
13758 );
13759 let effects = if let Some(scroll) = auto_scroll {
13760 SelectionEffects::scroll(scroll)
13761 } else {
13762 SelectionEffects::no_scroll()
13763 };
13764 self.change_selections(effects, window, cx, |s| {
13765 if replace_newest {
13766 s.delete(s.newest_anchor().id);
13767 }
13768 if reversed {
13769 s.insert_range(range.end..range.start);
13770 } else {
13771 s.insert_range(range);
13772 }
13773 });
13774 }
13775
13776 pub fn select_next_match_internal(
13777 &mut self,
13778 display_map: &DisplaySnapshot,
13779 replace_newest: bool,
13780 autoscroll: Option<Autoscroll>,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) -> Result<()> {
13784 let buffer = &display_map.buffer_snapshot;
13785 let mut selections = self.selections.all::<usize>(cx);
13786 if let Some(mut select_next_state) = self.select_next_state.take() {
13787 let query = &select_next_state.query;
13788 if !select_next_state.done {
13789 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13790 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13791 let mut next_selected_range = None;
13792
13793 let bytes_after_last_selection =
13794 buffer.bytes_in_range(last_selection.end..buffer.len());
13795 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13796 let query_matches = query
13797 .stream_find_iter(bytes_after_last_selection)
13798 .map(|result| (last_selection.end, result))
13799 .chain(
13800 query
13801 .stream_find_iter(bytes_before_first_selection)
13802 .map(|result| (0, result)),
13803 );
13804
13805 for (start_offset, query_match) in query_matches {
13806 let query_match = query_match.unwrap(); // can only fail due to I/O
13807 let offset_range =
13808 start_offset + query_match.start()..start_offset + query_match.end();
13809
13810 if !select_next_state.wordwise
13811 || (!buffer.is_inside_word(offset_range.start, false)
13812 && !buffer.is_inside_word(offset_range.end, false))
13813 {
13814 // TODO: This is n^2, because we might check all the selections
13815 if !selections
13816 .iter()
13817 .any(|selection| selection.range().overlaps(&offset_range))
13818 {
13819 next_selected_range = Some(offset_range);
13820 break;
13821 }
13822 }
13823 }
13824
13825 if let Some(next_selected_range) = next_selected_range {
13826 self.select_match_ranges(
13827 next_selected_range,
13828 last_selection.reversed,
13829 replace_newest,
13830 autoscroll,
13831 window,
13832 cx,
13833 );
13834 } else {
13835 select_next_state.done = true;
13836 }
13837 }
13838
13839 self.select_next_state = Some(select_next_state);
13840 } else {
13841 let mut only_carets = true;
13842 let mut same_text_selected = true;
13843 let mut selected_text = None;
13844
13845 let mut selections_iter = selections.iter().peekable();
13846 while let Some(selection) = selections_iter.next() {
13847 if selection.start != selection.end {
13848 only_carets = false;
13849 }
13850
13851 if same_text_selected {
13852 if selected_text.is_none() {
13853 selected_text =
13854 Some(buffer.text_for_range(selection.range()).collect::<String>());
13855 }
13856
13857 if let Some(next_selection) = selections_iter.peek() {
13858 if next_selection.range().len() == selection.range().len() {
13859 let next_selected_text = buffer
13860 .text_for_range(next_selection.range())
13861 .collect::<String>();
13862 if Some(next_selected_text) != selected_text {
13863 same_text_selected = false;
13864 selected_text = None;
13865 }
13866 } else {
13867 same_text_selected = false;
13868 selected_text = None;
13869 }
13870 }
13871 }
13872 }
13873
13874 if only_carets {
13875 for selection in &mut selections {
13876 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13877 selection.start = word_range.start;
13878 selection.end = word_range.end;
13879 selection.goal = SelectionGoal::None;
13880 selection.reversed = false;
13881 self.select_match_ranges(
13882 selection.start..selection.end,
13883 selection.reversed,
13884 replace_newest,
13885 autoscroll,
13886 window,
13887 cx,
13888 );
13889 }
13890
13891 if selections.len() == 1 {
13892 let selection = selections
13893 .last()
13894 .expect("ensured that there's only one selection");
13895 let query = buffer
13896 .text_for_range(selection.start..selection.end)
13897 .collect::<String>();
13898 let is_empty = query.is_empty();
13899 let select_state = SelectNextState {
13900 query: AhoCorasick::new(&[query])?,
13901 wordwise: true,
13902 done: is_empty,
13903 };
13904 self.select_next_state = Some(select_state);
13905 } else {
13906 self.select_next_state = None;
13907 }
13908 } else if let Some(selected_text) = selected_text {
13909 self.select_next_state = Some(SelectNextState {
13910 query: AhoCorasick::new(&[selected_text])?,
13911 wordwise: false,
13912 done: false,
13913 });
13914 self.select_next_match_internal(
13915 display_map,
13916 replace_newest,
13917 autoscroll,
13918 window,
13919 cx,
13920 )?;
13921 }
13922 }
13923 Ok(())
13924 }
13925
13926 pub fn select_all_matches(
13927 &mut self,
13928 _action: &SelectAllMatches,
13929 window: &mut Window,
13930 cx: &mut Context<Self>,
13931 ) -> Result<()> {
13932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13933
13934 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13935
13936 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13937 let Some(select_next_state) = self.select_next_state.as_mut() else {
13938 return Ok(());
13939 };
13940 if select_next_state.done {
13941 return Ok(());
13942 }
13943
13944 let mut new_selections = Vec::new();
13945
13946 let reversed = self.selections.oldest::<usize>(cx).reversed;
13947 let buffer = &display_map.buffer_snapshot;
13948 let query_matches = select_next_state
13949 .query
13950 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13951
13952 for query_match in query_matches.into_iter() {
13953 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13954 let offset_range = if reversed {
13955 query_match.end()..query_match.start()
13956 } else {
13957 query_match.start()..query_match.end()
13958 };
13959
13960 if !select_next_state.wordwise
13961 || (!buffer.is_inside_word(offset_range.start, false)
13962 && !buffer.is_inside_word(offset_range.end, false))
13963 {
13964 new_selections.push(offset_range.start..offset_range.end);
13965 }
13966 }
13967
13968 select_next_state.done = true;
13969
13970 if new_selections.is_empty() {
13971 log::error!("bug: new_selections is empty in select_all_matches");
13972 return Ok(());
13973 }
13974
13975 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13976 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13977 selections.select_ranges(new_selections)
13978 });
13979
13980 Ok(())
13981 }
13982
13983 pub fn select_next(
13984 &mut self,
13985 action: &SelectNext,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) -> Result<()> {
13989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13991 self.select_next_match_internal(
13992 &display_map,
13993 action.replace_newest,
13994 Some(Autoscroll::newest()),
13995 window,
13996 cx,
13997 )?;
13998 Ok(())
13999 }
14000
14001 pub fn select_previous(
14002 &mut self,
14003 action: &SelectPrevious,
14004 window: &mut Window,
14005 cx: &mut Context<Self>,
14006 ) -> Result<()> {
14007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14009 let buffer = &display_map.buffer_snapshot;
14010 let mut selections = self.selections.all::<usize>(cx);
14011 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14012 let query = &select_prev_state.query;
14013 if !select_prev_state.done {
14014 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14015 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14016 let mut next_selected_range = None;
14017 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14018 let bytes_before_last_selection =
14019 buffer.reversed_bytes_in_range(0..last_selection.start);
14020 let bytes_after_first_selection =
14021 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14022 let query_matches = query
14023 .stream_find_iter(bytes_before_last_selection)
14024 .map(|result| (last_selection.start, result))
14025 .chain(
14026 query
14027 .stream_find_iter(bytes_after_first_selection)
14028 .map(|result| (buffer.len(), result)),
14029 );
14030 for (end_offset, query_match) in query_matches {
14031 let query_match = query_match.unwrap(); // can only fail due to I/O
14032 let offset_range =
14033 end_offset - query_match.end()..end_offset - query_match.start();
14034
14035 if !select_prev_state.wordwise
14036 || (!buffer.is_inside_word(offset_range.start, false)
14037 && !buffer.is_inside_word(offset_range.end, false))
14038 {
14039 next_selected_range = Some(offset_range);
14040 break;
14041 }
14042 }
14043
14044 if let Some(next_selected_range) = next_selected_range {
14045 self.select_match_ranges(
14046 next_selected_range,
14047 last_selection.reversed,
14048 action.replace_newest,
14049 Some(Autoscroll::newest()),
14050 window,
14051 cx,
14052 );
14053 } else {
14054 select_prev_state.done = true;
14055 }
14056 }
14057
14058 self.select_prev_state = Some(select_prev_state);
14059 } else {
14060 let mut only_carets = true;
14061 let mut same_text_selected = true;
14062 let mut selected_text = None;
14063
14064 let mut selections_iter = selections.iter().peekable();
14065 while let Some(selection) = selections_iter.next() {
14066 if selection.start != selection.end {
14067 only_carets = false;
14068 }
14069
14070 if same_text_selected {
14071 if selected_text.is_none() {
14072 selected_text =
14073 Some(buffer.text_for_range(selection.range()).collect::<String>());
14074 }
14075
14076 if let Some(next_selection) = selections_iter.peek() {
14077 if next_selection.range().len() == selection.range().len() {
14078 let next_selected_text = buffer
14079 .text_for_range(next_selection.range())
14080 .collect::<String>();
14081 if Some(next_selected_text) != selected_text {
14082 same_text_selected = false;
14083 selected_text = None;
14084 }
14085 } else {
14086 same_text_selected = false;
14087 selected_text = None;
14088 }
14089 }
14090 }
14091 }
14092
14093 if only_carets {
14094 for selection in &mut selections {
14095 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14096 selection.start = word_range.start;
14097 selection.end = word_range.end;
14098 selection.goal = SelectionGoal::None;
14099 selection.reversed = false;
14100 self.select_match_ranges(
14101 selection.start..selection.end,
14102 selection.reversed,
14103 action.replace_newest,
14104 Some(Autoscroll::newest()),
14105 window,
14106 cx,
14107 );
14108 }
14109 if selections.len() == 1 {
14110 let selection = selections
14111 .last()
14112 .expect("ensured that there's only one selection");
14113 let query = buffer
14114 .text_for_range(selection.start..selection.end)
14115 .collect::<String>();
14116 let is_empty = query.is_empty();
14117 let select_state = SelectNextState {
14118 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14119 wordwise: true,
14120 done: is_empty,
14121 };
14122 self.select_prev_state = Some(select_state);
14123 } else {
14124 self.select_prev_state = None;
14125 }
14126 } else if let Some(selected_text) = selected_text {
14127 self.select_prev_state = Some(SelectNextState {
14128 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14129 wordwise: false,
14130 done: false,
14131 });
14132 self.select_previous(action, window, cx)?;
14133 }
14134 }
14135 Ok(())
14136 }
14137
14138 pub fn find_next_match(
14139 &mut self,
14140 _: &FindNextMatch,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) -> Result<()> {
14144 let selections = self.selections.disjoint_anchors();
14145 match selections.first() {
14146 Some(first) if selections.len() >= 2 => {
14147 self.change_selections(Default::default(), window, cx, |s| {
14148 s.select_ranges([first.range()]);
14149 });
14150 }
14151 _ => self.select_next(
14152 &SelectNext {
14153 replace_newest: true,
14154 },
14155 window,
14156 cx,
14157 )?,
14158 }
14159 Ok(())
14160 }
14161
14162 pub fn find_previous_match(
14163 &mut self,
14164 _: &FindPreviousMatch,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) -> Result<()> {
14168 let selections = self.selections.disjoint_anchors();
14169 match selections.last() {
14170 Some(last) if selections.len() >= 2 => {
14171 self.change_selections(Default::default(), window, cx, |s| {
14172 s.select_ranges([last.range()]);
14173 });
14174 }
14175 _ => self.select_previous(
14176 &SelectPrevious {
14177 replace_newest: true,
14178 },
14179 window,
14180 cx,
14181 )?,
14182 }
14183 Ok(())
14184 }
14185
14186 pub fn toggle_comments(
14187 &mut self,
14188 action: &ToggleComments,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) {
14192 if self.read_only(cx) {
14193 return;
14194 }
14195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14196 let text_layout_details = &self.text_layout_details(window);
14197 self.transact(window, cx, |this, window, cx| {
14198 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14199 let mut edits = Vec::new();
14200 let mut selection_edit_ranges = Vec::new();
14201 let mut last_toggled_row = None;
14202 let snapshot = this.buffer.read(cx).read(cx);
14203 let empty_str: Arc<str> = Arc::default();
14204 let mut suffixes_inserted = Vec::new();
14205 let ignore_indent = action.ignore_indent;
14206
14207 fn comment_prefix_range(
14208 snapshot: &MultiBufferSnapshot,
14209 row: MultiBufferRow,
14210 comment_prefix: &str,
14211 comment_prefix_whitespace: &str,
14212 ignore_indent: bool,
14213 ) -> Range<Point> {
14214 let indent_size = if ignore_indent {
14215 0
14216 } else {
14217 snapshot.indent_size_for_line(row).len
14218 };
14219
14220 let start = Point::new(row.0, indent_size);
14221
14222 let mut line_bytes = snapshot
14223 .bytes_in_range(start..snapshot.max_point())
14224 .flatten()
14225 .copied();
14226
14227 // If this line currently begins with the line comment prefix, then record
14228 // the range containing the prefix.
14229 if line_bytes
14230 .by_ref()
14231 .take(comment_prefix.len())
14232 .eq(comment_prefix.bytes())
14233 {
14234 // Include any whitespace that matches the comment prefix.
14235 let matching_whitespace_len = line_bytes
14236 .zip(comment_prefix_whitespace.bytes())
14237 .take_while(|(a, b)| a == b)
14238 .count() as u32;
14239 let end = Point::new(
14240 start.row,
14241 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14242 );
14243 start..end
14244 } else {
14245 start..start
14246 }
14247 }
14248
14249 fn comment_suffix_range(
14250 snapshot: &MultiBufferSnapshot,
14251 row: MultiBufferRow,
14252 comment_suffix: &str,
14253 comment_suffix_has_leading_space: bool,
14254 ) -> Range<Point> {
14255 let end = Point::new(row.0, snapshot.line_len(row));
14256 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14257
14258 let mut line_end_bytes = snapshot
14259 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14260 .flatten()
14261 .copied();
14262
14263 let leading_space_len = if suffix_start_column > 0
14264 && line_end_bytes.next() == Some(b' ')
14265 && comment_suffix_has_leading_space
14266 {
14267 1
14268 } else {
14269 0
14270 };
14271
14272 // If this line currently begins with the line comment prefix, then record
14273 // the range containing the prefix.
14274 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14275 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14276 start..end
14277 } else {
14278 end..end
14279 }
14280 }
14281
14282 // TODO: Handle selections that cross excerpts
14283 for selection in &mut selections {
14284 let start_column = snapshot
14285 .indent_size_for_line(MultiBufferRow(selection.start.row))
14286 .len;
14287 let language = if let Some(language) =
14288 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14289 {
14290 language
14291 } else {
14292 continue;
14293 };
14294
14295 selection_edit_ranges.clear();
14296
14297 // If multiple selections contain a given row, avoid processing that
14298 // row more than once.
14299 let mut start_row = MultiBufferRow(selection.start.row);
14300 if last_toggled_row == Some(start_row) {
14301 start_row = start_row.next_row();
14302 }
14303 let end_row =
14304 if selection.end.row > selection.start.row && selection.end.column == 0 {
14305 MultiBufferRow(selection.end.row - 1)
14306 } else {
14307 MultiBufferRow(selection.end.row)
14308 };
14309 last_toggled_row = Some(end_row);
14310
14311 if start_row > end_row {
14312 continue;
14313 }
14314
14315 // If the language has line comments, toggle those.
14316 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14317
14318 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14319 if ignore_indent {
14320 full_comment_prefixes = full_comment_prefixes
14321 .into_iter()
14322 .map(|s| Arc::from(s.trim_end()))
14323 .collect();
14324 }
14325
14326 if !full_comment_prefixes.is_empty() {
14327 let first_prefix = full_comment_prefixes
14328 .first()
14329 .expect("prefixes is non-empty");
14330 let prefix_trimmed_lengths = full_comment_prefixes
14331 .iter()
14332 .map(|p| p.trim_end_matches(' ').len())
14333 .collect::<SmallVec<[usize; 4]>>();
14334
14335 let mut all_selection_lines_are_comments = true;
14336
14337 for row in start_row.0..=end_row.0 {
14338 let row = MultiBufferRow(row);
14339 if start_row < end_row && snapshot.is_line_blank(row) {
14340 continue;
14341 }
14342
14343 let prefix_range = full_comment_prefixes
14344 .iter()
14345 .zip(prefix_trimmed_lengths.iter().copied())
14346 .map(|(prefix, trimmed_prefix_len)| {
14347 comment_prefix_range(
14348 snapshot.deref(),
14349 row,
14350 &prefix[..trimmed_prefix_len],
14351 &prefix[trimmed_prefix_len..],
14352 ignore_indent,
14353 )
14354 })
14355 .max_by_key(|range| range.end.column - range.start.column)
14356 .expect("prefixes is non-empty");
14357
14358 if prefix_range.is_empty() {
14359 all_selection_lines_are_comments = false;
14360 }
14361
14362 selection_edit_ranges.push(prefix_range);
14363 }
14364
14365 if all_selection_lines_are_comments {
14366 edits.extend(
14367 selection_edit_ranges
14368 .iter()
14369 .cloned()
14370 .map(|range| (range, empty_str.clone())),
14371 );
14372 } else {
14373 let min_column = selection_edit_ranges
14374 .iter()
14375 .map(|range| range.start.column)
14376 .min()
14377 .unwrap_or(0);
14378 edits.extend(selection_edit_ranges.iter().map(|range| {
14379 let position = Point::new(range.start.row, min_column);
14380 (position..position, first_prefix.clone())
14381 }));
14382 }
14383 } else if let Some(BlockCommentConfig {
14384 start: full_comment_prefix,
14385 end: comment_suffix,
14386 ..
14387 }) = language.block_comment()
14388 {
14389 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14390 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14391 let prefix_range = comment_prefix_range(
14392 snapshot.deref(),
14393 start_row,
14394 comment_prefix,
14395 comment_prefix_whitespace,
14396 ignore_indent,
14397 );
14398 let suffix_range = comment_suffix_range(
14399 snapshot.deref(),
14400 end_row,
14401 comment_suffix.trim_start_matches(' '),
14402 comment_suffix.starts_with(' '),
14403 );
14404
14405 if prefix_range.is_empty() || suffix_range.is_empty() {
14406 edits.push((
14407 prefix_range.start..prefix_range.start,
14408 full_comment_prefix.clone(),
14409 ));
14410 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14411 suffixes_inserted.push((end_row, comment_suffix.len()));
14412 } else {
14413 edits.push((prefix_range, empty_str.clone()));
14414 edits.push((suffix_range, empty_str.clone()));
14415 }
14416 } else {
14417 continue;
14418 }
14419 }
14420
14421 drop(snapshot);
14422 this.buffer.update(cx, |buffer, cx| {
14423 buffer.edit(edits, None, cx);
14424 });
14425
14426 // Adjust selections so that they end before any comment suffixes that
14427 // were inserted.
14428 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14429 let mut selections = this.selections.all::<Point>(cx);
14430 let snapshot = this.buffer.read(cx).read(cx);
14431 for selection in &mut selections {
14432 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14433 match row.cmp(&MultiBufferRow(selection.end.row)) {
14434 Ordering::Less => {
14435 suffixes_inserted.next();
14436 continue;
14437 }
14438 Ordering::Greater => break,
14439 Ordering::Equal => {
14440 if selection.end.column == snapshot.line_len(row) {
14441 if selection.is_empty() {
14442 selection.start.column -= suffix_len as u32;
14443 }
14444 selection.end.column -= suffix_len as u32;
14445 }
14446 break;
14447 }
14448 }
14449 }
14450 }
14451
14452 drop(snapshot);
14453 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14454
14455 let selections = this.selections.all::<Point>(cx);
14456 let selections_on_single_row = selections.windows(2).all(|selections| {
14457 selections[0].start.row == selections[1].start.row
14458 && selections[0].end.row == selections[1].end.row
14459 && selections[0].start.row == selections[0].end.row
14460 });
14461 let selections_selecting = selections
14462 .iter()
14463 .any(|selection| selection.start != selection.end);
14464 let advance_downwards = action.advance_downwards
14465 && selections_on_single_row
14466 && !selections_selecting
14467 && !matches!(this.mode, EditorMode::SingleLine { .. });
14468
14469 if advance_downwards {
14470 let snapshot = this.buffer.read(cx).snapshot(cx);
14471
14472 this.change_selections(Default::default(), window, cx, |s| {
14473 s.move_cursors_with(|display_snapshot, display_point, _| {
14474 let mut point = display_point.to_point(display_snapshot);
14475 point.row += 1;
14476 point = snapshot.clip_point(point, Bias::Left);
14477 let display_point = point.to_display_point(display_snapshot);
14478 let goal = SelectionGoal::HorizontalPosition(
14479 display_snapshot
14480 .x_for_display_point(display_point, text_layout_details)
14481 .into(),
14482 );
14483 (display_point, goal)
14484 })
14485 });
14486 }
14487 });
14488 }
14489
14490 pub fn select_enclosing_symbol(
14491 &mut self,
14492 _: &SelectEnclosingSymbol,
14493 window: &mut Window,
14494 cx: &mut Context<Self>,
14495 ) {
14496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14497
14498 let buffer = self.buffer.read(cx).snapshot(cx);
14499 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14500
14501 fn update_selection(
14502 selection: &Selection<usize>,
14503 buffer_snap: &MultiBufferSnapshot,
14504 ) -> Option<Selection<usize>> {
14505 let cursor = selection.head();
14506 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14507 for symbol in symbols.iter().rev() {
14508 let start = symbol.range.start.to_offset(buffer_snap);
14509 let end = symbol.range.end.to_offset(buffer_snap);
14510 let new_range = start..end;
14511 if start < selection.start || end > selection.end {
14512 return Some(Selection {
14513 id: selection.id,
14514 start: new_range.start,
14515 end: new_range.end,
14516 goal: SelectionGoal::None,
14517 reversed: selection.reversed,
14518 });
14519 }
14520 }
14521 None
14522 }
14523
14524 let mut selected_larger_symbol = false;
14525 let new_selections = old_selections
14526 .iter()
14527 .map(|selection| match update_selection(selection, &buffer) {
14528 Some(new_selection) => {
14529 if new_selection.range() != selection.range() {
14530 selected_larger_symbol = true;
14531 }
14532 new_selection
14533 }
14534 None => selection.clone(),
14535 })
14536 .collect::<Vec<_>>();
14537
14538 if selected_larger_symbol {
14539 self.change_selections(Default::default(), window, cx, |s| {
14540 s.select(new_selections);
14541 });
14542 }
14543 }
14544
14545 pub fn select_larger_syntax_node(
14546 &mut self,
14547 _: &SelectLargerSyntaxNode,
14548 window: &mut Window,
14549 cx: &mut Context<Self>,
14550 ) {
14551 let Some(visible_row_count) = self.visible_row_count() else {
14552 return;
14553 };
14554 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14555 if old_selections.is_empty() {
14556 return;
14557 }
14558
14559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14560
14561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14562 let buffer = self.buffer.read(cx).snapshot(cx);
14563
14564 let mut selected_larger_node = false;
14565 let mut new_selections = old_selections
14566 .iter()
14567 .map(|selection| {
14568 let old_range = selection.start..selection.end;
14569
14570 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14571 // manually select word at selection
14572 if ["string_content", "inline"].contains(&node.kind()) {
14573 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14574 // ignore if word is already selected
14575 if !word_range.is_empty() && old_range != word_range {
14576 let (last_word_range, _) =
14577 buffer.surrounding_word(old_range.end, false);
14578 // only select word if start and end point belongs to same word
14579 if word_range == last_word_range {
14580 selected_larger_node = true;
14581 return Selection {
14582 id: selection.id,
14583 start: word_range.start,
14584 end: word_range.end,
14585 goal: SelectionGoal::None,
14586 reversed: selection.reversed,
14587 };
14588 }
14589 }
14590 }
14591 }
14592
14593 let mut new_range = old_range.clone();
14594 while let Some((_node, containing_range)) =
14595 buffer.syntax_ancestor(new_range.clone())
14596 {
14597 new_range = match containing_range {
14598 MultiOrSingleBufferOffsetRange::Single(_) => break,
14599 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14600 };
14601 if !display_map.intersects_fold(new_range.start)
14602 && !display_map.intersects_fold(new_range.end)
14603 {
14604 break;
14605 }
14606 }
14607
14608 selected_larger_node |= new_range != old_range;
14609 Selection {
14610 id: selection.id,
14611 start: new_range.start,
14612 end: new_range.end,
14613 goal: SelectionGoal::None,
14614 reversed: selection.reversed,
14615 }
14616 })
14617 .collect::<Vec<_>>();
14618
14619 if !selected_larger_node {
14620 return; // don't put this call in the history
14621 }
14622
14623 // scroll based on transformation done to the last selection created by the user
14624 let (last_old, last_new) = old_selections
14625 .last()
14626 .zip(new_selections.last().cloned())
14627 .expect("old_selections isn't empty");
14628
14629 // revert selection
14630 let is_selection_reversed = {
14631 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14632 new_selections.last_mut().expect("checked above").reversed =
14633 should_newest_selection_be_reversed;
14634 should_newest_selection_be_reversed
14635 };
14636
14637 if selected_larger_node {
14638 self.select_syntax_node_history.disable_clearing = true;
14639 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14640 s.select(new_selections.clone());
14641 });
14642 self.select_syntax_node_history.disable_clearing = false;
14643 }
14644
14645 let start_row = last_new.start.to_display_point(&display_map).row().0;
14646 let end_row = last_new.end.to_display_point(&display_map).row().0;
14647 let selection_height = end_row - start_row + 1;
14648 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14649
14650 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14651 let scroll_behavior = if fits_on_the_screen {
14652 self.request_autoscroll(Autoscroll::fit(), cx);
14653 SelectSyntaxNodeScrollBehavior::FitSelection
14654 } else if is_selection_reversed {
14655 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14656 SelectSyntaxNodeScrollBehavior::CursorTop
14657 } else {
14658 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14659 SelectSyntaxNodeScrollBehavior::CursorBottom
14660 };
14661
14662 self.select_syntax_node_history.push((
14663 old_selections,
14664 scroll_behavior,
14665 is_selection_reversed,
14666 ));
14667 }
14668
14669 pub fn select_smaller_syntax_node(
14670 &mut self,
14671 _: &SelectSmallerSyntaxNode,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14676
14677 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14678 self.select_syntax_node_history.pop()
14679 {
14680 if let Some(selection) = selections.last_mut() {
14681 selection.reversed = is_selection_reversed;
14682 }
14683
14684 self.select_syntax_node_history.disable_clearing = true;
14685 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14686 s.select(selections.to_vec());
14687 });
14688 self.select_syntax_node_history.disable_clearing = false;
14689
14690 match scroll_behavior {
14691 SelectSyntaxNodeScrollBehavior::CursorTop => {
14692 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14693 }
14694 SelectSyntaxNodeScrollBehavior::FitSelection => {
14695 self.request_autoscroll(Autoscroll::fit(), cx);
14696 }
14697 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14698 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14699 }
14700 }
14701 }
14702 }
14703
14704 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14705 if !EditorSettings::get_global(cx).gutter.runnables {
14706 self.clear_tasks();
14707 return Task::ready(());
14708 }
14709 let project = self.project.as_ref().map(Entity::downgrade);
14710 let task_sources = self.lsp_task_sources(cx);
14711 let multi_buffer = self.buffer.downgrade();
14712 cx.spawn_in(window, async move |editor, cx| {
14713 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14714 let Some(project) = project.and_then(|p| p.upgrade()) else {
14715 return;
14716 };
14717 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14718 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14719 }) else {
14720 return;
14721 };
14722
14723 let hide_runnables = project
14724 .update(cx, |project, cx| {
14725 // Do not display any test indicators in non-dev server remote projects.
14726 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14727 })
14728 .unwrap_or(true);
14729 if hide_runnables {
14730 return;
14731 }
14732 let new_rows =
14733 cx.background_spawn({
14734 let snapshot = display_snapshot.clone();
14735 async move {
14736 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14737 }
14738 })
14739 .await;
14740 let Ok(lsp_tasks) =
14741 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14742 else {
14743 return;
14744 };
14745 let lsp_tasks = lsp_tasks.await;
14746
14747 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14748 lsp_tasks
14749 .into_iter()
14750 .flat_map(|(kind, tasks)| {
14751 tasks.into_iter().filter_map(move |(location, task)| {
14752 Some((kind.clone(), location?, task))
14753 })
14754 })
14755 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14756 let buffer = location.target.buffer;
14757 let buffer_snapshot = buffer.read(cx).snapshot();
14758 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14759 |(excerpt_id, snapshot, _)| {
14760 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14761 display_snapshot
14762 .buffer_snapshot
14763 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14764 } else {
14765 None
14766 }
14767 },
14768 );
14769 if let Some(offset) = offset {
14770 let task_buffer_range =
14771 location.target.range.to_point(&buffer_snapshot);
14772 let context_buffer_range =
14773 task_buffer_range.to_offset(&buffer_snapshot);
14774 let context_range = BufferOffset(context_buffer_range.start)
14775 ..BufferOffset(context_buffer_range.end);
14776
14777 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14778 .or_insert_with(|| RunnableTasks {
14779 templates: Vec::new(),
14780 offset,
14781 column: task_buffer_range.start.column,
14782 extra_variables: HashMap::default(),
14783 context_range,
14784 })
14785 .templates
14786 .push((kind, task.original_task().clone()));
14787 }
14788
14789 acc
14790 })
14791 }) else {
14792 return;
14793 };
14794
14795 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14796 buffer.language_settings(cx).tasks.prefer_lsp
14797 }) else {
14798 return;
14799 };
14800
14801 let rows = Self::runnable_rows(
14802 project,
14803 display_snapshot,
14804 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14805 new_rows,
14806 cx.clone(),
14807 )
14808 .await;
14809 editor
14810 .update(cx, |editor, _| {
14811 editor.clear_tasks();
14812 for (key, mut value) in rows {
14813 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14814 value.templates.extend(lsp_tasks.templates);
14815 }
14816
14817 editor.insert_tasks(key, value);
14818 }
14819 for (key, value) in lsp_tasks_by_rows {
14820 editor.insert_tasks(key, value);
14821 }
14822 })
14823 .ok();
14824 })
14825 }
14826 fn fetch_runnable_ranges(
14827 snapshot: &DisplaySnapshot,
14828 range: Range<Anchor>,
14829 ) -> Vec<language::RunnableRange> {
14830 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14831 }
14832
14833 fn runnable_rows(
14834 project: Entity<Project>,
14835 snapshot: DisplaySnapshot,
14836 prefer_lsp: bool,
14837 runnable_ranges: Vec<RunnableRange>,
14838 cx: AsyncWindowContext,
14839 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14840 cx.spawn(async move |cx| {
14841 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14842 for mut runnable in runnable_ranges {
14843 let Some(tasks) = cx
14844 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14845 .ok()
14846 else {
14847 continue;
14848 };
14849 let mut tasks = tasks.await;
14850
14851 if prefer_lsp {
14852 tasks.retain(|(task_kind, _)| {
14853 !matches!(task_kind, TaskSourceKind::Language { .. })
14854 });
14855 }
14856 if tasks.is_empty() {
14857 continue;
14858 }
14859
14860 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14861 let Some(row) = snapshot
14862 .buffer_snapshot
14863 .buffer_line_for_row(MultiBufferRow(point.row))
14864 .map(|(_, range)| range.start.row)
14865 else {
14866 continue;
14867 };
14868
14869 let context_range =
14870 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14871 runnable_rows.push((
14872 (runnable.buffer_id, row),
14873 RunnableTasks {
14874 templates: tasks,
14875 offset: snapshot
14876 .buffer_snapshot
14877 .anchor_before(runnable.run_range.start),
14878 context_range,
14879 column: point.column,
14880 extra_variables: runnable.extra_captures,
14881 },
14882 ));
14883 }
14884 runnable_rows
14885 })
14886 }
14887
14888 fn templates_with_tags(
14889 project: &Entity<Project>,
14890 runnable: &mut Runnable,
14891 cx: &mut App,
14892 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14893 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14894 let (worktree_id, file) = project
14895 .buffer_for_id(runnable.buffer, cx)
14896 .and_then(|buffer| buffer.read(cx).file())
14897 .map(|file| (file.worktree_id(cx), file.clone()))
14898 .unzip();
14899
14900 (
14901 project.task_store().read(cx).task_inventory().cloned(),
14902 worktree_id,
14903 file,
14904 )
14905 });
14906
14907 let tags = mem::take(&mut runnable.tags);
14908 let language = runnable.language.clone();
14909 cx.spawn(async move |cx| {
14910 let mut templates_with_tags = Vec::new();
14911 if let Some(inventory) = inventory {
14912 for RunnableTag(tag) in tags {
14913 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14914 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14915 }) else {
14916 return templates_with_tags;
14917 };
14918 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14919 move |(_, template)| {
14920 template.tags.iter().any(|source_tag| source_tag == &tag)
14921 },
14922 ));
14923 }
14924 }
14925 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14926
14927 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14928 // Strongest source wins; if we have worktree tag binding, prefer that to
14929 // global and language bindings;
14930 // if we have a global binding, prefer that to language binding.
14931 let first_mismatch = templates_with_tags
14932 .iter()
14933 .position(|(tag_source, _)| tag_source != leading_tag_source);
14934 if let Some(index) = first_mismatch {
14935 templates_with_tags.truncate(index);
14936 }
14937 }
14938
14939 templates_with_tags
14940 })
14941 }
14942
14943 pub fn move_to_enclosing_bracket(
14944 &mut self,
14945 _: &MoveToEnclosingBracket,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) {
14949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14950 self.change_selections(Default::default(), window, cx, |s| {
14951 s.move_offsets_with(|snapshot, selection| {
14952 let Some(enclosing_bracket_ranges) =
14953 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14954 else {
14955 return;
14956 };
14957
14958 let mut best_length = usize::MAX;
14959 let mut best_inside = false;
14960 let mut best_in_bracket_range = false;
14961 let mut best_destination = None;
14962 for (open, close) in enclosing_bracket_ranges {
14963 let close = close.to_inclusive();
14964 let length = close.end() - open.start;
14965 let inside = selection.start >= open.end && selection.end <= *close.start();
14966 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14967 || close.contains(&selection.head());
14968
14969 // If best is next to a bracket and current isn't, skip
14970 if !in_bracket_range && best_in_bracket_range {
14971 continue;
14972 }
14973
14974 // Prefer smaller lengths unless best is inside and current isn't
14975 if length > best_length && (best_inside || !inside) {
14976 continue;
14977 }
14978
14979 best_length = length;
14980 best_inside = inside;
14981 best_in_bracket_range = in_bracket_range;
14982 best_destination = Some(
14983 if close.contains(&selection.start) && close.contains(&selection.end) {
14984 if inside { open.end } else { open.start }
14985 } else if inside {
14986 *close.start()
14987 } else {
14988 *close.end()
14989 },
14990 );
14991 }
14992
14993 if let Some(destination) = best_destination {
14994 selection.collapse_to(destination, SelectionGoal::None);
14995 }
14996 })
14997 });
14998 }
14999
15000 pub fn undo_selection(
15001 &mut self,
15002 _: &UndoSelection,
15003 window: &mut Window,
15004 cx: &mut Context<Self>,
15005 ) {
15006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15007 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15008 self.selection_history.mode = SelectionHistoryMode::Undoing;
15009 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15010 this.end_selection(window, cx);
15011 this.change_selections(
15012 SelectionEffects::scroll(Autoscroll::newest()),
15013 window,
15014 cx,
15015 |s| s.select_anchors(entry.selections.to_vec()),
15016 );
15017 });
15018 self.selection_history.mode = SelectionHistoryMode::Normal;
15019
15020 self.select_next_state = entry.select_next_state;
15021 self.select_prev_state = entry.select_prev_state;
15022 self.add_selections_state = entry.add_selections_state;
15023 }
15024 }
15025
15026 pub fn redo_selection(
15027 &mut self,
15028 _: &RedoSelection,
15029 window: &mut Window,
15030 cx: &mut Context<Self>,
15031 ) {
15032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15033 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15034 self.selection_history.mode = SelectionHistoryMode::Redoing;
15035 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15036 this.end_selection(window, cx);
15037 this.change_selections(
15038 SelectionEffects::scroll(Autoscroll::newest()),
15039 window,
15040 cx,
15041 |s| s.select_anchors(entry.selections.to_vec()),
15042 );
15043 });
15044 self.selection_history.mode = SelectionHistoryMode::Normal;
15045
15046 self.select_next_state = entry.select_next_state;
15047 self.select_prev_state = entry.select_prev_state;
15048 self.add_selections_state = entry.add_selections_state;
15049 }
15050 }
15051
15052 pub fn expand_excerpts(
15053 &mut self,
15054 action: &ExpandExcerpts,
15055 _: &mut Window,
15056 cx: &mut Context<Self>,
15057 ) {
15058 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15059 }
15060
15061 pub fn expand_excerpts_down(
15062 &mut self,
15063 action: &ExpandExcerptsDown,
15064 _: &mut Window,
15065 cx: &mut Context<Self>,
15066 ) {
15067 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15068 }
15069
15070 pub fn expand_excerpts_up(
15071 &mut self,
15072 action: &ExpandExcerptsUp,
15073 _: &mut Window,
15074 cx: &mut Context<Self>,
15075 ) {
15076 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15077 }
15078
15079 pub fn expand_excerpts_for_direction(
15080 &mut self,
15081 lines: u32,
15082 direction: ExpandExcerptDirection,
15083
15084 cx: &mut Context<Self>,
15085 ) {
15086 let selections = self.selections.disjoint_anchors();
15087
15088 let lines = if lines == 0 {
15089 EditorSettings::get_global(cx).expand_excerpt_lines
15090 } else {
15091 lines
15092 };
15093
15094 self.buffer.update(cx, |buffer, cx| {
15095 let snapshot = buffer.snapshot(cx);
15096 let mut excerpt_ids = selections
15097 .iter()
15098 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15099 .collect::<Vec<_>>();
15100 excerpt_ids.sort();
15101 excerpt_ids.dedup();
15102 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15103 })
15104 }
15105
15106 pub fn expand_excerpt(
15107 &mut self,
15108 excerpt: ExcerptId,
15109 direction: ExpandExcerptDirection,
15110 window: &mut Window,
15111 cx: &mut Context<Self>,
15112 ) {
15113 let current_scroll_position = self.scroll_position(cx);
15114 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15115 let mut should_scroll_up = false;
15116
15117 if direction == ExpandExcerptDirection::Down {
15118 let multi_buffer = self.buffer.read(cx);
15119 let snapshot = multi_buffer.snapshot(cx);
15120 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15121 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15122 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15123 let buffer_snapshot = buffer.read(cx).snapshot();
15124 let excerpt_end_row =
15125 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15126 let last_row = buffer_snapshot.max_point().row;
15127 let lines_below = last_row.saturating_sub(excerpt_end_row);
15128 should_scroll_up = lines_below >= lines_to_expand;
15129 }
15130 }
15131 }
15132 }
15133
15134 self.buffer.update(cx, |buffer, cx| {
15135 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15136 });
15137
15138 if should_scroll_up {
15139 let new_scroll_position =
15140 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15141 self.set_scroll_position(new_scroll_position, window, cx);
15142 }
15143 }
15144
15145 pub fn go_to_singleton_buffer_point(
15146 &mut self,
15147 point: Point,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 self.go_to_singleton_buffer_range(point..point, window, cx);
15152 }
15153
15154 pub fn go_to_singleton_buffer_range(
15155 &mut self,
15156 range: Range<Point>,
15157 window: &mut Window,
15158 cx: &mut Context<Self>,
15159 ) {
15160 let multibuffer = self.buffer().read(cx);
15161 let Some(buffer) = multibuffer.as_singleton() else {
15162 return;
15163 };
15164 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15165 return;
15166 };
15167 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15168 return;
15169 };
15170 self.change_selections(
15171 SelectionEffects::default().nav_history(true),
15172 window,
15173 cx,
15174 |s| s.select_anchor_ranges([start..end]),
15175 );
15176 }
15177
15178 pub fn go_to_diagnostic(
15179 &mut self,
15180 action: &GoToDiagnostic,
15181 window: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 if !self.diagnostics_enabled() {
15185 return;
15186 }
15187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15188 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15189 }
15190
15191 pub fn go_to_prev_diagnostic(
15192 &mut self,
15193 action: &GoToPreviousDiagnostic,
15194 window: &mut Window,
15195 cx: &mut Context<Self>,
15196 ) {
15197 if !self.diagnostics_enabled() {
15198 return;
15199 }
15200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15201 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15202 }
15203
15204 pub fn go_to_diagnostic_impl(
15205 &mut self,
15206 direction: Direction,
15207 severity: GoToDiagnosticSeverityFilter,
15208 window: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 let buffer = self.buffer.read(cx).snapshot(cx);
15212 let selection = self.selections.newest::<usize>(cx);
15213
15214 let mut active_group_id = None;
15215 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15216 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15217 active_group_id = Some(active_group.group_id);
15218 }
15219 }
15220
15221 fn filtered(
15222 snapshot: EditorSnapshot,
15223 severity: GoToDiagnosticSeverityFilter,
15224 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15225 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15226 diagnostics
15227 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15228 .filter(|entry| entry.range.start != entry.range.end)
15229 .filter(|entry| !entry.diagnostic.is_unnecessary)
15230 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15231 }
15232
15233 let snapshot = self.snapshot(window, cx);
15234 let before = filtered(
15235 snapshot.clone(),
15236 severity,
15237 buffer
15238 .diagnostics_in_range(0..selection.start)
15239 .filter(|entry| entry.range.start <= selection.start),
15240 );
15241 let after = filtered(
15242 snapshot,
15243 severity,
15244 buffer
15245 .diagnostics_in_range(selection.start..buffer.len())
15246 .filter(|entry| entry.range.start >= selection.start),
15247 );
15248
15249 let mut found: Option<DiagnosticEntry<usize>> = None;
15250 if direction == Direction::Prev {
15251 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15252 {
15253 for diagnostic in prev_diagnostics.into_iter().rev() {
15254 if diagnostic.range.start != selection.start
15255 || active_group_id
15256 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15257 {
15258 found = Some(diagnostic);
15259 break 'outer;
15260 }
15261 }
15262 }
15263 } else {
15264 for diagnostic in after.chain(before) {
15265 if diagnostic.range.start != selection.start
15266 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15267 {
15268 found = Some(diagnostic);
15269 break;
15270 }
15271 }
15272 }
15273 let Some(next_diagnostic) = found else {
15274 return;
15275 };
15276
15277 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15278 return;
15279 };
15280 self.change_selections(Default::default(), window, cx, |s| {
15281 s.select_ranges(vec![
15282 next_diagnostic.range.start..next_diagnostic.range.start,
15283 ])
15284 });
15285 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15286 self.refresh_inline_completion(false, true, window, cx);
15287 }
15288
15289 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15291 let snapshot = self.snapshot(window, cx);
15292 let selection = self.selections.newest::<Point>(cx);
15293 self.go_to_hunk_before_or_after_position(
15294 &snapshot,
15295 selection.head(),
15296 Direction::Next,
15297 window,
15298 cx,
15299 );
15300 }
15301
15302 pub fn go_to_hunk_before_or_after_position(
15303 &mut self,
15304 snapshot: &EditorSnapshot,
15305 position: Point,
15306 direction: Direction,
15307 window: &mut Window,
15308 cx: &mut Context<Editor>,
15309 ) {
15310 let row = if direction == Direction::Next {
15311 self.hunk_after_position(snapshot, position)
15312 .map(|hunk| hunk.row_range.start)
15313 } else {
15314 self.hunk_before_position(snapshot, position)
15315 };
15316
15317 if let Some(row) = row {
15318 let destination = Point::new(row.0, 0);
15319 let autoscroll = Autoscroll::center();
15320
15321 self.unfold_ranges(&[destination..destination], false, false, cx);
15322 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15323 s.select_ranges([destination..destination]);
15324 });
15325 }
15326 }
15327
15328 fn hunk_after_position(
15329 &mut self,
15330 snapshot: &EditorSnapshot,
15331 position: Point,
15332 ) -> Option<MultiBufferDiffHunk> {
15333 snapshot
15334 .buffer_snapshot
15335 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15336 .find(|hunk| hunk.row_range.start.0 > position.row)
15337 .or_else(|| {
15338 snapshot
15339 .buffer_snapshot
15340 .diff_hunks_in_range(Point::zero()..position)
15341 .find(|hunk| hunk.row_range.end.0 < position.row)
15342 })
15343 }
15344
15345 fn go_to_prev_hunk(
15346 &mut self,
15347 _: &GoToPreviousHunk,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) {
15351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15352 let snapshot = self.snapshot(window, cx);
15353 let selection = self.selections.newest::<Point>(cx);
15354 self.go_to_hunk_before_or_after_position(
15355 &snapshot,
15356 selection.head(),
15357 Direction::Prev,
15358 window,
15359 cx,
15360 );
15361 }
15362
15363 fn hunk_before_position(
15364 &mut self,
15365 snapshot: &EditorSnapshot,
15366 position: Point,
15367 ) -> Option<MultiBufferRow> {
15368 snapshot
15369 .buffer_snapshot
15370 .diff_hunk_before(position)
15371 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15372 }
15373
15374 fn go_to_next_change(
15375 &mut self,
15376 _: &GoToNextChange,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 if let Some(selections) = self
15381 .change_list
15382 .next_change(1, Direction::Next)
15383 .map(|s| s.to_vec())
15384 {
15385 self.change_selections(Default::default(), window, cx, |s| {
15386 let map = s.display_map();
15387 s.select_display_ranges(selections.iter().map(|a| {
15388 let point = a.to_display_point(&map);
15389 point..point
15390 }))
15391 })
15392 }
15393 }
15394
15395 fn go_to_previous_change(
15396 &mut self,
15397 _: &GoToPreviousChange,
15398 window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 if let Some(selections) = self
15402 .change_list
15403 .next_change(1, Direction::Prev)
15404 .map(|s| s.to_vec())
15405 {
15406 self.change_selections(Default::default(), window, cx, |s| {
15407 let map = s.display_map();
15408 s.select_display_ranges(selections.iter().map(|a| {
15409 let point = a.to_display_point(&map);
15410 point..point
15411 }))
15412 })
15413 }
15414 }
15415
15416 fn go_to_line<T: 'static>(
15417 &mut self,
15418 position: Anchor,
15419 highlight_color: Option<Hsla>,
15420 window: &mut Window,
15421 cx: &mut Context<Self>,
15422 ) {
15423 let snapshot = self.snapshot(window, cx).display_snapshot;
15424 let position = position.to_point(&snapshot.buffer_snapshot);
15425 let start = snapshot
15426 .buffer_snapshot
15427 .clip_point(Point::new(position.row, 0), Bias::Left);
15428 let end = start + Point::new(1, 0);
15429 let start = snapshot.buffer_snapshot.anchor_before(start);
15430 let end = snapshot.buffer_snapshot.anchor_before(end);
15431
15432 self.highlight_rows::<T>(
15433 start..end,
15434 highlight_color
15435 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15436 Default::default(),
15437 cx,
15438 );
15439
15440 if self.buffer.read(cx).is_singleton() {
15441 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15442 }
15443 }
15444
15445 pub fn go_to_definition(
15446 &mut self,
15447 _: &GoToDefinition,
15448 window: &mut Window,
15449 cx: &mut Context<Self>,
15450 ) -> Task<Result<Navigated>> {
15451 let definition =
15452 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15453 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15454 cx.spawn_in(window, async move |editor, cx| {
15455 if definition.await? == Navigated::Yes {
15456 return Ok(Navigated::Yes);
15457 }
15458 match fallback_strategy {
15459 GoToDefinitionFallback::None => Ok(Navigated::No),
15460 GoToDefinitionFallback::FindAllReferences => {
15461 match editor.update_in(cx, |editor, window, cx| {
15462 editor.find_all_references(&FindAllReferences, window, cx)
15463 })? {
15464 Some(references) => references.await,
15465 None => Ok(Navigated::No),
15466 }
15467 }
15468 }
15469 })
15470 }
15471
15472 pub fn go_to_declaration(
15473 &mut self,
15474 _: &GoToDeclaration,
15475 window: &mut Window,
15476 cx: &mut Context<Self>,
15477 ) -> Task<Result<Navigated>> {
15478 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15479 }
15480
15481 pub fn go_to_declaration_split(
15482 &mut self,
15483 _: &GoToDeclaration,
15484 window: &mut Window,
15485 cx: &mut Context<Self>,
15486 ) -> Task<Result<Navigated>> {
15487 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15488 }
15489
15490 pub fn go_to_implementation(
15491 &mut self,
15492 _: &GoToImplementation,
15493 window: &mut Window,
15494 cx: &mut Context<Self>,
15495 ) -> Task<Result<Navigated>> {
15496 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15497 }
15498
15499 pub fn go_to_implementation_split(
15500 &mut self,
15501 _: &GoToImplementationSplit,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) -> Task<Result<Navigated>> {
15505 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15506 }
15507
15508 pub fn go_to_type_definition(
15509 &mut self,
15510 _: &GoToTypeDefinition,
15511 window: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) -> Task<Result<Navigated>> {
15514 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15515 }
15516
15517 pub fn go_to_definition_split(
15518 &mut self,
15519 _: &GoToDefinitionSplit,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) -> Task<Result<Navigated>> {
15523 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15524 }
15525
15526 pub fn go_to_type_definition_split(
15527 &mut self,
15528 _: &GoToTypeDefinitionSplit,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) -> Task<Result<Navigated>> {
15532 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15533 }
15534
15535 fn go_to_definition_of_kind(
15536 &mut self,
15537 kind: GotoDefinitionKind,
15538 split: bool,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) -> Task<Result<Navigated>> {
15542 let Some(provider) = self.semantics_provider.clone() else {
15543 return Task::ready(Ok(Navigated::No));
15544 };
15545 let head = self.selections.newest::<usize>(cx).head();
15546 let buffer = self.buffer.read(cx);
15547 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15548 text_anchor
15549 } else {
15550 return Task::ready(Ok(Navigated::No));
15551 };
15552
15553 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15554 return Task::ready(Ok(Navigated::No));
15555 };
15556
15557 cx.spawn_in(window, async move |editor, cx| {
15558 let definitions = definitions.await?;
15559 let navigated = editor
15560 .update_in(cx, |editor, window, cx| {
15561 editor.navigate_to_hover_links(
15562 Some(kind),
15563 definitions
15564 .into_iter()
15565 .filter(|location| {
15566 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15567 })
15568 .map(HoverLink::Text)
15569 .collect::<Vec<_>>(),
15570 split,
15571 window,
15572 cx,
15573 )
15574 })?
15575 .await?;
15576 anyhow::Ok(navigated)
15577 })
15578 }
15579
15580 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15581 let selection = self.selections.newest_anchor();
15582 let head = selection.head();
15583 let tail = selection.tail();
15584
15585 let Some((buffer, start_position)) =
15586 self.buffer.read(cx).text_anchor_for_position(head, cx)
15587 else {
15588 return;
15589 };
15590
15591 let end_position = if head != tail {
15592 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15593 return;
15594 };
15595 Some(pos)
15596 } else {
15597 None
15598 };
15599
15600 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15601 let url = if let Some(end_pos) = end_position {
15602 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15603 } else {
15604 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15605 };
15606
15607 if let Some(url) = url {
15608 editor.update(cx, |_, cx| {
15609 cx.open_url(&url);
15610 })
15611 } else {
15612 Ok(())
15613 }
15614 });
15615
15616 url_finder.detach();
15617 }
15618
15619 pub fn open_selected_filename(
15620 &mut self,
15621 _: &OpenSelectedFilename,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 let Some(workspace) = self.workspace() else {
15626 return;
15627 };
15628
15629 let position = self.selections.newest_anchor().head();
15630
15631 let Some((buffer, buffer_position)) =
15632 self.buffer.read(cx).text_anchor_for_position(position, cx)
15633 else {
15634 return;
15635 };
15636
15637 let project = self.project.clone();
15638
15639 cx.spawn_in(window, async move |_, cx| {
15640 let result = find_file(&buffer, project, buffer_position, cx).await;
15641
15642 if let Some((_, path)) = result {
15643 workspace
15644 .update_in(cx, |workspace, window, cx| {
15645 workspace.open_resolved_path(path, window, cx)
15646 })?
15647 .await?;
15648 }
15649 anyhow::Ok(())
15650 })
15651 .detach();
15652 }
15653
15654 pub(crate) fn navigate_to_hover_links(
15655 &mut self,
15656 kind: Option<GotoDefinitionKind>,
15657 mut definitions: Vec<HoverLink>,
15658 split: bool,
15659 window: &mut Window,
15660 cx: &mut Context<Editor>,
15661 ) -> Task<Result<Navigated>> {
15662 // If there is one definition, just open it directly
15663 if definitions.len() == 1 {
15664 let definition = definitions.pop().unwrap();
15665
15666 enum TargetTaskResult {
15667 Location(Option<Location>),
15668 AlreadyNavigated,
15669 }
15670
15671 let target_task = match definition {
15672 HoverLink::Text(link) => {
15673 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15674 }
15675 HoverLink::InlayHint(lsp_location, server_id) => {
15676 let computation =
15677 self.compute_target_location(lsp_location, server_id, window, cx);
15678 cx.background_spawn(async move {
15679 let location = computation.await?;
15680 Ok(TargetTaskResult::Location(location))
15681 })
15682 }
15683 HoverLink::Url(url) => {
15684 cx.open_url(&url);
15685 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15686 }
15687 HoverLink::File(path) => {
15688 if let Some(workspace) = self.workspace() {
15689 cx.spawn_in(window, async move |_, cx| {
15690 workspace
15691 .update_in(cx, |workspace, window, cx| {
15692 workspace.open_resolved_path(path, window, cx)
15693 })?
15694 .await
15695 .map(|_| TargetTaskResult::AlreadyNavigated)
15696 })
15697 } else {
15698 Task::ready(Ok(TargetTaskResult::Location(None)))
15699 }
15700 }
15701 };
15702 cx.spawn_in(window, async move |editor, cx| {
15703 let target = match target_task.await.context("target resolution task")? {
15704 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15705 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15706 TargetTaskResult::Location(Some(target)) => target,
15707 };
15708
15709 editor.update_in(cx, |editor, window, cx| {
15710 let Some(workspace) = editor.workspace() else {
15711 return Navigated::No;
15712 };
15713 let pane = workspace.read(cx).active_pane().clone();
15714
15715 let range = target.range.to_point(target.buffer.read(cx));
15716 let range = editor.range_for_match(&range);
15717 let range = collapse_multiline_range(range);
15718
15719 if !split
15720 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15721 {
15722 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15723 } else {
15724 window.defer(cx, move |window, cx| {
15725 let target_editor: Entity<Self> =
15726 workspace.update(cx, |workspace, cx| {
15727 let pane = if split {
15728 workspace.adjacent_pane(window, cx)
15729 } else {
15730 workspace.active_pane().clone()
15731 };
15732
15733 workspace.open_project_item(
15734 pane,
15735 target.buffer.clone(),
15736 true,
15737 true,
15738 window,
15739 cx,
15740 )
15741 });
15742 target_editor.update(cx, |target_editor, cx| {
15743 // When selecting a definition in a different buffer, disable the nav history
15744 // to avoid creating a history entry at the previous cursor location.
15745 pane.update(cx, |pane, _| pane.disable_history());
15746 target_editor.go_to_singleton_buffer_range(range, window, cx);
15747 pane.update(cx, |pane, _| pane.enable_history());
15748 });
15749 });
15750 }
15751 Navigated::Yes
15752 })
15753 })
15754 } else if !definitions.is_empty() {
15755 cx.spawn_in(window, async move |editor, cx| {
15756 let (title, location_tasks, workspace) = editor
15757 .update_in(cx, |editor, window, cx| {
15758 let tab_kind = match kind {
15759 Some(GotoDefinitionKind::Implementation) => "Implementations",
15760 _ => "Definitions",
15761 };
15762 let title = definitions
15763 .iter()
15764 .find_map(|definition| match definition {
15765 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15766 let buffer = origin.buffer.read(cx);
15767 format!(
15768 "{} for {}",
15769 tab_kind,
15770 buffer
15771 .text_for_range(origin.range.clone())
15772 .collect::<String>()
15773 )
15774 }),
15775 HoverLink::InlayHint(_, _) => None,
15776 HoverLink::Url(_) => None,
15777 HoverLink::File(_) => None,
15778 })
15779 .unwrap_or(tab_kind.to_string());
15780 let location_tasks = definitions
15781 .into_iter()
15782 .map(|definition| match definition {
15783 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15784 HoverLink::InlayHint(lsp_location, server_id) => editor
15785 .compute_target_location(lsp_location, server_id, window, cx),
15786 HoverLink::Url(_) => Task::ready(Ok(None)),
15787 HoverLink::File(_) => Task::ready(Ok(None)),
15788 })
15789 .collect::<Vec<_>>();
15790 (title, location_tasks, editor.workspace().clone())
15791 })
15792 .context("location tasks preparation")?;
15793
15794 let locations: Vec<Location> = future::join_all(location_tasks)
15795 .await
15796 .into_iter()
15797 .filter_map(|location| location.transpose())
15798 .collect::<Result<_>>()
15799 .context("location tasks")?;
15800
15801 if locations.is_empty() {
15802 return Ok(Navigated::No);
15803 }
15804
15805 let Some(workspace) = workspace else {
15806 return Ok(Navigated::No);
15807 };
15808
15809 let opened = workspace
15810 .update_in(cx, |workspace, window, cx| {
15811 Self::open_locations_in_multibuffer(
15812 workspace,
15813 locations,
15814 title,
15815 split,
15816 MultibufferSelectionMode::First,
15817 window,
15818 cx,
15819 )
15820 })
15821 .ok();
15822
15823 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15824 })
15825 } else {
15826 Task::ready(Ok(Navigated::No))
15827 }
15828 }
15829
15830 fn compute_target_location(
15831 &self,
15832 lsp_location: lsp::Location,
15833 server_id: LanguageServerId,
15834 window: &mut Window,
15835 cx: &mut Context<Self>,
15836 ) -> Task<anyhow::Result<Option<Location>>> {
15837 let Some(project) = self.project.clone() else {
15838 return Task::ready(Ok(None));
15839 };
15840
15841 cx.spawn_in(window, async move |editor, cx| {
15842 let location_task = editor.update(cx, |_, cx| {
15843 project.update(cx, |project, cx| {
15844 let language_server_name = project
15845 .language_server_statuses(cx)
15846 .find(|(id, _)| server_id == *id)
15847 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15848 language_server_name.map(|language_server_name| {
15849 project.open_local_buffer_via_lsp(
15850 lsp_location.uri.clone(),
15851 server_id,
15852 language_server_name,
15853 cx,
15854 )
15855 })
15856 })
15857 })?;
15858 let location = match location_task {
15859 Some(task) => Some({
15860 let target_buffer_handle = task.await.context("open local buffer")?;
15861 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15862 let target_start = target_buffer
15863 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15864 let target_end = target_buffer
15865 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15866 target_buffer.anchor_after(target_start)
15867 ..target_buffer.anchor_before(target_end)
15868 })?;
15869 Location {
15870 buffer: target_buffer_handle,
15871 range,
15872 }
15873 }),
15874 None => None,
15875 };
15876 Ok(location)
15877 })
15878 }
15879
15880 pub fn find_all_references(
15881 &mut self,
15882 _: &FindAllReferences,
15883 window: &mut Window,
15884 cx: &mut Context<Self>,
15885 ) -> Option<Task<Result<Navigated>>> {
15886 let selection = self.selections.newest::<usize>(cx);
15887 let multi_buffer = self.buffer.read(cx);
15888 let head = selection.head();
15889
15890 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15891 let head_anchor = multi_buffer_snapshot.anchor_at(
15892 head,
15893 if head < selection.tail() {
15894 Bias::Right
15895 } else {
15896 Bias::Left
15897 },
15898 );
15899
15900 match self
15901 .find_all_references_task_sources
15902 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15903 {
15904 Ok(_) => {
15905 log::info!(
15906 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15907 );
15908 return None;
15909 }
15910 Err(i) => {
15911 self.find_all_references_task_sources.insert(i, head_anchor);
15912 }
15913 }
15914
15915 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15916 let workspace = self.workspace()?;
15917 let project = workspace.read(cx).project().clone();
15918 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15919 Some(cx.spawn_in(window, async move |editor, cx| {
15920 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15921 if let Ok(i) = editor
15922 .find_all_references_task_sources
15923 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15924 {
15925 editor.find_all_references_task_sources.remove(i);
15926 }
15927 });
15928
15929 let locations = references.await?;
15930 if locations.is_empty() {
15931 return anyhow::Ok(Navigated::No);
15932 }
15933
15934 workspace.update_in(cx, |workspace, window, cx| {
15935 let title = locations
15936 .first()
15937 .as_ref()
15938 .map(|location| {
15939 let buffer = location.buffer.read(cx);
15940 format!(
15941 "References to `{}`",
15942 buffer
15943 .text_for_range(location.range.clone())
15944 .collect::<String>()
15945 )
15946 })
15947 .unwrap();
15948 Self::open_locations_in_multibuffer(
15949 workspace,
15950 locations,
15951 title,
15952 false,
15953 MultibufferSelectionMode::First,
15954 window,
15955 cx,
15956 );
15957 Navigated::Yes
15958 })
15959 }))
15960 }
15961
15962 /// Opens a multibuffer with the given project locations in it
15963 pub fn open_locations_in_multibuffer(
15964 workspace: &mut Workspace,
15965 mut locations: Vec<Location>,
15966 title: String,
15967 split: bool,
15968 multibuffer_selection_mode: MultibufferSelectionMode,
15969 window: &mut Window,
15970 cx: &mut Context<Workspace>,
15971 ) {
15972 if locations.is_empty() {
15973 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15974 return;
15975 }
15976
15977 // If there are multiple definitions, open them in a multibuffer
15978 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15979 let mut locations = locations.into_iter().peekable();
15980 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15981 let capability = workspace.project().read(cx).capability();
15982
15983 let excerpt_buffer = cx.new(|cx| {
15984 let mut multibuffer = MultiBuffer::new(capability);
15985 while let Some(location) = locations.next() {
15986 let buffer = location.buffer.read(cx);
15987 let mut ranges_for_buffer = Vec::new();
15988 let range = location.range.to_point(buffer);
15989 ranges_for_buffer.push(range.clone());
15990
15991 while let Some(next_location) = locations.peek() {
15992 if next_location.buffer == location.buffer {
15993 ranges_for_buffer.push(next_location.range.to_point(buffer));
15994 locations.next();
15995 } else {
15996 break;
15997 }
15998 }
15999
16000 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16001 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16002 PathKey::for_buffer(&location.buffer, cx),
16003 location.buffer.clone(),
16004 ranges_for_buffer,
16005 DEFAULT_MULTIBUFFER_CONTEXT,
16006 cx,
16007 );
16008 ranges.extend(new_ranges)
16009 }
16010
16011 multibuffer.with_title(title)
16012 });
16013
16014 let editor = cx.new(|cx| {
16015 Editor::for_multibuffer(
16016 excerpt_buffer,
16017 Some(workspace.project().clone()),
16018 window,
16019 cx,
16020 )
16021 });
16022 editor.update(cx, |editor, cx| {
16023 match multibuffer_selection_mode {
16024 MultibufferSelectionMode::First => {
16025 if let Some(first_range) = ranges.first() {
16026 editor.change_selections(
16027 SelectionEffects::no_scroll(),
16028 window,
16029 cx,
16030 |selections| {
16031 selections.clear_disjoint();
16032 selections
16033 .select_anchor_ranges(std::iter::once(first_range.clone()));
16034 },
16035 );
16036 }
16037 editor.highlight_background::<Self>(
16038 &ranges,
16039 |theme| theme.colors().editor_highlighted_line_background,
16040 cx,
16041 );
16042 }
16043 MultibufferSelectionMode::All => {
16044 editor.change_selections(
16045 SelectionEffects::no_scroll(),
16046 window,
16047 cx,
16048 |selections| {
16049 selections.clear_disjoint();
16050 selections.select_anchor_ranges(ranges);
16051 },
16052 );
16053 }
16054 }
16055 editor.register_buffers_with_language_servers(cx);
16056 });
16057
16058 let item = Box::new(editor);
16059 let item_id = item.item_id();
16060
16061 if split {
16062 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16063 } else {
16064 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16065 let (preview_item_id, preview_item_idx) =
16066 workspace.active_pane().read_with(cx, |pane, _| {
16067 (pane.preview_item_id(), pane.preview_item_idx())
16068 });
16069
16070 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16071
16072 if let Some(preview_item_id) = preview_item_id {
16073 workspace.active_pane().update(cx, |pane, cx| {
16074 pane.remove_item(preview_item_id, false, false, window, cx);
16075 });
16076 }
16077 } else {
16078 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16079 }
16080 }
16081 workspace.active_pane().update(cx, |pane, cx| {
16082 pane.set_preview_item_id(Some(item_id), cx);
16083 });
16084 }
16085
16086 pub fn rename(
16087 &mut self,
16088 _: &Rename,
16089 window: &mut Window,
16090 cx: &mut Context<Self>,
16091 ) -> Option<Task<Result<()>>> {
16092 use language::ToOffset as _;
16093
16094 let provider = self.semantics_provider.clone()?;
16095 let selection = self.selections.newest_anchor().clone();
16096 let (cursor_buffer, cursor_buffer_position) = self
16097 .buffer
16098 .read(cx)
16099 .text_anchor_for_position(selection.head(), cx)?;
16100 let (tail_buffer, cursor_buffer_position_end) = self
16101 .buffer
16102 .read(cx)
16103 .text_anchor_for_position(selection.tail(), cx)?;
16104 if tail_buffer != cursor_buffer {
16105 return None;
16106 }
16107
16108 let snapshot = cursor_buffer.read(cx).snapshot();
16109 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16110 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16111 let prepare_rename = provider
16112 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16113 .unwrap_or_else(|| Task::ready(Ok(None)));
16114 drop(snapshot);
16115
16116 Some(cx.spawn_in(window, async move |this, cx| {
16117 let rename_range = if let Some(range) = prepare_rename.await? {
16118 Some(range)
16119 } else {
16120 this.update(cx, |this, cx| {
16121 let buffer = this.buffer.read(cx).snapshot(cx);
16122 let mut buffer_highlights = this
16123 .document_highlights_for_position(selection.head(), &buffer)
16124 .filter(|highlight| {
16125 highlight.start.excerpt_id == selection.head().excerpt_id
16126 && highlight.end.excerpt_id == selection.head().excerpt_id
16127 });
16128 buffer_highlights
16129 .next()
16130 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16131 })?
16132 };
16133 if let Some(rename_range) = rename_range {
16134 this.update_in(cx, |this, window, cx| {
16135 let snapshot = cursor_buffer.read(cx).snapshot();
16136 let rename_buffer_range = rename_range.to_offset(&snapshot);
16137 let cursor_offset_in_rename_range =
16138 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16139 let cursor_offset_in_rename_range_end =
16140 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16141
16142 this.take_rename(false, window, cx);
16143 let buffer = this.buffer.read(cx).read(cx);
16144 let cursor_offset = selection.head().to_offset(&buffer);
16145 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16146 let rename_end = rename_start + rename_buffer_range.len();
16147 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16148 let mut old_highlight_id = None;
16149 let old_name: Arc<str> = buffer
16150 .chunks(rename_start..rename_end, true)
16151 .map(|chunk| {
16152 if old_highlight_id.is_none() {
16153 old_highlight_id = chunk.syntax_highlight_id;
16154 }
16155 chunk.text
16156 })
16157 .collect::<String>()
16158 .into();
16159
16160 drop(buffer);
16161
16162 // Position the selection in the rename editor so that it matches the current selection.
16163 this.show_local_selections = false;
16164 let rename_editor = cx.new(|cx| {
16165 let mut editor = Editor::single_line(window, cx);
16166 editor.buffer.update(cx, |buffer, cx| {
16167 buffer.edit([(0..0, old_name.clone())], None, cx)
16168 });
16169 let rename_selection_range = match cursor_offset_in_rename_range
16170 .cmp(&cursor_offset_in_rename_range_end)
16171 {
16172 Ordering::Equal => {
16173 editor.select_all(&SelectAll, window, cx);
16174 return editor;
16175 }
16176 Ordering::Less => {
16177 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16178 }
16179 Ordering::Greater => {
16180 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16181 }
16182 };
16183 if rename_selection_range.end > old_name.len() {
16184 editor.select_all(&SelectAll, window, cx);
16185 } else {
16186 editor.change_selections(Default::default(), window, cx, |s| {
16187 s.select_ranges([rename_selection_range]);
16188 });
16189 }
16190 editor
16191 });
16192 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16193 if e == &EditorEvent::Focused {
16194 cx.emit(EditorEvent::FocusedIn)
16195 }
16196 })
16197 .detach();
16198
16199 let write_highlights =
16200 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16201 let read_highlights =
16202 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16203 let ranges = write_highlights
16204 .iter()
16205 .flat_map(|(_, ranges)| ranges.iter())
16206 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16207 .cloned()
16208 .collect();
16209
16210 this.highlight_text::<Rename>(
16211 ranges,
16212 HighlightStyle {
16213 fade_out: Some(0.6),
16214 ..Default::default()
16215 },
16216 cx,
16217 );
16218 let rename_focus_handle = rename_editor.focus_handle(cx);
16219 window.focus(&rename_focus_handle);
16220 let block_id = this.insert_blocks(
16221 [BlockProperties {
16222 style: BlockStyle::Flex,
16223 placement: BlockPlacement::Below(range.start),
16224 height: Some(1),
16225 render: Arc::new({
16226 let rename_editor = rename_editor.clone();
16227 move |cx: &mut BlockContext| {
16228 let mut text_style = cx.editor_style.text.clone();
16229 if let Some(highlight_style) = old_highlight_id
16230 .and_then(|h| h.style(&cx.editor_style.syntax))
16231 {
16232 text_style = text_style.highlight(highlight_style);
16233 }
16234 div()
16235 .block_mouse_except_scroll()
16236 .pl(cx.anchor_x)
16237 .child(EditorElement::new(
16238 &rename_editor,
16239 EditorStyle {
16240 background: cx.theme().system().transparent,
16241 local_player: cx.editor_style.local_player,
16242 text: text_style,
16243 scrollbar_width: cx.editor_style.scrollbar_width,
16244 syntax: cx.editor_style.syntax.clone(),
16245 status: cx.editor_style.status.clone(),
16246 inlay_hints_style: HighlightStyle {
16247 font_weight: Some(FontWeight::BOLD),
16248 ..make_inlay_hints_style(cx.app)
16249 },
16250 inline_completion_styles: make_suggestion_styles(
16251 cx.app,
16252 ),
16253 ..EditorStyle::default()
16254 },
16255 ))
16256 .into_any_element()
16257 }
16258 }),
16259 priority: 0,
16260 }],
16261 Some(Autoscroll::fit()),
16262 cx,
16263 )[0];
16264 this.pending_rename = Some(RenameState {
16265 range,
16266 old_name,
16267 editor: rename_editor,
16268 block_id,
16269 });
16270 })?;
16271 }
16272
16273 Ok(())
16274 }))
16275 }
16276
16277 pub fn confirm_rename(
16278 &mut self,
16279 _: &ConfirmRename,
16280 window: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) -> Option<Task<Result<()>>> {
16283 let rename = self.take_rename(false, window, cx)?;
16284 let workspace = self.workspace()?.downgrade();
16285 let (buffer, start) = self
16286 .buffer
16287 .read(cx)
16288 .text_anchor_for_position(rename.range.start, cx)?;
16289 let (end_buffer, _) = self
16290 .buffer
16291 .read(cx)
16292 .text_anchor_for_position(rename.range.end, cx)?;
16293 if buffer != end_buffer {
16294 return None;
16295 }
16296
16297 let old_name = rename.old_name;
16298 let new_name = rename.editor.read(cx).text(cx);
16299
16300 let rename = self.semantics_provider.as_ref()?.perform_rename(
16301 &buffer,
16302 start,
16303 new_name.clone(),
16304 cx,
16305 )?;
16306
16307 Some(cx.spawn_in(window, async move |editor, cx| {
16308 let project_transaction = rename.await?;
16309 Self::open_project_transaction(
16310 &editor,
16311 workspace,
16312 project_transaction,
16313 format!("Rename: {} → {}", old_name, new_name),
16314 cx,
16315 )
16316 .await?;
16317
16318 editor.update(cx, |editor, cx| {
16319 editor.refresh_document_highlights(cx);
16320 })?;
16321 Ok(())
16322 }))
16323 }
16324
16325 fn take_rename(
16326 &mut self,
16327 moving_cursor: bool,
16328 window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) -> Option<RenameState> {
16331 let rename = self.pending_rename.take()?;
16332 if rename.editor.focus_handle(cx).is_focused(window) {
16333 window.focus(&self.focus_handle);
16334 }
16335
16336 self.remove_blocks(
16337 [rename.block_id].into_iter().collect(),
16338 Some(Autoscroll::fit()),
16339 cx,
16340 );
16341 self.clear_highlights::<Rename>(cx);
16342 self.show_local_selections = true;
16343
16344 if moving_cursor {
16345 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16346 editor.selections.newest::<usize>(cx).head()
16347 });
16348
16349 // Update the selection to match the position of the selection inside
16350 // the rename editor.
16351 let snapshot = self.buffer.read(cx).read(cx);
16352 let rename_range = rename.range.to_offset(&snapshot);
16353 let cursor_in_editor = snapshot
16354 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16355 .min(rename_range.end);
16356 drop(snapshot);
16357
16358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16359 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16360 });
16361 } else {
16362 self.refresh_document_highlights(cx);
16363 }
16364
16365 Some(rename)
16366 }
16367
16368 pub fn pending_rename(&self) -> Option<&RenameState> {
16369 self.pending_rename.as_ref()
16370 }
16371
16372 fn format(
16373 &mut self,
16374 _: &Format,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) -> Option<Task<Result<()>>> {
16378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16379
16380 let project = match &self.project {
16381 Some(project) => project.clone(),
16382 None => return None,
16383 };
16384
16385 Some(self.perform_format(
16386 project,
16387 FormatTrigger::Manual,
16388 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16389 window,
16390 cx,
16391 ))
16392 }
16393
16394 fn format_selections(
16395 &mut self,
16396 _: &FormatSelections,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) -> Option<Task<Result<()>>> {
16400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16401
16402 let project = match &self.project {
16403 Some(project) => project.clone(),
16404 None => return None,
16405 };
16406
16407 let ranges = self
16408 .selections
16409 .all_adjusted(cx)
16410 .into_iter()
16411 .map(|selection| selection.range())
16412 .collect_vec();
16413
16414 Some(self.perform_format(
16415 project,
16416 FormatTrigger::Manual,
16417 FormatTarget::Ranges(ranges),
16418 window,
16419 cx,
16420 ))
16421 }
16422
16423 fn perform_format(
16424 &mut self,
16425 project: Entity<Project>,
16426 trigger: FormatTrigger,
16427 target: FormatTarget,
16428 window: &mut Window,
16429 cx: &mut Context<Self>,
16430 ) -> Task<Result<()>> {
16431 let buffer = self.buffer.clone();
16432 let (buffers, target) = match target {
16433 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16434 FormatTarget::Ranges(selection_ranges) => {
16435 let multi_buffer = buffer.read(cx);
16436 let snapshot = multi_buffer.read(cx);
16437 let mut buffers = HashSet::default();
16438 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16439 BTreeMap::new();
16440 for selection_range in selection_ranges {
16441 for (buffer, buffer_range, _) in
16442 snapshot.range_to_buffer_ranges(selection_range)
16443 {
16444 let buffer_id = buffer.remote_id();
16445 let start = buffer.anchor_before(buffer_range.start);
16446 let end = buffer.anchor_after(buffer_range.end);
16447 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16448 buffer_id_to_ranges
16449 .entry(buffer_id)
16450 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16451 .or_insert_with(|| vec![start..end]);
16452 }
16453 }
16454 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16455 }
16456 };
16457
16458 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16459 let selections_prev = transaction_id_prev
16460 .and_then(|transaction_id_prev| {
16461 // default to selections as they were after the last edit, if we have them,
16462 // instead of how they are now.
16463 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16464 // will take you back to where you made the last edit, instead of staying where you scrolled
16465 self.selection_history
16466 .transaction(transaction_id_prev)
16467 .map(|t| t.0.clone())
16468 })
16469 .unwrap_or_else(|| {
16470 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16471 self.selections.disjoint_anchors()
16472 });
16473
16474 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16475 let format = project.update(cx, |project, cx| {
16476 project.format(buffers, target, true, trigger, cx)
16477 });
16478
16479 cx.spawn_in(window, async move |editor, cx| {
16480 let transaction = futures::select_biased! {
16481 transaction = format.log_err().fuse() => transaction,
16482 () = timeout => {
16483 log::warn!("timed out waiting for formatting");
16484 None
16485 }
16486 };
16487
16488 buffer
16489 .update(cx, |buffer, cx| {
16490 if let Some(transaction) = transaction {
16491 if !buffer.is_singleton() {
16492 buffer.push_transaction(&transaction.0, cx);
16493 }
16494 }
16495 cx.notify();
16496 })
16497 .ok();
16498
16499 if let Some(transaction_id_now) =
16500 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16501 {
16502 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16503 if has_new_transaction {
16504 _ = editor.update(cx, |editor, _| {
16505 editor
16506 .selection_history
16507 .insert_transaction(transaction_id_now, selections_prev);
16508 });
16509 }
16510 }
16511
16512 Ok(())
16513 })
16514 }
16515
16516 fn organize_imports(
16517 &mut self,
16518 _: &OrganizeImports,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) -> Option<Task<Result<()>>> {
16522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16523 let project = match &self.project {
16524 Some(project) => project.clone(),
16525 None => return None,
16526 };
16527 Some(self.perform_code_action_kind(
16528 project,
16529 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16530 window,
16531 cx,
16532 ))
16533 }
16534
16535 fn perform_code_action_kind(
16536 &mut self,
16537 project: Entity<Project>,
16538 kind: CodeActionKind,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) -> Task<Result<()>> {
16542 let buffer = self.buffer.clone();
16543 let buffers = buffer.read(cx).all_buffers();
16544 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16545 let apply_action = project.update(cx, |project, cx| {
16546 project.apply_code_action_kind(buffers, kind, true, cx)
16547 });
16548 cx.spawn_in(window, async move |_, cx| {
16549 let transaction = futures::select_biased! {
16550 () = timeout => {
16551 log::warn!("timed out waiting for executing code action");
16552 None
16553 }
16554 transaction = apply_action.log_err().fuse() => transaction,
16555 };
16556 buffer
16557 .update(cx, |buffer, cx| {
16558 // check if we need this
16559 if let Some(transaction) = transaction {
16560 if !buffer.is_singleton() {
16561 buffer.push_transaction(&transaction.0, cx);
16562 }
16563 }
16564 cx.notify();
16565 })
16566 .ok();
16567 Ok(())
16568 })
16569 }
16570
16571 pub fn restart_language_server(
16572 &mut self,
16573 _: &RestartLanguageServer,
16574 _: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 if let Some(project) = self.project.clone() {
16578 self.buffer.update(cx, |multi_buffer, cx| {
16579 project.update(cx, |project, cx| {
16580 project.restart_language_servers_for_buffers(
16581 multi_buffer.all_buffers().into_iter().collect(),
16582 HashSet::default(),
16583 cx,
16584 );
16585 });
16586 })
16587 }
16588 }
16589
16590 pub fn stop_language_server(
16591 &mut self,
16592 _: &StopLanguageServer,
16593 _: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) {
16596 if let Some(project) = self.project.clone() {
16597 self.buffer.update(cx, |multi_buffer, cx| {
16598 project.update(cx, |project, cx| {
16599 project.stop_language_servers_for_buffers(
16600 multi_buffer.all_buffers().into_iter().collect(),
16601 HashSet::default(),
16602 cx,
16603 );
16604 cx.emit(project::Event::RefreshInlayHints);
16605 });
16606 });
16607 }
16608 }
16609
16610 fn cancel_language_server_work(
16611 workspace: &mut Workspace,
16612 _: &actions::CancelLanguageServerWork,
16613 _: &mut Window,
16614 cx: &mut Context<Workspace>,
16615 ) {
16616 let project = workspace.project();
16617 let buffers = workspace
16618 .active_item(cx)
16619 .and_then(|item| item.act_as::<Editor>(cx))
16620 .map_or(HashSet::default(), |editor| {
16621 editor.read(cx).buffer.read(cx).all_buffers()
16622 });
16623 project.update(cx, |project, cx| {
16624 project.cancel_language_server_work_for_buffers(buffers, cx);
16625 });
16626 }
16627
16628 fn show_character_palette(
16629 &mut self,
16630 _: &ShowCharacterPalette,
16631 window: &mut Window,
16632 _: &mut Context<Self>,
16633 ) {
16634 window.show_character_palette();
16635 }
16636
16637 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16638 if !self.diagnostics_enabled() {
16639 return;
16640 }
16641
16642 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16643 let buffer = self.buffer.read(cx).snapshot(cx);
16644 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16645 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16646 let is_valid = buffer
16647 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16648 .any(|entry| {
16649 entry.diagnostic.is_primary
16650 && !entry.range.is_empty()
16651 && entry.range.start == primary_range_start
16652 && entry.diagnostic.message == active_diagnostics.active_message
16653 });
16654
16655 if !is_valid {
16656 self.dismiss_diagnostics(cx);
16657 }
16658 }
16659 }
16660
16661 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16662 match &self.active_diagnostics {
16663 ActiveDiagnostic::Group(group) => Some(group),
16664 _ => None,
16665 }
16666 }
16667
16668 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16669 if !self.diagnostics_enabled() {
16670 return;
16671 }
16672 self.dismiss_diagnostics(cx);
16673 self.active_diagnostics = ActiveDiagnostic::All;
16674 }
16675
16676 fn activate_diagnostics(
16677 &mut self,
16678 buffer_id: BufferId,
16679 diagnostic: DiagnosticEntry<usize>,
16680 window: &mut Window,
16681 cx: &mut Context<Self>,
16682 ) {
16683 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16684 return;
16685 }
16686 self.dismiss_diagnostics(cx);
16687 let snapshot = self.snapshot(window, cx);
16688 let buffer = self.buffer.read(cx).snapshot(cx);
16689 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16690 return;
16691 };
16692
16693 let diagnostic_group = buffer
16694 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16695 .collect::<Vec<_>>();
16696
16697 let blocks =
16698 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16699
16700 let blocks = self.display_map.update(cx, |display_map, cx| {
16701 display_map.insert_blocks(blocks, cx).into_iter().collect()
16702 });
16703 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16704 active_range: buffer.anchor_before(diagnostic.range.start)
16705 ..buffer.anchor_after(diagnostic.range.end),
16706 active_message: diagnostic.diagnostic.message.clone(),
16707 group_id: diagnostic.diagnostic.group_id,
16708 blocks,
16709 });
16710 cx.notify();
16711 }
16712
16713 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16714 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16715 return;
16716 };
16717
16718 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16719 if let ActiveDiagnostic::Group(group) = prev {
16720 self.display_map.update(cx, |display_map, cx| {
16721 display_map.remove_blocks(group.blocks, cx);
16722 });
16723 cx.notify();
16724 }
16725 }
16726
16727 /// Disable inline diagnostics rendering for this editor.
16728 pub fn disable_inline_diagnostics(&mut self) {
16729 self.inline_diagnostics_enabled = false;
16730 self.inline_diagnostics_update = Task::ready(());
16731 self.inline_diagnostics.clear();
16732 }
16733
16734 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16735 self.diagnostics_enabled = false;
16736 self.dismiss_diagnostics(cx);
16737 self.inline_diagnostics_update = Task::ready(());
16738 self.inline_diagnostics.clear();
16739 }
16740
16741 pub fn diagnostics_enabled(&self) -> bool {
16742 self.diagnostics_enabled && self.mode.is_full()
16743 }
16744
16745 pub fn inline_diagnostics_enabled(&self) -> bool {
16746 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16747 }
16748
16749 pub fn show_inline_diagnostics(&self) -> bool {
16750 self.show_inline_diagnostics
16751 }
16752
16753 pub fn toggle_inline_diagnostics(
16754 &mut self,
16755 _: &ToggleInlineDiagnostics,
16756 window: &mut Window,
16757 cx: &mut Context<Editor>,
16758 ) {
16759 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16760 self.refresh_inline_diagnostics(false, window, cx);
16761 }
16762
16763 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16764 self.diagnostics_max_severity = severity;
16765 self.display_map.update(cx, |display_map, _| {
16766 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16767 });
16768 }
16769
16770 pub fn toggle_diagnostics(
16771 &mut self,
16772 _: &ToggleDiagnostics,
16773 window: &mut Window,
16774 cx: &mut Context<Editor>,
16775 ) {
16776 if !self.diagnostics_enabled() {
16777 return;
16778 }
16779
16780 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16781 EditorSettings::get_global(cx)
16782 .diagnostics_max_severity
16783 .filter(|severity| severity != &DiagnosticSeverity::Off)
16784 .unwrap_or(DiagnosticSeverity::Hint)
16785 } else {
16786 DiagnosticSeverity::Off
16787 };
16788 self.set_max_diagnostics_severity(new_severity, cx);
16789 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16790 self.active_diagnostics = ActiveDiagnostic::None;
16791 self.inline_diagnostics_update = Task::ready(());
16792 self.inline_diagnostics.clear();
16793 } else {
16794 self.refresh_inline_diagnostics(false, window, cx);
16795 }
16796
16797 cx.notify();
16798 }
16799
16800 pub fn toggle_minimap(
16801 &mut self,
16802 _: &ToggleMinimap,
16803 window: &mut Window,
16804 cx: &mut Context<Editor>,
16805 ) {
16806 if self.supports_minimap(cx) {
16807 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16808 }
16809 }
16810
16811 fn refresh_inline_diagnostics(
16812 &mut self,
16813 debounce: bool,
16814 window: &mut Window,
16815 cx: &mut Context<Self>,
16816 ) {
16817 let max_severity = ProjectSettings::get_global(cx)
16818 .diagnostics
16819 .inline
16820 .max_severity
16821 .unwrap_or(self.diagnostics_max_severity);
16822
16823 if !self.inline_diagnostics_enabled()
16824 || !self.show_inline_diagnostics
16825 || max_severity == DiagnosticSeverity::Off
16826 {
16827 self.inline_diagnostics_update = Task::ready(());
16828 self.inline_diagnostics.clear();
16829 return;
16830 }
16831
16832 let debounce_ms = ProjectSettings::get_global(cx)
16833 .diagnostics
16834 .inline
16835 .update_debounce_ms;
16836 let debounce = if debounce && debounce_ms > 0 {
16837 Some(Duration::from_millis(debounce_ms))
16838 } else {
16839 None
16840 };
16841 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16842 if let Some(debounce) = debounce {
16843 cx.background_executor().timer(debounce).await;
16844 }
16845 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16846 editor
16847 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16848 .ok()
16849 }) else {
16850 return;
16851 };
16852
16853 let new_inline_diagnostics = cx
16854 .background_spawn(async move {
16855 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16856 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16857 let message = diagnostic_entry
16858 .diagnostic
16859 .message
16860 .split_once('\n')
16861 .map(|(line, _)| line)
16862 .map(SharedString::new)
16863 .unwrap_or_else(|| {
16864 SharedString::from(diagnostic_entry.diagnostic.message)
16865 });
16866 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16867 let (Ok(i) | Err(i)) = inline_diagnostics
16868 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16869 inline_diagnostics.insert(
16870 i,
16871 (
16872 start_anchor,
16873 InlineDiagnostic {
16874 message,
16875 group_id: diagnostic_entry.diagnostic.group_id,
16876 start: diagnostic_entry.range.start.to_point(&snapshot),
16877 is_primary: diagnostic_entry.diagnostic.is_primary,
16878 severity: diagnostic_entry.diagnostic.severity,
16879 },
16880 ),
16881 );
16882 }
16883 inline_diagnostics
16884 })
16885 .await;
16886
16887 editor
16888 .update(cx, |editor, cx| {
16889 editor.inline_diagnostics = new_inline_diagnostics;
16890 cx.notify();
16891 })
16892 .ok();
16893 });
16894 }
16895
16896 fn pull_diagnostics(
16897 &mut self,
16898 buffer_id: Option<BufferId>,
16899 window: &Window,
16900 cx: &mut Context<Self>,
16901 ) -> Option<()> {
16902 if !self.mode().is_full() {
16903 return None;
16904 }
16905 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16906 .diagnostics
16907 .lsp_pull_diagnostics;
16908 if !pull_diagnostics_settings.enabled {
16909 return None;
16910 }
16911 let project = self.project.as_ref()?.downgrade();
16912 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16913 let mut buffers = self.buffer.read(cx).all_buffers();
16914 if let Some(buffer_id) = buffer_id {
16915 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16916 }
16917
16918 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16919 cx.background_executor().timer(debounce).await;
16920
16921 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16922 buffers
16923 .into_iter()
16924 .filter_map(|buffer| {
16925 project
16926 .update(cx, |project, cx| {
16927 project.lsp_store().update(cx, |lsp_store, cx| {
16928 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16929 })
16930 })
16931 .ok()
16932 })
16933 .collect::<FuturesUnordered<_>>()
16934 }) else {
16935 return;
16936 };
16937
16938 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16939 match pull_task {
16940 Ok(()) => {
16941 if editor
16942 .update_in(cx, |editor, window, cx| {
16943 editor.update_diagnostics_state(window, cx);
16944 })
16945 .is_err()
16946 {
16947 return;
16948 }
16949 }
16950 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16951 }
16952 }
16953 });
16954
16955 Some(())
16956 }
16957
16958 pub fn set_selections_from_remote(
16959 &mut self,
16960 selections: Vec<Selection<Anchor>>,
16961 pending_selection: Option<Selection<Anchor>>,
16962 window: &mut Window,
16963 cx: &mut Context<Self>,
16964 ) {
16965 let old_cursor_position = self.selections.newest_anchor().head();
16966 self.selections.change_with(cx, |s| {
16967 s.select_anchors(selections);
16968 if let Some(pending_selection) = pending_selection {
16969 s.set_pending(pending_selection, SelectMode::Character);
16970 } else {
16971 s.clear_pending();
16972 }
16973 });
16974 self.selections_did_change(
16975 false,
16976 &old_cursor_position,
16977 SelectionEffects::default(),
16978 window,
16979 cx,
16980 );
16981 }
16982
16983 pub fn transact(
16984 &mut self,
16985 window: &mut Window,
16986 cx: &mut Context<Self>,
16987 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16988 ) -> Option<TransactionId> {
16989 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16990 this.start_transaction_at(Instant::now(), window, cx);
16991 update(this, window, cx);
16992 this.end_transaction_at(Instant::now(), cx)
16993 })
16994 }
16995
16996 pub fn start_transaction_at(
16997 &mut self,
16998 now: Instant,
16999 window: &mut Window,
17000 cx: &mut Context<Self>,
17001 ) -> Option<TransactionId> {
17002 self.end_selection(window, cx);
17003 if let Some(tx_id) = self
17004 .buffer
17005 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17006 {
17007 self.selection_history
17008 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17009 cx.emit(EditorEvent::TransactionBegun {
17010 transaction_id: tx_id,
17011 });
17012 Some(tx_id)
17013 } else {
17014 None
17015 }
17016 }
17017
17018 pub fn end_transaction_at(
17019 &mut self,
17020 now: Instant,
17021 cx: &mut Context<Self>,
17022 ) -> Option<TransactionId> {
17023 if let Some(transaction_id) = self
17024 .buffer
17025 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17026 {
17027 if let Some((_, end_selections)) =
17028 self.selection_history.transaction_mut(transaction_id)
17029 {
17030 *end_selections = Some(self.selections.disjoint_anchors());
17031 } else {
17032 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17033 }
17034
17035 cx.emit(EditorEvent::Edited { transaction_id });
17036 Some(transaction_id)
17037 } else {
17038 None
17039 }
17040 }
17041
17042 pub fn modify_transaction_selection_history(
17043 &mut self,
17044 transaction_id: TransactionId,
17045 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17046 ) -> bool {
17047 self.selection_history
17048 .transaction_mut(transaction_id)
17049 .map(modify)
17050 .is_some()
17051 }
17052
17053 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17054 if self.selection_mark_mode {
17055 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17056 s.move_with(|_, sel| {
17057 sel.collapse_to(sel.head(), SelectionGoal::None);
17058 });
17059 })
17060 }
17061 self.selection_mark_mode = true;
17062 cx.notify();
17063 }
17064
17065 pub fn swap_selection_ends(
17066 &mut self,
17067 _: &actions::SwapSelectionEnds,
17068 window: &mut Window,
17069 cx: &mut Context<Self>,
17070 ) {
17071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17072 s.move_with(|_, sel| {
17073 if sel.start != sel.end {
17074 sel.reversed = !sel.reversed
17075 }
17076 });
17077 });
17078 self.request_autoscroll(Autoscroll::newest(), cx);
17079 cx.notify();
17080 }
17081
17082 pub fn toggle_focus(
17083 workspace: &mut Workspace,
17084 _: &actions::ToggleFocus,
17085 window: &mut Window,
17086 cx: &mut Context<Workspace>,
17087 ) {
17088 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17089 return;
17090 };
17091 workspace.activate_item(&item, true, true, window, cx);
17092 }
17093
17094 pub fn toggle_fold(
17095 &mut self,
17096 _: &actions::ToggleFold,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) {
17100 if self.is_singleton(cx) {
17101 let selection = self.selections.newest::<Point>(cx);
17102
17103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17104 let range = if selection.is_empty() {
17105 let point = selection.head().to_display_point(&display_map);
17106 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17107 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17108 .to_point(&display_map);
17109 start..end
17110 } else {
17111 selection.range()
17112 };
17113 if display_map.folds_in_range(range).next().is_some() {
17114 self.unfold_lines(&Default::default(), window, cx)
17115 } else {
17116 self.fold(&Default::default(), window, cx)
17117 }
17118 } else {
17119 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17120 let buffer_ids: HashSet<_> = self
17121 .selections
17122 .disjoint_anchor_ranges()
17123 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17124 .collect();
17125
17126 let should_unfold = buffer_ids
17127 .iter()
17128 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17129
17130 for buffer_id in buffer_ids {
17131 if should_unfold {
17132 self.unfold_buffer(buffer_id, cx);
17133 } else {
17134 self.fold_buffer(buffer_id, cx);
17135 }
17136 }
17137 }
17138 }
17139
17140 pub fn toggle_fold_recursive(
17141 &mut self,
17142 _: &actions::ToggleFoldRecursive,
17143 window: &mut Window,
17144 cx: &mut Context<Self>,
17145 ) {
17146 let selection = self.selections.newest::<Point>(cx);
17147
17148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17149 let range = if selection.is_empty() {
17150 let point = selection.head().to_display_point(&display_map);
17151 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17152 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17153 .to_point(&display_map);
17154 start..end
17155 } else {
17156 selection.range()
17157 };
17158 if display_map.folds_in_range(range).next().is_some() {
17159 self.unfold_recursive(&Default::default(), window, cx)
17160 } else {
17161 self.fold_recursive(&Default::default(), window, cx)
17162 }
17163 }
17164
17165 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17166 if self.is_singleton(cx) {
17167 let mut to_fold = Vec::new();
17168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17169 let selections = self.selections.all_adjusted(cx);
17170
17171 for selection in selections {
17172 let range = selection.range().sorted();
17173 let buffer_start_row = range.start.row;
17174
17175 if range.start.row != range.end.row {
17176 let mut found = false;
17177 let mut row = range.start.row;
17178 while row <= range.end.row {
17179 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17180 {
17181 found = true;
17182 row = crease.range().end.row + 1;
17183 to_fold.push(crease);
17184 } else {
17185 row += 1
17186 }
17187 }
17188 if found {
17189 continue;
17190 }
17191 }
17192
17193 for row in (0..=range.start.row).rev() {
17194 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17195 if crease.range().end.row >= buffer_start_row {
17196 to_fold.push(crease);
17197 if row <= range.start.row {
17198 break;
17199 }
17200 }
17201 }
17202 }
17203 }
17204
17205 self.fold_creases(to_fold, true, window, cx);
17206 } else {
17207 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17208 let buffer_ids = self
17209 .selections
17210 .disjoint_anchor_ranges()
17211 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17212 .collect::<HashSet<_>>();
17213 for buffer_id in buffer_ids {
17214 self.fold_buffer(buffer_id, cx);
17215 }
17216 }
17217 }
17218
17219 pub fn toggle_fold_all(
17220 &mut self,
17221 _: &actions::ToggleFoldAll,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 ) {
17225 if self.buffer.read(cx).is_singleton() {
17226 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17227 let has_folds = display_map
17228 .folds_in_range(0..display_map.buffer_snapshot.len())
17229 .next()
17230 .is_some();
17231
17232 if has_folds {
17233 self.unfold_all(&actions::UnfoldAll, window, cx);
17234 } else {
17235 self.fold_all(&actions::FoldAll, window, cx);
17236 }
17237 } else {
17238 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17239 let should_unfold = buffer_ids
17240 .iter()
17241 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17242
17243 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17244 editor
17245 .update_in(cx, |editor, _, cx| {
17246 for buffer_id in buffer_ids {
17247 if should_unfold {
17248 editor.unfold_buffer(buffer_id, cx);
17249 } else {
17250 editor.fold_buffer(buffer_id, cx);
17251 }
17252 }
17253 })
17254 .ok();
17255 });
17256 }
17257 }
17258
17259 fn fold_at_level(
17260 &mut self,
17261 fold_at: &FoldAtLevel,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 if !self.buffer.read(cx).is_singleton() {
17266 return;
17267 }
17268
17269 let fold_at_level = fold_at.0;
17270 let snapshot = self.buffer.read(cx).snapshot(cx);
17271 let mut to_fold = Vec::new();
17272 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17273
17274 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17275 while start_row < end_row {
17276 match self
17277 .snapshot(window, cx)
17278 .crease_for_buffer_row(MultiBufferRow(start_row))
17279 {
17280 Some(crease) => {
17281 let nested_start_row = crease.range().start.row + 1;
17282 let nested_end_row = crease.range().end.row;
17283
17284 if current_level < fold_at_level {
17285 stack.push((nested_start_row, nested_end_row, current_level + 1));
17286 } else if current_level == fold_at_level {
17287 to_fold.push(crease);
17288 }
17289
17290 start_row = nested_end_row + 1;
17291 }
17292 None => start_row += 1,
17293 }
17294 }
17295 }
17296
17297 self.fold_creases(to_fold, true, window, cx);
17298 }
17299
17300 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17301 if self.buffer.read(cx).is_singleton() {
17302 let mut fold_ranges = Vec::new();
17303 let snapshot = self.buffer.read(cx).snapshot(cx);
17304
17305 for row in 0..snapshot.max_row().0 {
17306 if let Some(foldable_range) = self
17307 .snapshot(window, cx)
17308 .crease_for_buffer_row(MultiBufferRow(row))
17309 {
17310 fold_ranges.push(foldable_range);
17311 }
17312 }
17313
17314 self.fold_creases(fold_ranges, true, window, cx);
17315 } else {
17316 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17317 editor
17318 .update_in(cx, |editor, _, cx| {
17319 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17320 editor.fold_buffer(buffer_id, cx);
17321 }
17322 })
17323 .ok();
17324 });
17325 }
17326 }
17327
17328 pub fn fold_function_bodies(
17329 &mut self,
17330 _: &actions::FoldFunctionBodies,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 let snapshot = self.buffer.read(cx).snapshot(cx);
17335
17336 let ranges = snapshot
17337 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17338 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17339 .collect::<Vec<_>>();
17340
17341 let creases = ranges
17342 .into_iter()
17343 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17344 .collect();
17345
17346 self.fold_creases(creases, true, window, cx);
17347 }
17348
17349 pub fn fold_recursive(
17350 &mut self,
17351 _: &actions::FoldRecursive,
17352 window: &mut Window,
17353 cx: &mut Context<Self>,
17354 ) {
17355 let mut to_fold = Vec::new();
17356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17357 let selections = self.selections.all_adjusted(cx);
17358
17359 for selection in selections {
17360 let range = selection.range().sorted();
17361 let buffer_start_row = range.start.row;
17362
17363 if range.start.row != range.end.row {
17364 let mut found = false;
17365 for row in range.start.row..=range.end.row {
17366 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17367 found = true;
17368 to_fold.push(crease);
17369 }
17370 }
17371 if found {
17372 continue;
17373 }
17374 }
17375
17376 for row in (0..=range.start.row).rev() {
17377 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17378 if crease.range().end.row >= buffer_start_row {
17379 to_fold.push(crease);
17380 } else {
17381 break;
17382 }
17383 }
17384 }
17385 }
17386
17387 self.fold_creases(to_fold, true, window, cx);
17388 }
17389
17390 pub fn fold_at(
17391 &mut self,
17392 buffer_row: MultiBufferRow,
17393 window: &mut Window,
17394 cx: &mut Context<Self>,
17395 ) {
17396 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17397
17398 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17399 let autoscroll = self
17400 .selections
17401 .all::<Point>(cx)
17402 .iter()
17403 .any(|selection| crease.range().overlaps(&selection.range()));
17404
17405 self.fold_creases(vec![crease], autoscroll, window, cx);
17406 }
17407 }
17408
17409 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17410 if self.is_singleton(cx) {
17411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17412 let buffer = &display_map.buffer_snapshot;
17413 let selections = self.selections.all::<Point>(cx);
17414 let ranges = selections
17415 .iter()
17416 .map(|s| {
17417 let range = s.display_range(&display_map).sorted();
17418 let mut start = range.start.to_point(&display_map);
17419 let mut end = range.end.to_point(&display_map);
17420 start.column = 0;
17421 end.column = buffer.line_len(MultiBufferRow(end.row));
17422 start..end
17423 })
17424 .collect::<Vec<_>>();
17425
17426 self.unfold_ranges(&ranges, true, true, cx);
17427 } else {
17428 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17429 let buffer_ids = self
17430 .selections
17431 .disjoint_anchor_ranges()
17432 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17433 .collect::<HashSet<_>>();
17434 for buffer_id in buffer_ids {
17435 self.unfold_buffer(buffer_id, cx);
17436 }
17437 }
17438 }
17439
17440 pub fn unfold_recursive(
17441 &mut self,
17442 _: &UnfoldRecursive,
17443 _window: &mut Window,
17444 cx: &mut Context<Self>,
17445 ) {
17446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17447 let selections = self.selections.all::<Point>(cx);
17448 let ranges = selections
17449 .iter()
17450 .map(|s| {
17451 let mut range = s.display_range(&display_map).sorted();
17452 *range.start.column_mut() = 0;
17453 *range.end.column_mut() = display_map.line_len(range.end.row());
17454 let start = range.start.to_point(&display_map);
17455 let end = range.end.to_point(&display_map);
17456 start..end
17457 })
17458 .collect::<Vec<_>>();
17459
17460 self.unfold_ranges(&ranges, true, true, cx);
17461 }
17462
17463 pub fn unfold_at(
17464 &mut self,
17465 buffer_row: MultiBufferRow,
17466 _window: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17470
17471 let intersection_range = Point::new(buffer_row.0, 0)
17472 ..Point::new(
17473 buffer_row.0,
17474 display_map.buffer_snapshot.line_len(buffer_row),
17475 );
17476
17477 let autoscroll = self
17478 .selections
17479 .all::<Point>(cx)
17480 .iter()
17481 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17482
17483 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17484 }
17485
17486 pub fn unfold_all(
17487 &mut self,
17488 _: &actions::UnfoldAll,
17489 _window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 if self.buffer.read(cx).is_singleton() {
17493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17494 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17495 } else {
17496 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17497 editor
17498 .update(cx, |editor, cx| {
17499 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17500 editor.unfold_buffer(buffer_id, cx);
17501 }
17502 })
17503 .ok();
17504 });
17505 }
17506 }
17507
17508 pub fn fold_selected_ranges(
17509 &mut self,
17510 _: &FoldSelectedRanges,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let selections = self.selections.all_adjusted(cx);
17515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17516 let ranges = selections
17517 .into_iter()
17518 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17519 .collect::<Vec<_>>();
17520 self.fold_creases(ranges, true, window, cx);
17521 }
17522
17523 pub fn fold_ranges<T: ToOffset + Clone>(
17524 &mut self,
17525 ranges: Vec<Range<T>>,
17526 auto_scroll: bool,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17531 let ranges = ranges
17532 .into_iter()
17533 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17534 .collect::<Vec<_>>();
17535 self.fold_creases(ranges, auto_scroll, window, cx);
17536 }
17537
17538 pub fn fold_creases<T: ToOffset + Clone>(
17539 &mut self,
17540 creases: Vec<Crease<T>>,
17541 auto_scroll: bool,
17542 _window: &mut Window,
17543 cx: &mut Context<Self>,
17544 ) {
17545 if creases.is_empty() {
17546 return;
17547 }
17548
17549 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17550
17551 if auto_scroll {
17552 self.request_autoscroll(Autoscroll::fit(), cx);
17553 }
17554
17555 cx.notify();
17556
17557 self.scrollbar_marker_state.dirty = true;
17558 self.folds_did_change(cx);
17559 }
17560
17561 /// Removes any folds whose ranges intersect any of the given ranges.
17562 pub fn unfold_ranges<T: ToOffset + Clone>(
17563 &mut self,
17564 ranges: &[Range<T>],
17565 inclusive: bool,
17566 auto_scroll: bool,
17567 cx: &mut Context<Self>,
17568 ) {
17569 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17570 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17571 });
17572 self.folds_did_change(cx);
17573 }
17574
17575 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17576 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17577 return;
17578 }
17579 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17580 self.display_map.update(cx, |display_map, cx| {
17581 display_map.fold_buffers([buffer_id], cx)
17582 });
17583 cx.emit(EditorEvent::BufferFoldToggled {
17584 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17585 folded: true,
17586 });
17587 cx.notify();
17588 }
17589
17590 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17591 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17592 return;
17593 }
17594 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17595 self.display_map.update(cx, |display_map, cx| {
17596 display_map.unfold_buffers([buffer_id], cx);
17597 });
17598 cx.emit(EditorEvent::BufferFoldToggled {
17599 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17600 folded: false,
17601 });
17602 cx.notify();
17603 }
17604
17605 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17606 self.display_map.read(cx).is_buffer_folded(buffer)
17607 }
17608
17609 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17610 self.display_map.read(cx).folded_buffers()
17611 }
17612
17613 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17614 self.display_map.update(cx, |display_map, cx| {
17615 display_map.disable_header_for_buffer(buffer_id, cx);
17616 });
17617 cx.notify();
17618 }
17619
17620 /// Removes any folds with the given ranges.
17621 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17622 &mut self,
17623 ranges: &[Range<T>],
17624 type_id: TypeId,
17625 auto_scroll: bool,
17626 cx: &mut Context<Self>,
17627 ) {
17628 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17629 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17630 });
17631 self.folds_did_change(cx);
17632 }
17633
17634 fn remove_folds_with<T: ToOffset + Clone>(
17635 &mut self,
17636 ranges: &[Range<T>],
17637 auto_scroll: bool,
17638 cx: &mut Context<Self>,
17639 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17640 ) {
17641 if ranges.is_empty() {
17642 return;
17643 }
17644
17645 let mut buffers_affected = HashSet::default();
17646 let multi_buffer = self.buffer().read(cx);
17647 for range in ranges {
17648 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17649 buffers_affected.insert(buffer.read(cx).remote_id());
17650 };
17651 }
17652
17653 self.display_map.update(cx, update);
17654
17655 if auto_scroll {
17656 self.request_autoscroll(Autoscroll::fit(), cx);
17657 }
17658
17659 cx.notify();
17660 self.scrollbar_marker_state.dirty = true;
17661 self.active_indent_guides_state.dirty = true;
17662 }
17663
17664 pub fn update_renderer_widths(
17665 &mut self,
17666 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17667 cx: &mut Context<Self>,
17668 ) -> bool {
17669 self.display_map
17670 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17671 }
17672
17673 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17674 self.display_map.read(cx).fold_placeholder.clone()
17675 }
17676
17677 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17678 self.buffer.update(cx, |buffer, cx| {
17679 buffer.set_all_diff_hunks_expanded(cx);
17680 });
17681 }
17682
17683 pub fn expand_all_diff_hunks(
17684 &mut self,
17685 _: &ExpandAllDiffHunks,
17686 _window: &mut Window,
17687 cx: &mut Context<Self>,
17688 ) {
17689 self.buffer.update(cx, |buffer, cx| {
17690 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17691 });
17692 }
17693
17694 pub fn toggle_selected_diff_hunks(
17695 &mut self,
17696 _: &ToggleSelectedDiffHunks,
17697 _window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) {
17700 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17701 self.toggle_diff_hunks_in_ranges(ranges, cx);
17702 }
17703
17704 pub fn diff_hunks_in_ranges<'a>(
17705 &'a self,
17706 ranges: &'a [Range<Anchor>],
17707 buffer: &'a MultiBufferSnapshot,
17708 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17709 ranges.iter().flat_map(move |range| {
17710 let end_excerpt_id = range.end.excerpt_id;
17711 let range = range.to_point(buffer);
17712 let mut peek_end = range.end;
17713 if range.end.row < buffer.max_row().0 {
17714 peek_end = Point::new(range.end.row + 1, 0);
17715 }
17716 buffer
17717 .diff_hunks_in_range(range.start..peek_end)
17718 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17719 })
17720 }
17721
17722 pub fn has_stageable_diff_hunks_in_ranges(
17723 &self,
17724 ranges: &[Range<Anchor>],
17725 snapshot: &MultiBufferSnapshot,
17726 ) -> bool {
17727 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17728 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17729 }
17730
17731 pub fn toggle_staged_selected_diff_hunks(
17732 &mut self,
17733 _: &::git::ToggleStaged,
17734 _: &mut Window,
17735 cx: &mut Context<Self>,
17736 ) {
17737 let snapshot = self.buffer.read(cx).snapshot(cx);
17738 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17739 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17740 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17741 }
17742
17743 pub fn set_render_diff_hunk_controls(
17744 &mut self,
17745 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17746 cx: &mut Context<Self>,
17747 ) {
17748 self.render_diff_hunk_controls = render_diff_hunk_controls;
17749 cx.notify();
17750 }
17751
17752 pub fn stage_and_next(
17753 &mut self,
17754 _: &::git::StageAndNext,
17755 window: &mut Window,
17756 cx: &mut Context<Self>,
17757 ) {
17758 self.do_stage_or_unstage_and_next(true, window, cx);
17759 }
17760
17761 pub fn unstage_and_next(
17762 &mut self,
17763 _: &::git::UnstageAndNext,
17764 window: &mut Window,
17765 cx: &mut Context<Self>,
17766 ) {
17767 self.do_stage_or_unstage_and_next(false, window, cx);
17768 }
17769
17770 pub fn stage_or_unstage_diff_hunks(
17771 &mut self,
17772 stage: bool,
17773 ranges: Vec<Range<Anchor>>,
17774 cx: &mut Context<Self>,
17775 ) {
17776 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17777 cx.spawn(async move |this, cx| {
17778 task.await?;
17779 this.update(cx, |this, cx| {
17780 let snapshot = this.buffer.read(cx).snapshot(cx);
17781 let chunk_by = this
17782 .diff_hunks_in_ranges(&ranges, &snapshot)
17783 .chunk_by(|hunk| hunk.buffer_id);
17784 for (buffer_id, hunks) in &chunk_by {
17785 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17786 }
17787 })
17788 })
17789 .detach_and_log_err(cx);
17790 }
17791
17792 fn save_buffers_for_ranges_if_needed(
17793 &mut self,
17794 ranges: &[Range<Anchor>],
17795 cx: &mut Context<Editor>,
17796 ) -> Task<Result<()>> {
17797 let multibuffer = self.buffer.read(cx);
17798 let snapshot = multibuffer.read(cx);
17799 let buffer_ids: HashSet<_> = ranges
17800 .iter()
17801 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17802 .collect();
17803 drop(snapshot);
17804
17805 let mut buffers = HashSet::default();
17806 for buffer_id in buffer_ids {
17807 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17808 let buffer = buffer_entity.read(cx);
17809 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17810 {
17811 buffers.insert(buffer_entity);
17812 }
17813 }
17814 }
17815
17816 if let Some(project) = &self.project {
17817 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17818 } else {
17819 Task::ready(Ok(()))
17820 }
17821 }
17822
17823 fn do_stage_or_unstage_and_next(
17824 &mut self,
17825 stage: bool,
17826 window: &mut Window,
17827 cx: &mut Context<Self>,
17828 ) {
17829 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17830
17831 if ranges.iter().any(|range| range.start != range.end) {
17832 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17833 return;
17834 }
17835
17836 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17837 let snapshot = self.snapshot(window, cx);
17838 let position = self.selections.newest::<Point>(cx).head();
17839 let mut row = snapshot
17840 .buffer_snapshot
17841 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17842 .find(|hunk| hunk.row_range.start.0 > position.row)
17843 .map(|hunk| hunk.row_range.start);
17844
17845 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17846 // Outside of the project diff editor, wrap around to the beginning.
17847 if !all_diff_hunks_expanded {
17848 row = row.or_else(|| {
17849 snapshot
17850 .buffer_snapshot
17851 .diff_hunks_in_range(Point::zero()..position)
17852 .find(|hunk| hunk.row_range.end.0 < position.row)
17853 .map(|hunk| hunk.row_range.start)
17854 });
17855 }
17856
17857 if let Some(row) = row {
17858 let destination = Point::new(row.0, 0);
17859 let autoscroll = Autoscroll::center();
17860
17861 self.unfold_ranges(&[destination..destination], false, false, cx);
17862 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17863 s.select_ranges([destination..destination]);
17864 });
17865 }
17866 }
17867
17868 fn do_stage_or_unstage(
17869 &self,
17870 stage: bool,
17871 buffer_id: BufferId,
17872 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17873 cx: &mut App,
17874 ) -> Option<()> {
17875 let project = self.project.as_ref()?;
17876 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17877 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17878 let buffer_snapshot = buffer.read(cx).snapshot();
17879 let file_exists = buffer_snapshot
17880 .file()
17881 .is_some_and(|file| file.disk_state().exists());
17882 diff.update(cx, |diff, cx| {
17883 diff.stage_or_unstage_hunks(
17884 stage,
17885 &hunks
17886 .map(|hunk| buffer_diff::DiffHunk {
17887 buffer_range: hunk.buffer_range,
17888 diff_base_byte_range: hunk.diff_base_byte_range,
17889 secondary_status: hunk.secondary_status,
17890 range: Point::zero()..Point::zero(), // unused
17891 })
17892 .collect::<Vec<_>>(),
17893 &buffer_snapshot,
17894 file_exists,
17895 cx,
17896 )
17897 });
17898 None
17899 }
17900
17901 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17902 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17903 self.buffer
17904 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17905 }
17906
17907 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17908 self.buffer.update(cx, |buffer, cx| {
17909 let ranges = vec![Anchor::min()..Anchor::max()];
17910 if !buffer.all_diff_hunks_expanded()
17911 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17912 {
17913 buffer.collapse_diff_hunks(ranges, cx);
17914 true
17915 } else {
17916 false
17917 }
17918 })
17919 }
17920
17921 fn toggle_diff_hunks_in_ranges(
17922 &mut self,
17923 ranges: Vec<Range<Anchor>>,
17924 cx: &mut Context<Editor>,
17925 ) {
17926 self.buffer.update(cx, |buffer, cx| {
17927 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17928 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17929 })
17930 }
17931
17932 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17933 self.buffer.update(cx, |buffer, cx| {
17934 let snapshot = buffer.snapshot(cx);
17935 let excerpt_id = range.end.excerpt_id;
17936 let point_range = range.to_point(&snapshot);
17937 let expand = !buffer.single_hunk_is_expanded(range, cx);
17938 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17939 })
17940 }
17941
17942 pub(crate) fn apply_all_diff_hunks(
17943 &mut self,
17944 _: &ApplyAllDiffHunks,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17949
17950 let buffers = self.buffer.read(cx).all_buffers();
17951 for branch_buffer in buffers {
17952 branch_buffer.update(cx, |branch_buffer, cx| {
17953 branch_buffer.merge_into_base(Vec::new(), cx);
17954 });
17955 }
17956
17957 if let Some(project) = self.project.clone() {
17958 self.save(
17959 SaveOptions {
17960 format: true,
17961 autosave: false,
17962 },
17963 project,
17964 window,
17965 cx,
17966 )
17967 .detach_and_log_err(cx);
17968 }
17969 }
17970
17971 pub(crate) fn apply_selected_diff_hunks(
17972 &mut self,
17973 _: &ApplyDiffHunk,
17974 window: &mut Window,
17975 cx: &mut Context<Self>,
17976 ) {
17977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17978 let snapshot = self.snapshot(window, cx);
17979 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17980 let mut ranges_by_buffer = HashMap::default();
17981 self.transact(window, cx, |editor, _window, cx| {
17982 for hunk in hunks {
17983 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17984 ranges_by_buffer
17985 .entry(buffer.clone())
17986 .or_insert_with(Vec::new)
17987 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17988 }
17989 }
17990
17991 for (buffer, ranges) in ranges_by_buffer {
17992 buffer.update(cx, |buffer, cx| {
17993 buffer.merge_into_base(ranges, cx);
17994 });
17995 }
17996 });
17997
17998 if let Some(project) = self.project.clone() {
17999 self.save(
18000 SaveOptions {
18001 format: true,
18002 autosave: false,
18003 },
18004 project,
18005 window,
18006 cx,
18007 )
18008 .detach_and_log_err(cx);
18009 }
18010 }
18011
18012 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18013 if hovered != self.gutter_hovered {
18014 self.gutter_hovered = hovered;
18015 cx.notify();
18016 }
18017 }
18018
18019 pub fn insert_blocks(
18020 &mut self,
18021 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18022 autoscroll: Option<Autoscroll>,
18023 cx: &mut Context<Self>,
18024 ) -> Vec<CustomBlockId> {
18025 let blocks = self
18026 .display_map
18027 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18028 if let Some(autoscroll) = autoscroll {
18029 self.request_autoscroll(autoscroll, cx);
18030 }
18031 cx.notify();
18032 blocks
18033 }
18034
18035 pub fn resize_blocks(
18036 &mut self,
18037 heights: HashMap<CustomBlockId, u32>,
18038 autoscroll: Option<Autoscroll>,
18039 cx: &mut Context<Self>,
18040 ) {
18041 self.display_map
18042 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18043 if let Some(autoscroll) = autoscroll {
18044 self.request_autoscroll(autoscroll, cx);
18045 }
18046 cx.notify();
18047 }
18048
18049 pub fn replace_blocks(
18050 &mut self,
18051 renderers: HashMap<CustomBlockId, RenderBlock>,
18052 autoscroll: Option<Autoscroll>,
18053 cx: &mut Context<Self>,
18054 ) {
18055 self.display_map
18056 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18057 if let Some(autoscroll) = autoscroll {
18058 self.request_autoscroll(autoscroll, cx);
18059 }
18060 cx.notify();
18061 }
18062
18063 pub fn remove_blocks(
18064 &mut self,
18065 block_ids: HashSet<CustomBlockId>,
18066 autoscroll: Option<Autoscroll>,
18067 cx: &mut Context<Self>,
18068 ) {
18069 self.display_map.update(cx, |display_map, cx| {
18070 display_map.remove_blocks(block_ids, cx)
18071 });
18072 if let Some(autoscroll) = autoscroll {
18073 self.request_autoscroll(autoscroll, cx);
18074 }
18075 cx.notify();
18076 }
18077
18078 pub fn row_for_block(
18079 &self,
18080 block_id: CustomBlockId,
18081 cx: &mut Context<Self>,
18082 ) -> Option<DisplayRow> {
18083 self.display_map
18084 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18085 }
18086
18087 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18088 self.focused_block = Some(focused_block);
18089 }
18090
18091 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18092 self.focused_block.take()
18093 }
18094
18095 pub fn insert_creases(
18096 &mut self,
18097 creases: impl IntoIterator<Item = Crease<Anchor>>,
18098 cx: &mut Context<Self>,
18099 ) -> Vec<CreaseId> {
18100 self.display_map
18101 .update(cx, |map, cx| map.insert_creases(creases, cx))
18102 }
18103
18104 pub fn remove_creases(
18105 &mut self,
18106 ids: impl IntoIterator<Item = CreaseId>,
18107 cx: &mut Context<Self>,
18108 ) -> Vec<(CreaseId, Range<Anchor>)> {
18109 self.display_map
18110 .update(cx, |map, cx| map.remove_creases(ids, cx))
18111 }
18112
18113 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18114 self.display_map
18115 .update(cx, |map, cx| map.snapshot(cx))
18116 .longest_row()
18117 }
18118
18119 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18120 self.display_map
18121 .update(cx, |map, cx| map.snapshot(cx))
18122 .max_point()
18123 }
18124
18125 pub fn text(&self, cx: &App) -> String {
18126 self.buffer.read(cx).read(cx).text()
18127 }
18128
18129 pub fn is_empty(&self, cx: &App) -> bool {
18130 self.buffer.read(cx).read(cx).is_empty()
18131 }
18132
18133 pub fn text_option(&self, cx: &App) -> Option<String> {
18134 let text = self.text(cx);
18135 let text = text.trim();
18136
18137 if text.is_empty() {
18138 return None;
18139 }
18140
18141 Some(text.to_string())
18142 }
18143
18144 pub fn set_text(
18145 &mut self,
18146 text: impl Into<Arc<str>>,
18147 window: &mut Window,
18148 cx: &mut Context<Self>,
18149 ) {
18150 self.transact(window, cx, |this, _, cx| {
18151 this.buffer
18152 .read(cx)
18153 .as_singleton()
18154 .expect("you can only call set_text on editors for singleton buffers")
18155 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18156 });
18157 }
18158
18159 pub fn display_text(&self, cx: &mut App) -> String {
18160 self.display_map
18161 .update(cx, |map, cx| map.snapshot(cx))
18162 .text()
18163 }
18164
18165 fn create_minimap(
18166 &self,
18167 minimap_settings: MinimapSettings,
18168 window: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) -> Option<Entity<Self>> {
18171 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18172 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18173 }
18174
18175 fn initialize_new_minimap(
18176 &self,
18177 minimap_settings: MinimapSettings,
18178 window: &mut Window,
18179 cx: &mut Context<Self>,
18180 ) -> Entity<Self> {
18181 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18182
18183 let mut minimap = Editor::new_internal(
18184 EditorMode::Minimap {
18185 parent: cx.weak_entity(),
18186 },
18187 self.buffer.clone(),
18188 None,
18189 Some(self.display_map.clone()),
18190 window,
18191 cx,
18192 );
18193 minimap.scroll_manager.clone_state(&self.scroll_manager);
18194 minimap.set_text_style_refinement(TextStyleRefinement {
18195 font_size: Some(MINIMAP_FONT_SIZE),
18196 font_weight: Some(MINIMAP_FONT_WEIGHT),
18197 ..Default::default()
18198 });
18199 minimap.update_minimap_configuration(minimap_settings, cx);
18200 cx.new(|_| minimap)
18201 }
18202
18203 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18204 let current_line_highlight = minimap_settings
18205 .current_line_highlight
18206 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18207 self.set_current_line_highlight(Some(current_line_highlight));
18208 }
18209
18210 pub fn minimap(&self) -> Option<&Entity<Self>> {
18211 self.minimap
18212 .as_ref()
18213 .filter(|_| self.minimap_visibility.visible())
18214 }
18215
18216 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18217 let mut wrap_guides = smallvec![];
18218
18219 if self.show_wrap_guides == Some(false) {
18220 return wrap_guides;
18221 }
18222
18223 let settings = self.buffer.read(cx).language_settings(cx);
18224 if settings.show_wrap_guides {
18225 match self.soft_wrap_mode(cx) {
18226 SoftWrap::Column(soft_wrap) => {
18227 wrap_guides.push((soft_wrap as usize, true));
18228 }
18229 SoftWrap::Bounded(soft_wrap) => {
18230 wrap_guides.push((soft_wrap as usize, true));
18231 }
18232 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18233 }
18234 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18235 }
18236
18237 wrap_guides
18238 }
18239
18240 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18241 let settings = self.buffer.read(cx).language_settings(cx);
18242 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18243 match mode {
18244 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18245 SoftWrap::None
18246 }
18247 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18248 language_settings::SoftWrap::PreferredLineLength => {
18249 SoftWrap::Column(settings.preferred_line_length)
18250 }
18251 language_settings::SoftWrap::Bounded => {
18252 SoftWrap::Bounded(settings.preferred_line_length)
18253 }
18254 }
18255 }
18256
18257 pub fn set_soft_wrap_mode(
18258 &mut self,
18259 mode: language_settings::SoftWrap,
18260
18261 cx: &mut Context<Self>,
18262 ) {
18263 self.soft_wrap_mode_override = Some(mode);
18264 cx.notify();
18265 }
18266
18267 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18268 self.hard_wrap = hard_wrap;
18269 cx.notify();
18270 }
18271
18272 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18273 self.text_style_refinement = Some(style);
18274 }
18275
18276 /// called by the Element so we know what style we were most recently rendered with.
18277 pub(crate) fn set_style(
18278 &mut self,
18279 style: EditorStyle,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 // We intentionally do not inform the display map about the minimap style
18284 // so that wrapping is not recalculated and stays consistent for the editor
18285 // and its linked minimap.
18286 if !self.mode.is_minimap() {
18287 let rem_size = window.rem_size();
18288 self.display_map.update(cx, |map, cx| {
18289 map.set_font(
18290 style.text.font(),
18291 style.text.font_size.to_pixels(rem_size),
18292 cx,
18293 )
18294 });
18295 }
18296 self.style = Some(style);
18297 }
18298
18299 pub fn style(&self) -> Option<&EditorStyle> {
18300 self.style.as_ref()
18301 }
18302
18303 // Called by the element. This method is not designed to be called outside of the editor
18304 // element's layout code because it does not notify when rewrapping is computed synchronously.
18305 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18306 self.display_map
18307 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18308 }
18309
18310 pub fn set_soft_wrap(&mut self) {
18311 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18312 }
18313
18314 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18315 if self.soft_wrap_mode_override.is_some() {
18316 self.soft_wrap_mode_override.take();
18317 } else {
18318 let soft_wrap = match self.soft_wrap_mode(cx) {
18319 SoftWrap::GitDiff => return,
18320 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18321 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18322 language_settings::SoftWrap::None
18323 }
18324 };
18325 self.soft_wrap_mode_override = Some(soft_wrap);
18326 }
18327 cx.notify();
18328 }
18329
18330 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18331 let Some(workspace) = self.workspace() else {
18332 return;
18333 };
18334 let fs = workspace.read(cx).app_state().fs.clone();
18335 let current_show = TabBarSettings::get_global(cx).show;
18336 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18337 setting.show = Some(!current_show);
18338 });
18339 }
18340
18341 pub fn toggle_indent_guides(
18342 &mut self,
18343 _: &ToggleIndentGuides,
18344 _: &mut Window,
18345 cx: &mut Context<Self>,
18346 ) {
18347 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18348 self.buffer
18349 .read(cx)
18350 .language_settings(cx)
18351 .indent_guides
18352 .enabled
18353 });
18354 self.show_indent_guides = Some(!currently_enabled);
18355 cx.notify();
18356 }
18357
18358 fn should_show_indent_guides(&self) -> Option<bool> {
18359 self.show_indent_guides
18360 }
18361
18362 pub fn toggle_line_numbers(
18363 &mut self,
18364 _: &ToggleLineNumbers,
18365 _: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) {
18368 let mut editor_settings = EditorSettings::get_global(cx).clone();
18369 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18370 EditorSettings::override_global(editor_settings, cx);
18371 }
18372
18373 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18374 if let Some(show_line_numbers) = self.show_line_numbers {
18375 return show_line_numbers;
18376 }
18377 EditorSettings::get_global(cx).gutter.line_numbers
18378 }
18379
18380 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18381 self.use_relative_line_numbers
18382 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18383 }
18384
18385 pub fn toggle_relative_line_numbers(
18386 &mut self,
18387 _: &ToggleRelativeLineNumbers,
18388 _: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 let is_relative = self.should_use_relative_line_numbers(cx);
18392 self.set_relative_line_number(Some(!is_relative), cx)
18393 }
18394
18395 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18396 self.use_relative_line_numbers = is_relative;
18397 cx.notify();
18398 }
18399
18400 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18401 self.show_gutter = show_gutter;
18402 cx.notify();
18403 }
18404
18405 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18406 self.show_scrollbars = ScrollbarAxes {
18407 horizontal: show,
18408 vertical: show,
18409 };
18410 cx.notify();
18411 }
18412
18413 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18414 self.show_scrollbars.vertical = show;
18415 cx.notify();
18416 }
18417
18418 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18419 self.show_scrollbars.horizontal = show;
18420 cx.notify();
18421 }
18422
18423 pub fn set_minimap_visibility(
18424 &mut self,
18425 minimap_visibility: MinimapVisibility,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 if self.minimap_visibility != minimap_visibility {
18430 if minimap_visibility.visible() && self.minimap.is_none() {
18431 let minimap_settings = EditorSettings::get_global(cx).minimap;
18432 self.minimap =
18433 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18434 }
18435 self.minimap_visibility = minimap_visibility;
18436 cx.notify();
18437 }
18438 }
18439
18440 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18441 self.set_show_scrollbars(false, cx);
18442 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18443 }
18444
18445 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18446 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18447 }
18448
18449 /// Normally the text in full mode and auto height editors is padded on the
18450 /// left side by roughly half a character width for improved hit testing.
18451 ///
18452 /// Use this method to disable this for cases where this is not wanted (e.g.
18453 /// if you want to align the editor text with some other text above or below)
18454 /// or if you want to add this padding to single-line editors.
18455 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18456 self.offset_content = offset_content;
18457 cx.notify();
18458 }
18459
18460 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18461 self.show_line_numbers = Some(show_line_numbers);
18462 cx.notify();
18463 }
18464
18465 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18466 self.disable_expand_excerpt_buttons = true;
18467 cx.notify();
18468 }
18469
18470 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18471 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18472 cx.notify();
18473 }
18474
18475 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18476 self.show_code_actions = Some(show_code_actions);
18477 cx.notify();
18478 }
18479
18480 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18481 self.show_runnables = Some(show_runnables);
18482 cx.notify();
18483 }
18484
18485 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18486 self.show_breakpoints = Some(show_breakpoints);
18487 cx.notify();
18488 }
18489
18490 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18491 if self.display_map.read(cx).masked != masked {
18492 self.display_map.update(cx, |map, _| map.masked = masked);
18493 }
18494 cx.notify()
18495 }
18496
18497 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18498 self.show_wrap_guides = Some(show_wrap_guides);
18499 cx.notify();
18500 }
18501
18502 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18503 self.show_indent_guides = Some(show_indent_guides);
18504 cx.notify();
18505 }
18506
18507 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18508 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18509 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18510 if let Some(dir) = file.abs_path(cx).parent() {
18511 return Some(dir.to_owned());
18512 }
18513 }
18514
18515 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18516 return Some(project_path.path.to_path_buf());
18517 }
18518 }
18519
18520 None
18521 }
18522
18523 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18524 self.active_excerpt(cx)?
18525 .1
18526 .read(cx)
18527 .file()
18528 .and_then(|f| f.as_local())
18529 }
18530
18531 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18532 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18533 let buffer = buffer.read(cx);
18534 if let Some(project_path) = buffer.project_path(cx) {
18535 let project = self.project.as_ref()?.read(cx);
18536 project.absolute_path(&project_path, cx)
18537 } else {
18538 buffer
18539 .file()
18540 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18541 }
18542 })
18543 }
18544
18545 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18546 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18547 let project_path = buffer.read(cx).project_path(cx)?;
18548 let project = self.project.as_ref()?.read(cx);
18549 let entry = project.entry_for_path(&project_path, cx)?;
18550 let path = entry.path.to_path_buf();
18551 Some(path)
18552 })
18553 }
18554
18555 pub fn reveal_in_finder(
18556 &mut self,
18557 _: &RevealInFileManager,
18558 _window: &mut Window,
18559 cx: &mut Context<Self>,
18560 ) {
18561 if let Some(target) = self.target_file(cx) {
18562 cx.reveal_path(&target.abs_path(cx));
18563 }
18564 }
18565
18566 pub fn copy_path(
18567 &mut self,
18568 _: &zed_actions::workspace::CopyPath,
18569 _window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 if let Some(path) = self.target_file_abs_path(cx) {
18573 if let Some(path) = path.to_str() {
18574 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18575 }
18576 }
18577 }
18578
18579 pub fn copy_relative_path(
18580 &mut self,
18581 _: &zed_actions::workspace::CopyRelativePath,
18582 _window: &mut Window,
18583 cx: &mut Context<Self>,
18584 ) {
18585 if let Some(path) = self.target_file_path(cx) {
18586 if let Some(path) = path.to_str() {
18587 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18588 }
18589 }
18590 }
18591
18592 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18593 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18594 buffer.read(cx).project_path(cx)
18595 } else {
18596 None
18597 }
18598 }
18599
18600 // Returns true if the editor handled a go-to-line request
18601 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18602 maybe!({
18603 let breakpoint_store = self.breakpoint_store.as_ref()?;
18604
18605 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18606 else {
18607 self.clear_row_highlights::<ActiveDebugLine>();
18608 return None;
18609 };
18610
18611 let position = active_stack_frame.position;
18612 let buffer_id = position.buffer_id?;
18613 let snapshot = self
18614 .project
18615 .as_ref()?
18616 .read(cx)
18617 .buffer_for_id(buffer_id, cx)?
18618 .read(cx)
18619 .snapshot();
18620
18621 let mut handled = false;
18622 for (id, ExcerptRange { context, .. }) in
18623 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18624 {
18625 if context.start.cmp(&position, &snapshot).is_ge()
18626 || context.end.cmp(&position, &snapshot).is_lt()
18627 {
18628 continue;
18629 }
18630 let snapshot = self.buffer.read(cx).snapshot(cx);
18631 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18632
18633 handled = true;
18634 self.clear_row_highlights::<ActiveDebugLine>();
18635
18636 self.go_to_line::<ActiveDebugLine>(
18637 multibuffer_anchor,
18638 Some(cx.theme().colors().editor_debugger_active_line_background),
18639 window,
18640 cx,
18641 );
18642
18643 cx.notify();
18644 }
18645
18646 handled.then_some(())
18647 })
18648 .is_some()
18649 }
18650
18651 pub fn copy_file_name_without_extension(
18652 &mut self,
18653 _: &CopyFileNameWithoutExtension,
18654 _: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 if let Some(file) = self.target_file(cx) {
18658 if let Some(file_stem) = file.path().file_stem() {
18659 if let Some(name) = file_stem.to_str() {
18660 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18661 }
18662 }
18663 }
18664 }
18665
18666 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18667 if let Some(file) = self.target_file(cx) {
18668 if let Some(file_name) = file.path().file_name() {
18669 if let Some(name) = file_name.to_str() {
18670 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18671 }
18672 }
18673 }
18674 }
18675
18676 pub fn toggle_git_blame(
18677 &mut self,
18678 _: &::git::Blame,
18679 window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18683
18684 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18685 self.start_git_blame(true, window, cx);
18686 }
18687
18688 cx.notify();
18689 }
18690
18691 pub fn toggle_git_blame_inline(
18692 &mut self,
18693 _: &ToggleGitBlameInline,
18694 window: &mut Window,
18695 cx: &mut Context<Self>,
18696 ) {
18697 self.toggle_git_blame_inline_internal(true, window, cx);
18698 cx.notify();
18699 }
18700
18701 pub fn open_git_blame_commit(
18702 &mut self,
18703 _: &OpenGitBlameCommit,
18704 window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 self.open_git_blame_commit_internal(window, cx);
18708 }
18709
18710 fn open_git_blame_commit_internal(
18711 &mut self,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) -> Option<()> {
18715 let blame = self.blame.as_ref()?;
18716 let snapshot = self.snapshot(window, cx);
18717 let cursor = self.selections.newest::<Point>(cx).head();
18718 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18719 let blame_entry = blame
18720 .update(cx, |blame, cx| {
18721 blame
18722 .blame_for_rows(
18723 &[RowInfo {
18724 buffer_id: Some(buffer.remote_id()),
18725 buffer_row: Some(point.row),
18726 ..Default::default()
18727 }],
18728 cx,
18729 )
18730 .next()
18731 })
18732 .flatten()?;
18733 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18734 let repo = blame.read(cx).repository(cx)?;
18735 let workspace = self.workspace()?.downgrade();
18736 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18737 None
18738 }
18739
18740 pub fn git_blame_inline_enabled(&self) -> bool {
18741 self.git_blame_inline_enabled
18742 }
18743
18744 pub fn toggle_selection_menu(
18745 &mut self,
18746 _: &ToggleSelectionMenu,
18747 _: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 self.show_selection_menu = self
18751 .show_selection_menu
18752 .map(|show_selections_menu| !show_selections_menu)
18753 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18754
18755 cx.notify();
18756 }
18757
18758 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18759 self.show_selection_menu
18760 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18761 }
18762
18763 fn start_git_blame(
18764 &mut self,
18765 user_triggered: bool,
18766 window: &mut Window,
18767 cx: &mut Context<Self>,
18768 ) {
18769 if let Some(project) = self.project.as_ref() {
18770 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18771 return;
18772 };
18773
18774 if buffer.read(cx).file().is_none() {
18775 return;
18776 }
18777
18778 let focused = self.focus_handle(cx).contains_focused(window, cx);
18779
18780 let project = project.clone();
18781 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18782 self.blame_subscription =
18783 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18784 self.blame = Some(blame);
18785 }
18786 }
18787
18788 fn toggle_git_blame_inline_internal(
18789 &mut self,
18790 user_triggered: bool,
18791 window: &mut Window,
18792 cx: &mut Context<Self>,
18793 ) {
18794 if self.git_blame_inline_enabled {
18795 self.git_blame_inline_enabled = false;
18796 self.show_git_blame_inline = false;
18797 self.show_git_blame_inline_delay_task.take();
18798 } else {
18799 self.git_blame_inline_enabled = true;
18800 self.start_git_blame_inline(user_triggered, window, cx);
18801 }
18802
18803 cx.notify();
18804 }
18805
18806 fn start_git_blame_inline(
18807 &mut self,
18808 user_triggered: bool,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.start_git_blame(user_triggered, window, cx);
18813
18814 if ProjectSettings::get_global(cx)
18815 .git
18816 .inline_blame_delay()
18817 .is_some()
18818 {
18819 self.start_inline_blame_timer(window, cx);
18820 } else {
18821 self.show_git_blame_inline = true
18822 }
18823 }
18824
18825 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18826 self.blame.as_ref()
18827 }
18828
18829 pub fn show_git_blame_gutter(&self) -> bool {
18830 self.show_git_blame_gutter
18831 }
18832
18833 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18834 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18835 }
18836
18837 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18838 self.show_git_blame_inline
18839 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18840 && !self.newest_selection_head_on_empty_line(cx)
18841 && self.has_blame_entries(cx)
18842 }
18843
18844 fn has_blame_entries(&self, cx: &App) -> bool {
18845 self.blame()
18846 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18847 }
18848
18849 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18850 let cursor_anchor = self.selections.newest_anchor().head();
18851
18852 let snapshot = self.buffer.read(cx).snapshot(cx);
18853 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18854
18855 snapshot.line_len(buffer_row) == 0
18856 }
18857
18858 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18859 let buffer_and_selection = maybe!({
18860 let selection = self.selections.newest::<Point>(cx);
18861 let selection_range = selection.range();
18862
18863 let multi_buffer = self.buffer().read(cx);
18864 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18865 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18866
18867 let (buffer, range, _) = if selection.reversed {
18868 buffer_ranges.first()
18869 } else {
18870 buffer_ranges.last()
18871 }?;
18872
18873 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18874 ..text::ToPoint::to_point(&range.end, &buffer).row;
18875 Some((
18876 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18877 selection,
18878 ))
18879 });
18880
18881 let Some((buffer, selection)) = buffer_and_selection else {
18882 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18883 };
18884
18885 let Some(project) = self.project.as_ref() else {
18886 return Task::ready(Err(anyhow!("editor does not have project")));
18887 };
18888
18889 project.update(cx, |project, cx| {
18890 project.get_permalink_to_line(&buffer, selection, cx)
18891 })
18892 }
18893
18894 pub fn copy_permalink_to_line(
18895 &mut self,
18896 _: &CopyPermalinkToLine,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 let permalink_task = self.get_permalink_to_line(cx);
18901 let workspace = self.workspace();
18902
18903 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18904 Ok(permalink) => {
18905 cx.update(|_, cx| {
18906 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18907 })
18908 .ok();
18909 }
18910 Err(err) => {
18911 let message = format!("Failed to copy permalink: {err}");
18912
18913 anyhow::Result::<()>::Err(err).log_err();
18914
18915 if let Some(workspace) = workspace {
18916 workspace
18917 .update_in(cx, |workspace, _, cx| {
18918 struct CopyPermalinkToLine;
18919
18920 workspace.show_toast(
18921 Toast::new(
18922 NotificationId::unique::<CopyPermalinkToLine>(),
18923 message,
18924 ),
18925 cx,
18926 )
18927 })
18928 .ok();
18929 }
18930 }
18931 })
18932 .detach();
18933 }
18934
18935 pub fn copy_file_location(
18936 &mut self,
18937 _: &CopyFileLocation,
18938 _: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) {
18941 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18942 if let Some(file) = self.target_file(cx) {
18943 if let Some(path) = file.path().to_str() {
18944 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18945 }
18946 }
18947 }
18948
18949 pub fn open_permalink_to_line(
18950 &mut self,
18951 _: &OpenPermalinkToLine,
18952 window: &mut Window,
18953 cx: &mut Context<Self>,
18954 ) {
18955 let permalink_task = self.get_permalink_to_line(cx);
18956 let workspace = self.workspace();
18957
18958 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18959 Ok(permalink) => {
18960 cx.update(|_, cx| {
18961 cx.open_url(permalink.as_ref());
18962 })
18963 .ok();
18964 }
18965 Err(err) => {
18966 let message = format!("Failed to open permalink: {err}");
18967
18968 anyhow::Result::<()>::Err(err).log_err();
18969
18970 if let Some(workspace) = workspace {
18971 workspace
18972 .update(cx, |workspace, cx| {
18973 struct OpenPermalinkToLine;
18974
18975 workspace.show_toast(
18976 Toast::new(
18977 NotificationId::unique::<OpenPermalinkToLine>(),
18978 message,
18979 ),
18980 cx,
18981 )
18982 })
18983 .ok();
18984 }
18985 }
18986 })
18987 .detach();
18988 }
18989
18990 pub fn insert_uuid_v4(
18991 &mut self,
18992 _: &InsertUuidV4,
18993 window: &mut Window,
18994 cx: &mut Context<Self>,
18995 ) {
18996 self.insert_uuid(UuidVersion::V4, window, cx);
18997 }
18998
18999 pub fn insert_uuid_v7(
19000 &mut self,
19001 _: &InsertUuidV7,
19002 window: &mut Window,
19003 cx: &mut Context<Self>,
19004 ) {
19005 self.insert_uuid(UuidVersion::V7, window, cx);
19006 }
19007
19008 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19010 self.transact(window, cx, |this, window, cx| {
19011 let edits = this
19012 .selections
19013 .all::<Point>(cx)
19014 .into_iter()
19015 .map(|selection| {
19016 let uuid = match version {
19017 UuidVersion::V4 => uuid::Uuid::new_v4(),
19018 UuidVersion::V7 => uuid::Uuid::now_v7(),
19019 };
19020
19021 (selection.range(), uuid.to_string())
19022 });
19023 this.edit(edits, cx);
19024 this.refresh_inline_completion(true, false, window, cx);
19025 });
19026 }
19027
19028 pub fn open_selections_in_multibuffer(
19029 &mut self,
19030 _: &OpenSelectionsInMultibuffer,
19031 window: &mut Window,
19032 cx: &mut Context<Self>,
19033 ) {
19034 let multibuffer = self.buffer.read(cx);
19035
19036 let Some(buffer) = multibuffer.as_singleton() else {
19037 return;
19038 };
19039
19040 let Some(workspace) = self.workspace() else {
19041 return;
19042 };
19043
19044 let title = multibuffer.title(cx).to_string();
19045
19046 let locations = self
19047 .selections
19048 .all_anchors(cx)
19049 .into_iter()
19050 .map(|selection| Location {
19051 buffer: buffer.clone(),
19052 range: selection.start.text_anchor..selection.end.text_anchor,
19053 })
19054 .collect::<Vec<_>>();
19055
19056 cx.spawn_in(window, async move |_, cx| {
19057 workspace.update_in(cx, |workspace, window, cx| {
19058 Self::open_locations_in_multibuffer(
19059 workspace,
19060 locations,
19061 format!("Selections for '{title}'"),
19062 false,
19063 MultibufferSelectionMode::All,
19064 window,
19065 cx,
19066 );
19067 })
19068 })
19069 .detach();
19070 }
19071
19072 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19073 /// last highlight added will be used.
19074 ///
19075 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19076 pub fn highlight_rows<T: 'static>(
19077 &mut self,
19078 range: Range<Anchor>,
19079 color: Hsla,
19080 options: RowHighlightOptions,
19081 cx: &mut Context<Self>,
19082 ) {
19083 let snapshot = self.buffer().read(cx).snapshot(cx);
19084 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19085 let ix = row_highlights.binary_search_by(|highlight| {
19086 Ordering::Equal
19087 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19088 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19089 });
19090
19091 if let Err(mut ix) = ix {
19092 let index = post_inc(&mut self.highlight_order);
19093
19094 // If this range intersects with the preceding highlight, then merge it with
19095 // the preceding highlight. Otherwise insert a new highlight.
19096 let mut merged = false;
19097 if ix > 0 {
19098 let prev_highlight = &mut row_highlights[ix - 1];
19099 if prev_highlight
19100 .range
19101 .end
19102 .cmp(&range.start, &snapshot)
19103 .is_ge()
19104 {
19105 ix -= 1;
19106 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19107 prev_highlight.range.end = range.end;
19108 }
19109 merged = true;
19110 prev_highlight.index = index;
19111 prev_highlight.color = color;
19112 prev_highlight.options = options;
19113 }
19114 }
19115
19116 if !merged {
19117 row_highlights.insert(
19118 ix,
19119 RowHighlight {
19120 range: range.clone(),
19121 index,
19122 color,
19123 options,
19124 type_id: TypeId::of::<T>(),
19125 },
19126 );
19127 }
19128
19129 // If any of the following highlights intersect with this one, merge them.
19130 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19131 let highlight = &row_highlights[ix];
19132 if next_highlight
19133 .range
19134 .start
19135 .cmp(&highlight.range.end, &snapshot)
19136 .is_le()
19137 {
19138 if next_highlight
19139 .range
19140 .end
19141 .cmp(&highlight.range.end, &snapshot)
19142 .is_gt()
19143 {
19144 row_highlights[ix].range.end = next_highlight.range.end;
19145 }
19146 row_highlights.remove(ix + 1);
19147 } else {
19148 break;
19149 }
19150 }
19151 }
19152 }
19153
19154 /// Remove any highlighted row ranges of the given type that intersect the
19155 /// given ranges.
19156 pub fn remove_highlighted_rows<T: 'static>(
19157 &mut self,
19158 ranges_to_remove: Vec<Range<Anchor>>,
19159 cx: &mut Context<Self>,
19160 ) {
19161 let snapshot = self.buffer().read(cx).snapshot(cx);
19162 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19163 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19164 row_highlights.retain(|highlight| {
19165 while let Some(range_to_remove) = ranges_to_remove.peek() {
19166 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19167 Ordering::Less | Ordering::Equal => {
19168 ranges_to_remove.next();
19169 }
19170 Ordering::Greater => {
19171 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19172 Ordering::Less | Ordering::Equal => {
19173 return false;
19174 }
19175 Ordering::Greater => break,
19176 }
19177 }
19178 }
19179 }
19180
19181 true
19182 })
19183 }
19184
19185 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19186 pub fn clear_row_highlights<T: 'static>(&mut self) {
19187 self.highlighted_rows.remove(&TypeId::of::<T>());
19188 }
19189
19190 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19191 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19192 self.highlighted_rows
19193 .get(&TypeId::of::<T>())
19194 .map_or(&[] as &[_], |vec| vec.as_slice())
19195 .iter()
19196 .map(|highlight| (highlight.range.clone(), highlight.color))
19197 }
19198
19199 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19200 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19201 /// Allows to ignore certain kinds of highlights.
19202 pub fn highlighted_display_rows(
19203 &self,
19204 window: &mut Window,
19205 cx: &mut App,
19206 ) -> BTreeMap<DisplayRow, LineHighlight> {
19207 let snapshot = self.snapshot(window, cx);
19208 let mut used_highlight_orders = HashMap::default();
19209 self.highlighted_rows
19210 .iter()
19211 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19212 .fold(
19213 BTreeMap::<DisplayRow, LineHighlight>::new(),
19214 |mut unique_rows, highlight| {
19215 let start = highlight.range.start.to_display_point(&snapshot);
19216 let end = highlight.range.end.to_display_point(&snapshot);
19217 let start_row = start.row().0;
19218 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19219 && end.column() == 0
19220 {
19221 end.row().0.saturating_sub(1)
19222 } else {
19223 end.row().0
19224 };
19225 for row in start_row..=end_row {
19226 let used_index =
19227 used_highlight_orders.entry(row).or_insert(highlight.index);
19228 if highlight.index >= *used_index {
19229 *used_index = highlight.index;
19230 unique_rows.insert(
19231 DisplayRow(row),
19232 LineHighlight {
19233 include_gutter: highlight.options.include_gutter,
19234 border: None,
19235 background: highlight.color.into(),
19236 type_id: Some(highlight.type_id),
19237 },
19238 );
19239 }
19240 }
19241 unique_rows
19242 },
19243 )
19244 }
19245
19246 pub fn highlighted_display_row_for_autoscroll(
19247 &self,
19248 snapshot: &DisplaySnapshot,
19249 ) -> Option<DisplayRow> {
19250 self.highlighted_rows
19251 .values()
19252 .flat_map(|highlighted_rows| highlighted_rows.iter())
19253 .filter_map(|highlight| {
19254 if highlight.options.autoscroll {
19255 Some(highlight.range.start.to_display_point(snapshot).row())
19256 } else {
19257 None
19258 }
19259 })
19260 .min()
19261 }
19262
19263 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19264 self.highlight_background::<SearchWithinRange>(
19265 ranges,
19266 |colors| colors.colors().editor_document_highlight_read_background,
19267 cx,
19268 )
19269 }
19270
19271 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19272 self.breadcrumb_header = Some(new_header);
19273 }
19274
19275 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19276 self.clear_background_highlights::<SearchWithinRange>(cx);
19277 }
19278
19279 pub fn highlight_background<T: 'static>(
19280 &mut self,
19281 ranges: &[Range<Anchor>],
19282 color_fetcher: fn(&Theme) -> Hsla,
19283 cx: &mut Context<Self>,
19284 ) {
19285 self.background_highlights.insert(
19286 HighlightKey::Type(TypeId::of::<T>()),
19287 (color_fetcher, Arc::from(ranges)),
19288 );
19289 self.scrollbar_marker_state.dirty = true;
19290 cx.notify();
19291 }
19292
19293 pub fn highlight_background_key<T: 'static>(
19294 &mut self,
19295 key: usize,
19296 ranges: &[Range<Anchor>],
19297 color_fetcher: fn(&Theme) -> Hsla,
19298 cx: &mut Context<Self>,
19299 ) {
19300 self.background_highlights.insert(
19301 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19302 (color_fetcher, Arc::from(ranges)),
19303 );
19304 self.scrollbar_marker_state.dirty = true;
19305 cx.notify();
19306 }
19307
19308 pub fn clear_background_highlights<T: 'static>(
19309 &mut self,
19310 cx: &mut Context<Self>,
19311 ) -> Option<BackgroundHighlight> {
19312 let text_highlights = self
19313 .background_highlights
19314 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19315 if !text_highlights.1.is_empty() {
19316 self.scrollbar_marker_state.dirty = true;
19317 cx.notify();
19318 }
19319 Some(text_highlights)
19320 }
19321
19322 pub fn highlight_gutter<T: 'static>(
19323 &mut self,
19324 ranges: impl Into<Vec<Range<Anchor>>>,
19325 color_fetcher: fn(&App) -> Hsla,
19326 cx: &mut Context<Self>,
19327 ) {
19328 self.gutter_highlights
19329 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19330 cx.notify();
19331 }
19332
19333 pub fn clear_gutter_highlights<T: 'static>(
19334 &mut self,
19335 cx: &mut Context<Self>,
19336 ) -> Option<GutterHighlight> {
19337 cx.notify();
19338 self.gutter_highlights.remove(&TypeId::of::<T>())
19339 }
19340
19341 pub fn insert_gutter_highlight<T: 'static>(
19342 &mut self,
19343 range: Range<Anchor>,
19344 color_fetcher: fn(&App) -> Hsla,
19345 cx: &mut Context<Self>,
19346 ) {
19347 let snapshot = self.buffer().read(cx).snapshot(cx);
19348 let mut highlights = self
19349 .gutter_highlights
19350 .remove(&TypeId::of::<T>())
19351 .map(|(_, highlights)| highlights)
19352 .unwrap_or_default();
19353 let ix = highlights.binary_search_by(|highlight| {
19354 Ordering::Equal
19355 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19356 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19357 });
19358 if let Err(ix) = ix {
19359 highlights.insert(ix, range);
19360 }
19361 self.gutter_highlights
19362 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19363 }
19364
19365 pub fn remove_gutter_highlights<T: 'static>(
19366 &mut self,
19367 ranges_to_remove: Vec<Range<Anchor>>,
19368 cx: &mut Context<Self>,
19369 ) {
19370 let snapshot = self.buffer().read(cx).snapshot(cx);
19371 let Some((color_fetcher, mut gutter_highlights)) =
19372 self.gutter_highlights.remove(&TypeId::of::<T>())
19373 else {
19374 return;
19375 };
19376 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19377 gutter_highlights.retain(|highlight| {
19378 while let Some(range_to_remove) = ranges_to_remove.peek() {
19379 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19380 Ordering::Less | Ordering::Equal => {
19381 ranges_to_remove.next();
19382 }
19383 Ordering::Greater => {
19384 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19385 Ordering::Less | Ordering::Equal => {
19386 return false;
19387 }
19388 Ordering::Greater => break,
19389 }
19390 }
19391 }
19392 }
19393
19394 true
19395 });
19396 self.gutter_highlights
19397 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19398 }
19399
19400 #[cfg(feature = "test-support")]
19401 pub fn all_text_highlights(
19402 &self,
19403 window: &mut Window,
19404 cx: &mut Context<Self>,
19405 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19406 let snapshot = self.snapshot(window, cx);
19407 self.display_map.update(cx, |display_map, _| {
19408 display_map
19409 .all_text_highlights()
19410 .map(|highlight| {
19411 let (style, ranges) = highlight.as_ref();
19412 (
19413 *style,
19414 ranges
19415 .iter()
19416 .map(|range| range.clone().to_display_points(&snapshot))
19417 .collect(),
19418 )
19419 })
19420 .collect()
19421 })
19422 }
19423
19424 #[cfg(feature = "test-support")]
19425 pub fn all_text_background_highlights(
19426 &self,
19427 window: &mut Window,
19428 cx: &mut Context<Self>,
19429 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19430 let snapshot = self.snapshot(window, cx);
19431 let buffer = &snapshot.buffer_snapshot;
19432 let start = buffer.anchor_before(0);
19433 let end = buffer.anchor_after(buffer.len());
19434 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19435 }
19436
19437 #[cfg(feature = "test-support")]
19438 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19439 let snapshot = self.buffer().read(cx).snapshot(cx);
19440
19441 let highlights = self
19442 .background_highlights
19443 .get(&HighlightKey::Type(TypeId::of::<
19444 items::BufferSearchHighlights,
19445 >()));
19446
19447 if let Some((_color, ranges)) = highlights {
19448 ranges
19449 .iter()
19450 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19451 .collect_vec()
19452 } else {
19453 vec![]
19454 }
19455 }
19456
19457 fn document_highlights_for_position<'a>(
19458 &'a self,
19459 position: Anchor,
19460 buffer: &'a MultiBufferSnapshot,
19461 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19462 let read_highlights = self
19463 .background_highlights
19464 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19465 .map(|h| &h.1);
19466 let write_highlights = self
19467 .background_highlights
19468 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19469 .map(|h| &h.1);
19470 let left_position = position.bias_left(buffer);
19471 let right_position = position.bias_right(buffer);
19472 read_highlights
19473 .into_iter()
19474 .chain(write_highlights)
19475 .flat_map(move |ranges| {
19476 let start_ix = match ranges.binary_search_by(|probe| {
19477 let cmp = probe.end.cmp(&left_position, buffer);
19478 if cmp.is_ge() {
19479 Ordering::Greater
19480 } else {
19481 Ordering::Less
19482 }
19483 }) {
19484 Ok(i) | Err(i) => i,
19485 };
19486
19487 ranges[start_ix..]
19488 .iter()
19489 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19490 })
19491 }
19492
19493 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19494 self.background_highlights
19495 .get(&HighlightKey::Type(TypeId::of::<T>()))
19496 .map_or(false, |(_, highlights)| !highlights.is_empty())
19497 }
19498
19499 pub fn background_highlights_in_range(
19500 &self,
19501 search_range: Range<Anchor>,
19502 display_snapshot: &DisplaySnapshot,
19503 theme: &Theme,
19504 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19505 let mut results = Vec::new();
19506 for (color_fetcher, ranges) in self.background_highlights.values() {
19507 let color = color_fetcher(theme);
19508 let start_ix = match ranges.binary_search_by(|probe| {
19509 let cmp = probe
19510 .end
19511 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19512 if cmp.is_gt() {
19513 Ordering::Greater
19514 } else {
19515 Ordering::Less
19516 }
19517 }) {
19518 Ok(i) | Err(i) => i,
19519 };
19520 for range in &ranges[start_ix..] {
19521 if range
19522 .start
19523 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19524 .is_ge()
19525 {
19526 break;
19527 }
19528
19529 let start = range.start.to_display_point(display_snapshot);
19530 let end = range.end.to_display_point(display_snapshot);
19531 results.push((start..end, color))
19532 }
19533 }
19534 results
19535 }
19536
19537 pub fn background_highlight_row_ranges<T: 'static>(
19538 &self,
19539 search_range: Range<Anchor>,
19540 display_snapshot: &DisplaySnapshot,
19541 count: usize,
19542 ) -> Vec<RangeInclusive<DisplayPoint>> {
19543 let mut results = Vec::new();
19544 let Some((_, ranges)) = self
19545 .background_highlights
19546 .get(&HighlightKey::Type(TypeId::of::<T>()))
19547 else {
19548 return vec![];
19549 };
19550
19551 let start_ix = match ranges.binary_search_by(|probe| {
19552 let cmp = probe
19553 .end
19554 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19555 if cmp.is_gt() {
19556 Ordering::Greater
19557 } else {
19558 Ordering::Less
19559 }
19560 }) {
19561 Ok(i) | Err(i) => i,
19562 };
19563 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19564 if let (Some(start_display), Some(end_display)) = (start, end) {
19565 results.push(
19566 start_display.to_display_point(display_snapshot)
19567 ..=end_display.to_display_point(display_snapshot),
19568 );
19569 }
19570 };
19571 let mut start_row: Option<Point> = None;
19572 let mut end_row: Option<Point> = None;
19573 if ranges.len() > count {
19574 return Vec::new();
19575 }
19576 for range in &ranges[start_ix..] {
19577 if range
19578 .start
19579 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19580 .is_ge()
19581 {
19582 break;
19583 }
19584 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19585 if let Some(current_row) = &end_row {
19586 if end.row == current_row.row {
19587 continue;
19588 }
19589 }
19590 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19591 if start_row.is_none() {
19592 assert_eq!(end_row, None);
19593 start_row = Some(start);
19594 end_row = Some(end);
19595 continue;
19596 }
19597 if let Some(current_end) = end_row.as_mut() {
19598 if start.row > current_end.row + 1 {
19599 push_region(start_row, end_row);
19600 start_row = Some(start);
19601 end_row = Some(end);
19602 } else {
19603 // Merge two hunks.
19604 *current_end = end;
19605 }
19606 } else {
19607 unreachable!();
19608 }
19609 }
19610 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19611 push_region(start_row, end_row);
19612 results
19613 }
19614
19615 pub fn gutter_highlights_in_range(
19616 &self,
19617 search_range: Range<Anchor>,
19618 display_snapshot: &DisplaySnapshot,
19619 cx: &App,
19620 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19621 let mut results = Vec::new();
19622 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19623 let color = color_fetcher(cx);
19624 let start_ix = match ranges.binary_search_by(|probe| {
19625 let cmp = probe
19626 .end
19627 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19628 if cmp.is_gt() {
19629 Ordering::Greater
19630 } else {
19631 Ordering::Less
19632 }
19633 }) {
19634 Ok(i) | Err(i) => i,
19635 };
19636 for range in &ranges[start_ix..] {
19637 if range
19638 .start
19639 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19640 .is_ge()
19641 {
19642 break;
19643 }
19644
19645 let start = range.start.to_display_point(display_snapshot);
19646 let end = range.end.to_display_point(display_snapshot);
19647 results.push((start..end, color))
19648 }
19649 }
19650 results
19651 }
19652
19653 /// Get the text ranges corresponding to the redaction query
19654 pub fn redacted_ranges(
19655 &self,
19656 search_range: Range<Anchor>,
19657 display_snapshot: &DisplaySnapshot,
19658 cx: &App,
19659 ) -> Vec<Range<DisplayPoint>> {
19660 display_snapshot
19661 .buffer_snapshot
19662 .redacted_ranges(search_range, |file| {
19663 if let Some(file) = file {
19664 file.is_private()
19665 && EditorSettings::get(
19666 Some(SettingsLocation {
19667 worktree_id: file.worktree_id(cx),
19668 path: file.path().as_ref(),
19669 }),
19670 cx,
19671 )
19672 .redact_private_values
19673 } else {
19674 false
19675 }
19676 })
19677 .map(|range| {
19678 range.start.to_display_point(display_snapshot)
19679 ..range.end.to_display_point(display_snapshot)
19680 })
19681 .collect()
19682 }
19683
19684 pub fn highlight_text_key<T: 'static>(
19685 &mut self,
19686 key: usize,
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(
19693 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19694 ranges,
19695 style,
19696 );
19697 });
19698 cx.notify();
19699 }
19700
19701 pub fn highlight_text<T: 'static>(
19702 &mut self,
19703 ranges: Vec<Range<Anchor>>,
19704 style: HighlightStyle,
19705 cx: &mut Context<Self>,
19706 ) {
19707 self.display_map.update(cx, |map, _| {
19708 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19709 });
19710 cx.notify();
19711 }
19712
19713 pub(crate) fn highlight_inlays<T: 'static>(
19714 &mut self,
19715 highlights: Vec<InlayHighlight>,
19716 style: HighlightStyle,
19717 cx: &mut Context<Self>,
19718 ) {
19719 self.display_map.update(cx, |map, _| {
19720 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19721 });
19722 cx.notify();
19723 }
19724
19725 pub fn text_highlights<'a, T: 'static>(
19726 &'a self,
19727 cx: &'a App,
19728 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19729 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19730 }
19731
19732 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19733 let cleared = self
19734 .display_map
19735 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19736 if cleared {
19737 cx.notify();
19738 }
19739 }
19740
19741 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19742 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19743 && self.focus_handle.is_focused(window)
19744 }
19745
19746 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19747 self.show_cursor_when_unfocused = is_enabled;
19748 cx.notify();
19749 }
19750
19751 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19752 cx.notify();
19753 }
19754
19755 fn on_debug_session_event(
19756 &mut self,
19757 _session: Entity<Session>,
19758 event: &SessionEvent,
19759 cx: &mut Context<Self>,
19760 ) {
19761 match event {
19762 SessionEvent::InvalidateInlineValue => {
19763 self.refresh_inline_values(cx);
19764 }
19765 _ => {}
19766 }
19767 }
19768
19769 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19770 let Some(project) = self.project.clone() else {
19771 return;
19772 };
19773
19774 if !self.inline_value_cache.enabled {
19775 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19776 self.splice_inlays(&inlays, Vec::new(), cx);
19777 return;
19778 }
19779
19780 let current_execution_position = self
19781 .highlighted_rows
19782 .get(&TypeId::of::<ActiveDebugLine>())
19783 .and_then(|lines| lines.last().map(|line| line.range.end));
19784
19785 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19786 let inline_values = editor
19787 .update(cx, |editor, cx| {
19788 let Some(current_execution_position) = current_execution_position else {
19789 return Some(Task::ready(Ok(Vec::new())));
19790 };
19791
19792 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19793 let snapshot = buffer.snapshot(cx);
19794
19795 let excerpt = snapshot.excerpt_containing(
19796 current_execution_position..current_execution_position,
19797 )?;
19798
19799 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19800 })?;
19801
19802 let range =
19803 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19804
19805 project.inline_values(buffer, range, cx)
19806 })
19807 .ok()
19808 .flatten()?
19809 .await
19810 .context("refreshing debugger inlays")
19811 .log_err()?;
19812
19813 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19814
19815 for (buffer_id, inline_value) in inline_values
19816 .into_iter()
19817 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19818 {
19819 buffer_inline_values
19820 .entry(buffer_id)
19821 .or_default()
19822 .push(inline_value);
19823 }
19824
19825 editor
19826 .update(cx, |editor, cx| {
19827 let snapshot = editor.buffer.read(cx).snapshot(cx);
19828 let mut new_inlays = Vec::default();
19829
19830 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19831 let buffer_id = buffer_snapshot.remote_id();
19832 buffer_inline_values
19833 .get(&buffer_id)
19834 .into_iter()
19835 .flatten()
19836 .for_each(|hint| {
19837 let inlay = Inlay::debugger(
19838 post_inc(&mut editor.next_inlay_id),
19839 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19840 hint.text(),
19841 );
19842 if !inlay.text.chars().contains(&'\n') {
19843 new_inlays.push(inlay);
19844 }
19845 });
19846 }
19847
19848 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19849 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19850
19851 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19852 })
19853 .ok()?;
19854 Some(())
19855 });
19856 }
19857
19858 fn on_buffer_event(
19859 &mut self,
19860 multibuffer: &Entity<MultiBuffer>,
19861 event: &multi_buffer::Event,
19862 window: &mut Window,
19863 cx: &mut Context<Self>,
19864 ) {
19865 match event {
19866 multi_buffer::Event::Edited {
19867 singleton_buffer_edited,
19868 edited_buffer,
19869 } => {
19870 self.scrollbar_marker_state.dirty = true;
19871 self.active_indent_guides_state.dirty = true;
19872 self.refresh_active_diagnostics(cx);
19873 self.refresh_code_actions(window, cx);
19874 self.refresh_selected_text_highlights(true, window, cx);
19875 self.refresh_single_line_folds(window, cx);
19876 refresh_matching_bracket_highlights(self, window, cx);
19877 if self.has_active_inline_completion() {
19878 self.update_visible_inline_completion(window, cx);
19879 }
19880 if let Some(project) = self.project.as_ref() {
19881 if let Some(edited_buffer) = edited_buffer {
19882 project.update(cx, |project, cx| {
19883 self.registered_buffers
19884 .entry(edited_buffer.read(cx).remote_id())
19885 .or_insert_with(|| {
19886 project
19887 .register_buffer_with_language_servers(&edited_buffer, cx)
19888 });
19889 });
19890 }
19891 }
19892 cx.emit(EditorEvent::BufferEdited);
19893 cx.emit(SearchEvent::MatchesInvalidated);
19894
19895 if let Some(buffer) = edited_buffer {
19896 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19897 }
19898
19899 if *singleton_buffer_edited {
19900 if let Some(buffer) = edited_buffer {
19901 if buffer.read(cx).file().is_none() {
19902 cx.emit(EditorEvent::TitleChanged);
19903 }
19904 }
19905 if let Some(project) = &self.project {
19906 #[allow(clippy::mutable_key_type)]
19907 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19908 multibuffer
19909 .all_buffers()
19910 .into_iter()
19911 .filter_map(|buffer| {
19912 buffer.update(cx, |buffer, cx| {
19913 let language = buffer.language()?;
19914 let should_discard = project.update(cx, |project, cx| {
19915 project.is_local()
19916 && !project.has_language_servers_for(buffer, cx)
19917 });
19918 should_discard.not().then_some(language.clone())
19919 })
19920 })
19921 .collect::<HashSet<_>>()
19922 });
19923 if !languages_affected.is_empty() {
19924 self.refresh_inlay_hints(
19925 InlayHintRefreshReason::BufferEdited(languages_affected),
19926 cx,
19927 );
19928 }
19929 }
19930 }
19931
19932 let Some(project) = &self.project else { return };
19933 let (telemetry, is_via_ssh) = {
19934 let project = project.read(cx);
19935 let telemetry = project.client().telemetry().clone();
19936 let is_via_ssh = project.is_via_ssh();
19937 (telemetry, is_via_ssh)
19938 };
19939 refresh_linked_ranges(self, window, cx);
19940 telemetry.log_edit_event("editor", is_via_ssh);
19941 }
19942 multi_buffer::Event::ExcerptsAdded {
19943 buffer,
19944 predecessor,
19945 excerpts,
19946 } => {
19947 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19948 let buffer_id = buffer.read(cx).remote_id();
19949 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19950 if let Some(project) = &self.project {
19951 update_uncommitted_diff_for_buffer(
19952 cx.entity(),
19953 project,
19954 [buffer.clone()],
19955 self.buffer.clone(),
19956 cx,
19957 )
19958 .detach();
19959 }
19960 }
19961 self.update_lsp_data(false, Some(buffer_id), window, cx);
19962 cx.emit(EditorEvent::ExcerptsAdded {
19963 buffer: buffer.clone(),
19964 predecessor: *predecessor,
19965 excerpts: excerpts.clone(),
19966 });
19967 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19968 }
19969 multi_buffer::Event::ExcerptsRemoved {
19970 ids,
19971 removed_buffer_ids,
19972 } => {
19973 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19974 let buffer = self.buffer.read(cx);
19975 self.registered_buffers
19976 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19977 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19978 cx.emit(EditorEvent::ExcerptsRemoved {
19979 ids: ids.clone(),
19980 removed_buffer_ids: removed_buffer_ids.clone(),
19981 });
19982 }
19983 multi_buffer::Event::ExcerptsEdited {
19984 excerpt_ids,
19985 buffer_ids,
19986 } => {
19987 self.display_map.update(cx, |map, cx| {
19988 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19989 });
19990 cx.emit(EditorEvent::ExcerptsEdited {
19991 ids: excerpt_ids.clone(),
19992 });
19993 }
19994 multi_buffer::Event::ExcerptsExpanded { ids } => {
19995 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19996 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19997 }
19998 multi_buffer::Event::Reparsed(buffer_id) => {
19999 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20000 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20001
20002 cx.emit(EditorEvent::Reparsed(*buffer_id));
20003 }
20004 multi_buffer::Event::DiffHunksToggled => {
20005 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20006 }
20007 multi_buffer::Event::LanguageChanged(buffer_id) => {
20008 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20009 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20010 cx.emit(EditorEvent::Reparsed(*buffer_id));
20011 cx.notify();
20012 }
20013 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20014 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20015 multi_buffer::Event::FileHandleChanged
20016 | multi_buffer::Event::Reloaded
20017 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20018 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20019 multi_buffer::Event::DiagnosticsUpdated => {
20020 self.update_diagnostics_state(window, cx);
20021 }
20022 _ => {}
20023 };
20024 }
20025
20026 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20027 if !self.diagnostics_enabled() {
20028 return;
20029 }
20030 self.refresh_active_diagnostics(cx);
20031 self.refresh_inline_diagnostics(true, window, cx);
20032 self.scrollbar_marker_state.dirty = true;
20033 cx.notify();
20034 }
20035
20036 pub fn start_temporary_diff_override(&mut self) {
20037 self.load_diff_task.take();
20038 self.temporary_diff_override = true;
20039 }
20040
20041 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20042 self.temporary_diff_override = false;
20043 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20044 self.buffer.update(cx, |buffer, cx| {
20045 buffer.set_all_diff_hunks_collapsed(cx);
20046 });
20047
20048 if let Some(project) = self.project.clone() {
20049 self.load_diff_task = Some(
20050 update_uncommitted_diff_for_buffer(
20051 cx.entity(),
20052 &project,
20053 self.buffer.read(cx).all_buffers(),
20054 self.buffer.clone(),
20055 cx,
20056 )
20057 .shared(),
20058 );
20059 }
20060 }
20061
20062 fn on_display_map_changed(
20063 &mut self,
20064 _: Entity<DisplayMap>,
20065 _: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 cx.notify();
20069 }
20070
20071 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20072 if self.diagnostics_enabled() {
20073 let new_severity = EditorSettings::get_global(cx)
20074 .diagnostics_max_severity
20075 .unwrap_or(DiagnosticSeverity::Hint);
20076 self.set_max_diagnostics_severity(new_severity, cx);
20077 }
20078 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20079 self.update_edit_prediction_settings(cx);
20080 self.refresh_inline_completion(true, false, window, cx);
20081 self.refresh_inline_values(cx);
20082 self.refresh_inlay_hints(
20083 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20084 self.selections.newest_anchor().head(),
20085 &self.buffer.read(cx).snapshot(cx),
20086 cx,
20087 )),
20088 cx,
20089 );
20090
20091 let old_cursor_shape = self.cursor_shape;
20092
20093 {
20094 let editor_settings = EditorSettings::get_global(cx);
20095 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20096 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20097 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20098 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20099 }
20100
20101 if old_cursor_shape != self.cursor_shape {
20102 cx.emit(EditorEvent::CursorShapeChanged);
20103 }
20104
20105 let project_settings = ProjectSettings::get_global(cx);
20106 self.serialize_dirty_buffers =
20107 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20108
20109 if self.mode.is_full() {
20110 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20111 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20112 if self.show_inline_diagnostics != show_inline_diagnostics {
20113 self.show_inline_diagnostics = show_inline_diagnostics;
20114 self.refresh_inline_diagnostics(false, window, cx);
20115 }
20116
20117 if self.git_blame_inline_enabled != inline_blame_enabled {
20118 self.toggle_git_blame_inline_internal(false, window, cx);
20119 }
20120
20121 let minimap_settings = EditorSettings::get_global(cx).minimap;
20122 if self.minimap_visibility != MinimapVisibility::Disabled {
20123 if self.minimap_visibility.settings_visibility()
20124 != minimap_settings.minimap_enabled()
20125 {
20126 self.set_minimap_visibility(
20127 MinimapVisibility::for_mode(self.mode(), cx),
20128 window,
20129 cx,
20130 );
20131 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20132 minimap_entity.update(cx, |minimap_editor, cx| {
20133 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20134 })
20135 }
20136 }
20137 }
20138
20139 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20140 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20141 }) {
20142 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20143 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20144 }
20145 self.refresh_colors(false, None, window, cx);
20146 }
20147
20148 cx.notify();
20149 }
20150
20151 pub fn set_searchable(&mut self, searchable: bool) {
20152 self.searchable = searchable;
20153 }
20154
20155 pub fn searchable(&self) -> bool {
20156 self.searchable
20157 }
20158
20159 fn open_proposed_changes_editor(
20160 &mut self,
20161 _: &OpenProposedChangesEditor,
20162 window: &mut Window,
20163 cx: &mut Context<Self>,
20164 ) {
20165 let Some(workspace) = self.workspace() else {
20166 cx.propagate();
20167 return;
20168 };
20169
20170 let selections = self.selections.all::<usize>(cx);
20171 let multi_buffer = self.buffer.read(cx);
20172 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20173 let mut new_selections_by_buffer = HashMap::default();
20174 for selection in selections {
20175 for (buffer, range, _) in
20176 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20177 {
20178 let mut range = range.to_point(buffer);
20179 range.start.column = 0;
20180 range.end.column = buffer.line_len(range.end.row);
20181 new_selections_by_buffer
20182 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20183 .or_insert(Vec::new())
20184 .push(range)
20185 }
20186 }
20187
20188 let proposed_changes_buffers = new_selections_by_buffer
20189 .into_iter()
20190 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20191 .collect::<Vec<_>>();
20192 let proposed_changes_editor = cx.new(|cx| {
20193 ProposedChangesEditor::new(
20194 "Proposed changes",
20195 proposed_changes_buffers,
20196 self.project.clone(),
20197 window,
20198 cx,
20199 )
20200 });
20201
20202 window.defer(cx, move |window, cx| {
20203 workspace.update(cx, |workspace, cx| {
20204 workspace.active_pane().update(cx, |pane, cx| {
20205 pane.add_item(
20206 Box::new(proposed_changes_editor),
20207 true,
20208 true,
20209 None,
20210 window,
20211 cx,
20212 );
20213 });
20214 });
20215 });
20216 }
20217
20218 pub fn open_excerpts_in_split(
20219 &mut self,
20220 _: &OpenExcerptsSplit,
20221 window: &mut Window,
20222 cx: &mut Context<Self>,
20223 ) {
20224 self.open_excerpts_common(None, true, window, cx)
20225 }
20226
20227 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20228 self.open_excerpts_common(None, false, window, cx)
20229 }
20230
20231 fn open_excerpts_common(
20232 &mut self,
20233 jump_data: Option<JumpData>,
20234 split: bool,
20235 window: &mut Window,
20236 cx: &mut Context<Self>,
20237 ) {
20238 let Some(workspace) = self.workspace() else {
20239 cx.propagate();
20240 return;
20241 };
20242
20243 if self.buffer.read(cx).is_singleton() {
20244 cx.propagate();
20245 return;
20246 }
20247
20248 let mut new_selections_by_buffer = HashMap::default();
20249 match &jump_data {
20250 Some(JumpData::MultiBufferPoint {
20251 excerpt_id,
20252 position,
20253 anchor,
20254 line_offset_from_top,
20255 }) => {
20256 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20257 if let Some(buffer) = multi_buffer_snapshot
20258 .buffer_id_for_excerpt(*excerpt_id)
20259 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20260 {
20261 let buffer_snapshot = buffer.read(cx).snapshot();
20262 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20263 language::ToPoint::to_point(anchor, &buffer_snapshot)
20264 } else {
20265 buffer_snapshot.clip_point(*position, Bias::Left)
20266 };
20267 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20268 new_selections_by_buffer.insert(
20269 buffer,
20270 (
20271 vec![jump_to_offset..jump_to_offset],
20272 Some(*line_offset_from_top),
20273 ),
20274 );
20275 }
20276 }
20277 Some(JumpData::MultiBufferRow {
20278 row,
20279 line_offset_from_top,
20280 }) => {
20281 let point = MultiBufferPoint::new(row.0, 0);
20282 if let Some((buffer, buffer_point, _)) =
20283 self.buffer.read(cx).point_to_buffer_point(point, cx)
20284 {
20285 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20286 new_selections_by_buffer
20287 .entry(buffer)
20288 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20289 .0
20290 .push(buffer_offset..buffer_offset)
20291 }
20292 }
20293 None => {
20294 let selections = self.selections.all::<usize>(cx);
20295 let multi_buffer = self.buffer.read(cx);
20296 for selection in selections {
20297 for (snapshot, range, _, anchor) in multi_buffer
20298 .snapshot(cx)
20299 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20300 {
20301 if let Some(anchor) = anchor {
20302 // selection is in a deleted hunk
20303 let Some(buffer_id) = anchor.buffer_id else {
20304 continue;
20305 };
20306 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20307 continue;
20308 };
20309 let offset = text::ToOffset::to_offset(
20310 &anchor.text_anchor,
20311 &buffer_handle.read(cx).snapshot(),
20312 );
20313 let range = offset..offset;
20314 new_selections_by_buffer
20315 .entry(buffer_handle)
20316 .or_insert((Vec::new(), None))
20317 .0
20318 .push(range)
20319 } else {
20320 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20321 else {
20322 continue;
20323 };
20324 new_selections_by_buffer
20325 .entry(buffer_handle)
20326 .or_insert((Vec::new(), None))
20327 .0
20328 .push(range)
20329 }
20330 }
20331 }
20332 }
20333 }
20334
20335 new_selections_by_buffer
20336 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20337
20338 if new_selections_by_buffer.is_empty() {
20339 return;
20340 }
20341
20342 // We defer the pane interaction because we ourselves are a workspace item
20343 // and activating a new item causes the pane to call a method on us reentrantly,
20344 // which panics if we're on the stack.
20345 window.defer(cx, move |window, cx| {
20346 workspace.update(cx, |workspace, cx| {
20347 let pane = if split {
20348 workspace.adjacent_pane(window, cx)
20349 } else {
20350 workspace.active_pane().clone()
20351 };
20352
20353 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20354 let editor = buffer
20355 .read(cx)
20356 .file()
20357 .is_none()
20358 .then(|| {
20359 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20360 // so `workspace.open_project_item` will never find them, always opening a new editor.
20361 // Instead, we try to activate the existing editor in the pane first.
20362 let (editor, pane_item_index) =
20363 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20364 let editor = item.downcast::<Editor>()?;
20365 let singleton_buffer =
20366 editor.read(cx).buffer().read(cx).as_singleton()?;
20367 if singleton_buffer == buffer {
20368 Some((editor, i))
20369 } else {
20370 None
20371 }
20372 })?;
20373 pane.update(cx, |pane, cx| {
20374 pane.activate_item(pane_item_index, true, true, window, cx)
20375 });
20376 Some(editor)
20377 })
20378 .flatten()
20379 .unwrap_or_else(|| {
20380 workspace.open_project_item::<Self>(
20381 pane.clone(),
20382 buffer,
20383 true,
20384 true,
20385 window,
20386 cx,
20387 )
20388 });
20389
20390 editor.update(cx, |editor, cx| {
20391 let autoscroll = match scroll_offset {
20392 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20393 None => Autoscroll::newest(),
20394 };
20395 let nav_history = editor.nav_history.take();
20396 editor.change_selections(
20397 SelectionEffects::scroll(autoscroll),
20398 window,
20399 cx,
20400 |s| {
20401 s.select_ranges(ranges);
20402 },
20403 );
20404 editor.nav_history = nav_history;
20405 });
20406 }
20407 })
20408 });
20409 }
20410
20411 // For now, don't allow opening excerpts in buffers that aren't backed by
20412 // regular project files.
20413 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20414 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20415 }
20416
20417 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20418 let snapshot = self.buffer.read(cx).read(cx);
20419 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20420 Some(
20421 ranges
20422 .iter()
20423 .map(move |range| {
20424 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20425 })
20426 .collect(),
20427 )
20428 }
20429
20430 fn selection_replacement_ranges(
20431 &self,
20432 range: Range<OffsetUtf16>,
20433 cx: &mut App,
20434 ) -> Vec<Range<OffsetUtf16>> {
20435 let selections = self.selections.all::<OffsetUtf16>(cx);
20436 let newest_selection = selections
20437 .iter()
20438 .max_by_key(|selection| selection.id)
20439 .unwrap();
20440 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20441 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20442 let snapshot = self.buffer.read(cx).read(cx);
20443 selections
20444 .into_iter()
20445 .map(|mut selection| {
20446 selection.start.0 =
20447 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20448 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20449 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20450 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20451 })
20452 .collect()
20453 }
20454
20455 fn report_editor_event(
20456 &self,
20457 event_type: &'static str,
20458 file_extension: Option<String>,
20459 cx: &App,
20460 ) {
20461 if cfg!(any(test, feature = "test-support")) {
20462 return;
20463 }
20464
20465 let Some(project) = &self.project else { return };
20466
20467 // If None, we are in a file without an extension
20468 let file = self
20469 .buffer
20470 .read(cx)
20471 .as_singleton()
20472 .and_then(|b| b.read(cx).file());
20473 let file_extension = file_extension.or(file
20474 .as_ref()
20475 .and_then(|file| Path::new(file.file_name(cx)).extension())
20476 .and_then(|e| e.to_str())
20477 .map(|a| a.to_string()));
20478
20479 let vim_mode = vim_enabled(cx);
20480
20481 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20482 let copilot_enabled = edit_predictions_provider
20483 == language::language_settings::EditPredictionProvider::Copilot;
20484 let copilot_enabled_for_language = self
20485 .buffer
20486 .read(cx)
20487 .language_settings(cx)
20488 .show_edit_predictions;
20489
20490 let project = project.read(cx);
20491 telemetry::event!(
20492 event_type,
20493 file_extension,
20494 vim_mode,
20495 copilot_enabled,
20496 copilot_enabled_for_language,
20497 edit_predictions_provider,
20498 is_via_ssh = project.is_via_ssh(),
20499 );
20500 }
20501
20502 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20503 /// with each line being an array of {text, highlight} objects.
20504 fn copy_highlight_json(
20505 &mut self,
20506 _: &CopyHighlightJson,
20507 window: &mut Window,
20508 cx: &mut Context<Self>,
20509 ) {
20510 #[derive(Serialize)]
20511 struct Chunk<'a> {
20512 text: String,
20513 highlight: Option<&'a str>,
20514 }
20515
20516 let snapshot = self.buffer.read(cx).snapshot(cx);
20517 let range = self
20518 .selected_text_range(false, window, cx)
20519 .and_then(|selection| {
20520 if selection.range.is_empty() {
20521 None
20522 } else {
20523 Some(selection.range)
20524 }
20525 })
20526 .unwrap_or_else(|| 0..snapshot.len());
20527
20528 let chunks = snapshot.chunks(range, true);
20529 let mut lines = Vec::new();
20530 let mut line: VecDeque<Chunk> = VecDeque::new();
20531
20532 let Some(style) = self.style.as_ref() else {
20533 return;
20534 };
20535
20536 for chunk in chunks {
20537 let highlight = chunk
20538 .syntax_highlight_id
20539 .and_then(|id| id.name(&style.syntax));
20540 let mut chunk_lines = chunk.text.split('\n').peekable();
20541 while let Some(text) = chunk_lines.next() {
20542 let mut merged_with_last_token = false;
20543 if let Some(last_token) = line.back_mut() {
20544 if last_token.highlight == highlight {
20545 last_token.text.push_str(text);
20546 merged_with_last_token = true;
20547 }
20548 }
20549
20550 if !merged_with_last_token {
20551 line.push_back(Chunk {
20552 text: text.into(),
20553 highlight,
20554 });
20555 }
20556
20557 if chunk_lines.peek().is_some() {
20558 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20559 line.pop_front();
20560 }
20561 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20562 line.pop_back();
20563 }
20564
20565 lines.push(mem::take(&mut line));
20566 }
20567 }
20568 }
20569
20570 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20571 return;
20572 };
20573 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20574 }
20575
20576 pub fn open_context_menu(
20577 &mut self,
20578 _: &OpenContextMenu,
20579 window: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) {
20582 self.request_autoscroll(Autoscroll::newest(), cx);
20583 let position = self.selections.newest_display(cx).start;
20584 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20585 }
20586
20587 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20588 &self.inlay_hint_cache
20589 }
20590
20591 pub fn replay_insert_event(
20592 &mut self,
20593 text: &str,
20594 relative_utf16_range: Option<Range<isize>>,
20595 window: &mut Window,
20596 cx: &mut Context<Self>,
20597 ) {
20598 if !self.input_enabled {
20599 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20600 return;
20601 }
20602 if let Some(relative_utf16_range) = relative_utf16_range {
20603 let selections = self.selections.all::<OffsetUtf16>(cx);
20604 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20605 let new_ranges = selections.into_iter().map(|range| {
20606 let start = OffsetUtf16(
20607 range
20608 .head()
20609 .0
20610 .saturating_add_signed(relative_utf16_range.start),
20611 );
20612 let end = OffsetUtf16(
20613 range
20614 .head()
20615 .0
20616 .saturating_add_signed(relative_utf16_range.end),
20617 );
20618 start..end
20619 });
20620 s.select_ranges(new_ranges);
20621 });
20622 }
20623
20624 self.handle_input(text, window, cx);
20625 }
20626
20627 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20628 let Some(provider) = self.semantics_provider.as_ref() else {
20629 return false;
20630 };
20631
20632 let mut supports = false;
20633 self.buffer().update(cx, |this, cx| {
20634 this.for_each_buffer(|buffer| {
20635 supports |= provider.supports_inlay_hints(buffer, cx);
20636 });
20637 });
20638
20639 supports
20640 }
20641
20642 pub fn is_focused(&self, window: &Window) -> bool {
20643 self.focus_handle.is_focused(window)
20644 }
20645
20646 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20647 cx.emit(EditorEvent::Focused);
20648
20649 if let Some(descendant) = self
20650 .last_focused_descendant
20651 .take()
20652 .and_then(|descendant| descendant.upgrade())
20653 {
20654 window.focus(&descendant);
20655 } else {
20656 if let Some(blame) = self.blame.as_ref() {
20657 blame.update(cx, GitBlame::focus)
20658 }
20659
20660 self.blink_manager.update(cx, BlinkManager::enable);
20661 self.show_cursor_names(window, cx);
20662 self.buffer.update(cx, |buffer, cx| {
20663 buffer.finalize_last_transaction(cx);
20664 if self.leader_id.is_none() {
20665 buffer.set_active_selections(
20666 &self.selections.disjoint_anchors(),
20667 self.selections.line_mode,
20668 self.cursor_shape,
20669 cx,
20670 );
20671 }
20672 });
20673 }
20674 }
20675
20676 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20677 cx.emit(EditorEvent::FocusedIn)
20678 }
20679
20680 fn handle_focus_out(
20681 &mut self,
20682 event: FocusOutEvent,
20683 _window: &mut Window,
20684 cx: &mut Context<Self>,
20685 ) {
20686 if event.blurred != self.focus_handle {
20687 self.last_focused_descendant = Some(event.blurred);
20688 }
20689 self.selection_drag_state = SelectionDragState::None;
20690 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20691 }
20692
20693 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20694 self.blink_manager.update(cx, BlinkManager::disable);
20695 self.buffer
20696 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20697
20698 if let Some(blame) = self.blame.as_ref() {
20699 blame.update(cx, GitBlame::blur)
20700 }
20701 if !self.hover_state.focused(window, cx) {
20702 hide_hover(self, cx);
20703 }
20704 if !self
20705 .context_menu
20706 .borrow()
20707 .as_ref()
20708 .is_some_and(|context_menu| context_menu.focused(window, cx))
20709 {
20710 self.hide_context_menu(window, cx);
20711 }
20712 self.discard_inline_completion(false, cx);
20713 cx.emit(EditorEvent::Blurred);
20714 cx.notify();
20715 }
20716
20717 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20718 let mut pending: String = window
20719 .pending_input_keystrokes()
20720 .into_iter()
20721 .flatten()
20722 .filter_map(|keystroke| {
20723 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20724 keystroke.key_char.clone()
20725 } else {
20726 None
20727 }
20728 })
20729 .collect();
20730
20731 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20732 pending = "".to_string();
20733 }
20734
20735 let existing_pending = self
20736 .text_highlights::<PendingInput>(cx)
20737 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20738 if existing_pending.is_none() && pending.is_empty() {
20739 return;
20740 }
20741 let transaction =
20742 self.transact(window, cx, |this, window, cx| {
20743 let selections = this.selections.all::<usize>(cx);
20744 let edits = selections
20745 .iter()
20746 .map(|selection| (selection.end..selection.end, pending.clone()));
20747 this.edit(edits, cx);
20748 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20749 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20750 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20751 }));
20752 });
20753 if let Some(existing_ranges) = existing_pending {
20754 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20755 this.edit(edits, cx);
20756 }
20757 });
20758
20759 let snapshot = self.snapshot(window, cx);
20760 let ranges = self
20761 .selections
20762 .all::<usize>(cx)
20763 .into_iter()
20764 .map(|selection| {
20765 snapshot.buffer_snapshot.anchor_after(selection.end)
20766 ..snapshot
20767 .buffer_snapshot
20768 .anchor_before(selection.end + pending.len())
20769 })
20770 .collect();
20771
20772 if pending.is_empty() {
20773 self.clear_highlights::<PendingInput>(cx);
20774 } else {
20775 self.highlight_text::<PendingInput>(
20776 ranges,
20777 HighlightStyle {
20778 underline: Some(UnderlineStyle {
20779 thickness: px(1.),
20780 color: None,
20781 wavy: false,
20782 }),
20783 ..Default::default()
20784 },
20785 cx,
20786 );
20787 }
20788
20789 self.ime_transaction = self.ime_transaction.or(transaction);
20790 if let Some(transaction) = self.ime_transaction {
20791 self.buffer.update(cx, |buffer, cx| {
20792 buffer.group_until_transaction(transaction, cx);
20793 });
20794 }
20795
20796 if self.text_highlights::<PendingInput>(cx).is_none() {
20797 self.ime_transaction.take();
20798 }
20799 }
20800
20801 pub fn register_action_renderer(
20802 &mut self,
20803 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20804 ) -> Subscription {
20805 let id = self.next_editor_action_id.post_inc();
20806 self.editor_actions
20807 .borrow_mut()
20808 .insert(id, Box::new(listener));
20809
20810 let editor_actions = self.editor_actions.clone();
20811 Subscription::new(move || {
20812 editor_actions.borrow_mut().remove(&id);
20813 })
20814 }
20815
20816 pub fn register_action<A: Action>(
20817 &mut self,
20818 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20819 ) -> Subscription {
20820 let id = self.next_editor_action_id.post_inc();
20821 let listener = Arc::new(listener);
20822 self.editor_actions.borrow_mut().insert(
20823 id,
20824 Box::new(move |_, window, _| {
20825 let listener = listener.clone();
20826 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20827 let action = action.downcast_ref().unwrap();
20828 if phase == DispatchPhase::Bubble {
20829 listener(action, window, cx)
20830 }
20831 })
20832 }),
20833 );
20834
20835 let editor_actions = self.editor_actions.clone();
20836 Subscription::new(move || {
20837 editor_actions.borrow_mut().remove(&id);
20838 })
20839 }
20840
20841 pub fn file_header_size(&self) -> u32 {
20842 FILE_HEADER_HEIGHT
20843 }
20844
20845 pub fn restore(
20846 &mut self,
20847 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20848 window: &mut Window,
20849 cx: &mut Context<Self>,
20850 ) {
20851 let workspace = self.workspace();
20852 let project = self.project.as_ref();
20853 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20854 let mut tasks = Vec::new();
20855 for (buffer_id, changes) in revert_changes {
20856 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20857 buffer.update(cx, |buffer, cx| {
20858 buffer.edit(
20859 changes
20860 .into_iter()
20861 .map(|(range, text)| (range, text.to_string())),
20862 None,
20863 cx,
20864 );
20865 });
20866
20867 if let Some(project) =
20868 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20869 {
20870 project.update(cx, |project, cx| {
20871 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20872 })
20873 }
20874 }
20875 }
20876 tasks
20877 });
20878 cx.spawn_in(window, async move |_, cx| {
20879 for (buffer, task) in save_tasks {
20880 let result = task.await;
20881 if result.is_err() {
20882 let Some(path) = buffer
20883 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20884 .ok()
20885 else {
20886 continue;
20887 };
20888 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20889 let Some(task) = cx
20890 .update_window_entity(&workspace, |workspace, window, cx| {
20891 workspace
20892 .open_path_preview(path, None, false, false, false, window, cx)
20893 })
20894 .ok()
20895 else {
20896 continue;
20897 };
20898 task.await.log_err();
20899 }
20900 }
20901 }
20902 })
20903 .detach();
20904 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20905 selections.refresh()
20906 });
20907 }
20908
20909 pub fn to_pixel_point(
20910 &self,
20911 source: multi_buffer::Anchor,
20912 editor_snapshot: &EditorSnapshot,
20913 window: &mut Window,
20914 ) -> Option<gpui::Point<Pixels>> {
20915 let source_point = source.to_display_point(editor_snapshot);
20916 self.display_to_pixel_point(source_point, editor_snapshot, window)
20917 }
20918
20919 pub fn display_to_pixel_point(
20920 &self,
20921 source: DisplayPoint,
20922 editor_snapshot: &EditorSnapshot,
20923 window: &mut Window,
20924 ) -> Option<gpui::Point<Pixels>> {
20925 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20926 let text_layout_details = self.text_layout_details(window);
20927 let scroll_top = text_layout_details
20928 .scroll_anchor
20929 .scroll_position(editor_snapshot)
20930 .y;
20931
20932 if source.row().as_f32() < scroll_top.floor() {
20933 return None;
20934 }
20935 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20936 let source_y = line_height * (source.row().as_f32() - scroll_top);
20937 Some(gpui::Point::new(source_x, source_y))
20938 }
20939
20940 pub fn has_visible_completions_menu(&self) -> bool {
20941 !self.edit_prediction_preview_is_active()
20942 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20943 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20944 })
20945 }
20946
20947 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20948 if self.mode.is_minimap() {
20949 return;
20950 }
20951 self.addons
20952 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20953 }
20954
20955 pub fn unregister_addon<T: Addon>(&mut self) {
20956 self.addons.remove(&std::any::TypeId::of::<T>());
20957 }
20958
20959 pub fn addon<T: Addon>(&self) -> Option<&T> {
20960 let type_id = std::any::TypeId::of::<T>();
20961 self.addons
20962 .get(&type_id)
20963 .and_then(|item| item.to_any().downcast_ref::<T>())
20964 }
20965
20966 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20967 let type_id = std::any::TypeId::of::<T>();
20968 self.addons
20969 .get_mut(&type_id)
20970 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20971 }
20972
20973 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20974 let text_layout_details = self.text_layout_details(window);
20975 let style = &text_layout_details.editor_style;
20976 let font_id = window.text_system().resolve_font(&style.text.font());
20977 let font_size = style.text.font_size.to_pixels(window.rem_size());
20978 let line_height = style.text.line_height_in_pixels(window.rem_size());
20979 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20980 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20981
20982 CharacterDimensions {
20983 em_width,
20984 em_advance,
20985 line_height,
20986 }
20987 }
20988
20989 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20990 self.load_diff_task.clone()
20991 }
20992
20993 fn read_metadata_from_db(
20994 &mut self,
20995 item_id: u64,
20996 workspace_id: WorkspaceId,
20997 window: &mut Window,
20998 cx: &mut Context<Editor>,
20999 ) {
21000 if self.is_singleton(cx)
21001 && !self.mode.is_minimap()
21002 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21003 {
21004 let buffer_snapshot = OnceCell::new();
21005
21006 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21007 if !folds.is_empty() {
21008 let snapshot =
21009 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21010 self.fold_ranges(
21011 folds
21012 .into_iter()
21013 .map(|(start, end)| {
21014 snapshot.clip_offset(start, Bias::Left)
21015 ..snapshot.clip_offset(end, Bias::Right)
21016 })
21017 .collect(),
21018 false,
21019 window,
21020 cx,
21021 );
21022 }
21023 }
21024
21025 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21026 if !selections.is_empty() {
21027 let snapshot =
21028 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21029 // skip adding the initial selection to selection history
21030 self.selection_history.mode = SelectionHistoryMode::Skipping;
21031 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21032 s.select_ranges(selections.into_iter().map(|(start, end)| {
21033 snapshot.clip_offset(start, Bias::Left)
21034 ..snapshot.clip_offset(end, Bias::Right)
21035 }));
21036 });
21037 self.selection_history.mode = SelectionHistoryMode::Normal;
21038 }
21039 };
21040 }
21041
21042 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21043 }
21044
21045 fn update_lsp_data(
21046 &mut self,
21047 ignore_cache: bool,
21048 for_buffer: Option<BufferId>,
21049 window: &mut Window,
21050 cx: &mut Context<'_, Self>,
21051 ) {
21052 self.pull_diagnostics(for_buffer, window, cx);
21053 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21054 }
21055}
21056
21057fn vim_enabled(cx: &App) -> bool {
21058 cx.global::<SettingsStore>()
21059 .raw_user_settings()
21060 .get("vim_mode")
21061 == Some(&serde_json::Value::Bool(true))
21062}
21063
21064fn process_completion_for_edit(
21065 completion: &Completion,
21066 intent: CompletionIntent,
21067 buffer: &Entity<Buffer>,
21068 cursor_position: &text::Anchor,
21069 cx: &mut Context<Editor>,
21070) -> CompletionEdit {
21071 let buffer = buffer.read(cx);
21072 let buffer_snapshot = buffer.snapshot();
21073 let (snippet, new_text) = if completion.is_snippet() {
21074 // Workaround for typescript language server issues so that methods don't expand within
21075 // strings and functions with type expressions. The previous point is used because the query
21076 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21077 let mut snippet_source = completion.new_text.clone();
21078 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21079 previous_point.column = previous_point.column.saturating_sub(1);
21080 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21081 if scope.prefers_label_for_snippet_in_completion() {
21082 if let Some(label) = completion.label() {
21083 if matches!(
21084 completion.kind(),
21085 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21086 ) {
21087 snippet_source = label;
21088 }
21089 }
21090 }
21091 }
21092 match Snippet::parse(&snippet_source).log_err() {
21093 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21094 None => (None, completion.new_text.clone()),
21095 }
21096 } else {
21097 (None, completion.new_text.clone())
21098 };
21099
21100 let mut range_to_replace = {
21101 let replace_range = &completion.replace_range;
21102 if let CompletionSource::Lsp {
21103 insert_range: Some(insert_range),
21104 ..
21105 } = &completion.source
21106 {
21107 debug_assert_eq!(
21108 insert_range.start, replace_range.start,
21109 "insert_range and replace_range should start at the same position"
21110 );
21111 debug_assert!(
21112 insert_range
21113 .start
21114 .cmp(&cursor_position, &buffer_snapshot)
21115 .is_le(),
21116 "insert_range should start before or at cursor position"
21117 );
21118 debug_assert!(
21119 replace_range
21120 .start
21121 .cmp(&cursor_position, &buffer_snapshot)
21122 .is_le(),
21123 "replace_range should start before or at cursor position"
21124 );
21125
21126 let should_replace = match intent {
21127 CompletionIntent::CompleteWithInsert => false,
21128 CompletionIntent::CompleteWithReplace => true,
21129 CompletionIntent::Complete | CompletionIntent::Compose => {
21130 let insert_mode =
21131 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21132 .completions
21133 .lsp_insert_mode;
21134 match insert_mode {
21135 LspInsertMode::Insert => false,
21136 LspInsertMode::Replace => true,
21137 LspInsertMode::ReplaceSubsequence => {
21138 let mut text_to_replace = buffer.chars_for_range(
21139 buffer.anchor_before(replace_range.start)
21140 ..buffer.anchor_after(replace_range.end),
21141 );
21142 let mut current_needle = text_to_replace.next();
21143 for haystack_ch in completion.label.text.chars() {
21144 if let Some(needle_ch) = current_needle {
21145 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21146 current_needle = text_to_replace.next();
21147 }
21148 }
21149 }
21150 current_needle.is_none()
21151 }
21152 LspInsertMode::ReplaceSuffix => {
21153 if replace_range
21154 .end
21155 .cmp(&cursor_position, &buffer_snapshot)
21156 .is_gt()
21157 {
21158 let range_after_cursor = *cursor_position..replace_range.end;
21159 let text_after_cursor = buffer
21160 .text_for_range(
21161 buffer.anchor_before(range_after_cursor.start)
21162 ..buffer.anchor_after(range_after_cursor.end),
21163 )
21164 .collect::<String>()
21165 .to_ascii_lowercase();
21166 completion
21167 .label
21168 .text
21169 .to_ascii_lowercase()
21170 .ends_with(&text_after_cursor)
21171 } else {
21172 true
21173 }
21174 }
21175 }
21176 }
21177 };
21178
21179 if should_replace {
21180 replace_range.clone()
21181 } else {
21182 insert_range.clone()
21183 }
21184 } else {
21185 replace_range.clone()
21186 }
21187 };
21188
21189 if range_to_replace
21190 .end
21191 .cmp(&cursor_position, &buffer_snapshot)
21192 .is_lt()
21193 {
21194 range_to_replace.end = *cursor_position;
21195 }
21196
21197 CompletionEdit {
21198 new_text,
21199 replace_range: range_to_replace.to_offset(&buffer),
21200 snippet,
21201 }
21202}
21203
21204struct CompletionEdit {
21205 new_text: String,
21206 replace_range: Range<usize>,
21207 snippet: Option<Snippet>,
21208}
21209
21210fn insert_extra_newline_brackets(
21211 buffer: &MultiBufferSnapshot,
21212 range: Range<usize>,
21213 language: &language::LanguageScope,
21214) -> bool {
21215 let leading_whitespace_len = buffer
21216 .reversed_chars_at(range.start)
21217 .take_while(|c| c.is_whitespace() && *c != '\n')
21218 .map(|c| c.len_utf8())
21219 .sum::<usize>();
21220 let trailing_whitespace_len = buffer
21221 .chars_at(range.end)
21222 .take_while(|c| c.is_whitespace() && *c != '\n')
21223 .map(|c| c.len_utf8())
21224 .sum::<usize>();
21225 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21226
21227 language.brackets().any(|(pair, enabled)| {
21228 let pair_start = pair.start.trim_end();
21229 let pair_end = pair.end.trim_start();
21230
21231 enabled
21232 && pair.newline
21233 && buffer.contains_str_at(range.end, pair_end)
21234 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21235 })
21236}
21237
21238fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21239 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21240 [(buffer, range, _)] => (*buffer, range.clone()),
21241 _ => return false,
21242 };
21243 let pair = {
21244 let mut result: Option<BracketMatch> = None;
21245
21246 for pair in buffer
21247 .all_bracket_ranges(range.clone())
21248 .filter(move |pair| {
21249 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21250 })
21251 {
21252 let len = pair.close_range.end - pair.open_range.start;
21253
21254 if let Some(existing) = &result {
21255 let existing_len = existing.close_range.end - existing.open_range.start;
21256 if len > existing_len {
21257 continue;
21258 }
21259 }
21260
21261 result = Some(pair);
21262 }
21263
21264 result
21265 };
21266 let Some(pair) = pair else {
21267 return false;
21268 };
21269 pair.newline_only
21270 && buffer
21271 .chars_for_range(pair.open_range.end..range.start)
21272 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21273 .all(|c| c.is_whitespace() && c != '\n')
21274}
21275
21276fn update_uncommitted_diff_for_buffer(
21277 editor: Entity<Editor>,
21278 project: &Entity<Project>,
21279 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21280 buffer: Entity<MultiBuffer>,
21281 cx: &mut App,
21282) -> Task<()> {
21283 let mut tasks = Vec::new();
21284 project.update(cx, |project, cx| {
21285 for buffer in buffers {
21286 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21287 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21288 }
21289 }
21290 });
21291 cx.spawn(async move |cx| {
21292 let diffs = future::join_all(tasks).await;
21293 if editor
21294 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21295 .unwrap_or(false)
21296 {
21297 return;
21298 }
21299
21300 buffer
21301 .update(cx, |buffer, cx| {
21302 for diff in diffs.into_iter().flatten() {
21303 buffer.add_diff(diff, cx);
21304 }
21305 })
21306 .ok();
21307 })
21308}
21309
21310fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21311 let tab_size = tab_size.get() as usize;
21312 let mut width = offset;
21313
21314 for ch in text.chars() {
21315 width += if ch == '\t' {
21316 tab_size - (width % tab_size)
21317 } else {
21318 1
21319 };
21320 }
21321
21322 width - offset
21323}
21324
21325#[cfg(test)]
21326mod tests {
21327 use super::*;
21328
21329 #[test]
21330 fn test_string_size_with_expanded_tabs() {
21331 let nz = |val| NonZeroU32::new(val).unwrap();
21332 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21333 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21334 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21335 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21336 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21337 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21338 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21339 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21340 }
21341}
21342
21343/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21344struct WordBreakingTokenizer<'a> {
21345 input: &'a str,
21346}
21347
21348impl<'a> WordBreakingTokenizer<'a> {
21349 fn new(input: &'a str) -> Self {
21350 Self { input }
21351 }
21352}
21353
21354fn is_char_ideographic(ch: char) -> bool {
21355 use unicode_script::Script::*;
21356 use unicode_script::UnicodeScript;
21357 matches!(ch.script(), Han | Tangut | Yi)
21358}
21359
21360fn is_grapheme_ideographic(text: &str) -> bool {
21361 text.chars().any(is_char_ideographic)
21362}
21363
21364fn is_grapheme_whitespace(text: &str) -> bool {
21365 text.chars().any(|x| x.is_whitespace())
21366}
21367
21368fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21369 text.chars().next().map_or(false, |ch| {
21370 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21371 })
21372}
21373
21374#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21375enum WordBreakToken<'a> {
21376 Word { token: &'a str, grapheme_len: usize },
21377 InlineWhitespace { token: &'a str, grapheme_len: usize },
21378 Newline,
21379}
21380
21381impl<'a> Iterator for WordBreakingTokenizer<'a> {
21382 /// Yields a span, the count of graphemes in the token, and whether it was
21383 /// whitespace. Note that it also breaks at word boundaries.
21384 type Item = WordBreakToken<'a>;
21385
21386 fn next(&mut self) -> Option<Self::Item> {
21387 use unicode_segmentation::UnicodeSegmentation;
21388 if self.input.is_empty() {
21389 return None;
21390 }
21391
21392 let mut iter = self.input.graphemes(true).peekable();
21393 let mut offset = 0;
21394 let mut grapheme_len = 0;
21395 if let Some(first_grapheme) = iter.next() {
21396 let is_newline = first_grapheme == "\n";
21397 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21398 offset += first_grapheme.len();
21399 grapheme_len += 1;
21400 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21401 if let Some(grapheme) = iter.peek().copied() {
21402 if should_stay_with_preceding_ideograph(grapheme) {
21403 offset += grapheme.len();
21404 grapheme_len += 1;
21405 }
21406 }
21407 } else {
21408 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21409 let mut next_word_bound = words.peek().copied();
21410 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21411 next_word_bound = words.next();
21412 }
21413 while let Some(grapheme) = iter.peek().copied() {
21414 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21415 break;
21416 };
21417 if is_grapheme_whitespace(grapheme) != is_whitespace
21418 || (grapheme == "\n") != is_newline
21419 {
21420 break;
21421 };
21422 offset += grapheme.len();
21423 grapheme_len += 1;
21424 iter.next();
21425 }
21426 }
21427 let token = &self.input[..offset];
21428 self.input = &self.input[offset..];
21429 if token == "\n" {
21430 Some(WordBreakToken::Newline)
21431 } else if is_whitespace {
21432 Some(WordBreakToken::InlineWhitespace {
21433 token,
21434 grapheme_len,
21435 })
21436 } else {
21437 Some(WordBreakToken::Word {
21438 token,
21439 grapheme_len,
21440 })
21441 }
21442 } else {
21443 None
21444 }
21445 }
21446}
21447
21448#[test]
21449fn test_word_breaking_tokenizer() {
21450 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21451 ("", &[]),
21452 (" ", &[whitespace(" ", 2)]),
21453 ("Ʒ", &[word("Ʒ", 1)]),
21454 ("Ǽ", &[word("Ǽ", 1)]),
21455 ("⋑", &[word("⋑", 1)]),
21456 ("⋑⋑", &[word("⋑⋑", 2)]),
21457 (
21458 "原理,进而",
21459 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21460 ),
21461 (
21462 "hello world",
21463 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21464 ),
21465 (
21466 "hello, world",
21467 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21468 ),
21469 (
21470 " hello world",
21471 &[
21472 whitespace(" ", 2),
21473 word("hello", 5),
21474 whitespace(" ", 1),
21475 word("world", 5),
21476 ],
21477 ),
21478 (
21479 "这是什么 \n 钢笔",
21480 &[
21481 word("这", 1),
21482 word("是", 1),
21483 word("什", 1),
21484 word("么", 1),
21485 whitespace(" ", 1),
21486 newline(),
21487 whitespace(" ", 1),
21488 word("钢", 1),
21489 word("笔", 1),
21490 ],
21491 ),
21492 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21493 ];
21494
21495 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21496 WordBreakToken::Word {
21497 token,
21498 grapheme_len,
21499 }
21500 }
21501
21502 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21503 WordBreakToken::InlineWhitespace {
21504 token,
21505 grapheme_len,
21506 }
21507 }
21508
21509 fn newline() -> WordBreakToken<'static> {
21510 WordBreakToken::Newline
21511 }
21512
21513 for (input, result) in tests {
21514 assert_eq!(
21515 WordBreakingTokenizer::new(input)
21516 .collect::<Vec<_>>()
21517 .as_slice(),
21518 *result,
21519 );
21520 }
21521}
21522
21523fn wrap_with_prefix(
21524 first_line_prefix: String,
21525 subsequent_lines_prefix: String,
21526 unwrapped_text: String,
21527 wrap_column: usize,
21528 tab_size: NonZeroU32,
21529 preserve_existing_whitespace: bool,
21530) -> String {
21531 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21532 let subsequent_lines_prefix_len =
21533 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21534 let mut wrapped_text = String::new();
21535 let mut current_line = first_line_prefix.clone();
21536 let mut is_first_line = true;
21537
21538 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21539 let mut current_line_len = first_line_prefix_len;
21540 let mut in_whitespace = false;
21541 for token in tokenizer {
21542 let have_preceding_whitespace = in_whitespace;
21543 match token {
21544 WordBreakToken::Word {
21545 token,
21546 grapheme_len,
21547 } => {
21548 in_whitespace = false;
21549 let current_prefix_len = if is_first_line {
21550 first_line_prefix_len
21551 } else {
21552 subsequent_lines_prefix_len
21553 };
21554 if current_line_len + grapheme_len > wrap_column
21555 && current_line_len != current_prefix_len
21556 {
21557 wrapped_text.push_str(current_line.trim_end());
21558 wrapped_text.push('\n');
21559 is_first_line = false;
21560 current_line = subsequent_lines_prefix.clone();
21561 current_line_len = subsequent_lines_prefix_len;
21562 }
21563 current_line.push_str(token);
21564 current_line_len += grapheme_len;
21565 }
21566 WordBreakToken::InlineWhitespace {
21567 mut token,
21568 mut grapheme_len,
21569 } => {
21570 in_whitespace = true;
21571 if have_preceding_whitespace && !preserve_existing_whitespace {
21572 continue;
21573 }
21574 if !preserve_existing_whitespace {
21575 token = " ";
21576 grapheme_len = 1;
21577 }
21578 let current_prefix_len = if is_first_line {
21579 first_line_prefix_len
21580 } else {
21581 subsequent_lines_prefix_len
21582 };
21583 if current_line_len + grapheme_len > wrap_column {
21584 wrapped_text.push_str(current_line.trim_end());
21585 wrapped_text.push('\n');
21586 is_first_line = false;
21587 current_line = subsequent_lines_prefix.clone();
21588 current_line_len = subsequent_lines_prefix_len;
21589 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21590 current_line.push_str(token);
21591 current_line_len += grapheme_len;
21592 }
21593 }
21594 WordBreakToken::Newline => {
21595 in_whitespace = true;
21596 let current_prefix_len = if is_first_line {
21597 first_line_prefix_len
21598 } else {
21599 subsequent_lines_prefix_len
21600 };
21601 if preserve_existing_whitespace {
21602 wrapped_text.push_str(current_line.trim_end());
21603 wrapped_text.push('\n');
21604 is_first_line = false;
21605 current_line = subsequent_lines_prefix.clone();
21606 current_line_len = subsequent_lines_prefix_len;
21607 } else if have_preceding_whitespace {
21608 continue;
21609 } else if current_line_len + 1 > wrap_column
21610 && current_line_len != current_prefix_len
21611 {
21612 wrapped_text.push_str(current_line.trim_end());
21613 wrapped_text.push('\n');
21614 is_first_line = false;
21615 current_line = subsequent_lines_prefix.clone();
21616 current_line_len = subsequent_lines_prefix_len;
21617 } else if current_line_len != current_prefix_len {
21618 current_line.push(' ');
21619 current_line_len += 1;
21620 }
21621 }
21622 }
21623 }
21624
21625 if !current_line.is_empty() {
21626 wrapped_text.push_str(¤t_line);
21627 }
21628 wrapped_text
21629}
21630
21631#[test]
21632fn test_wrap_with_prefix() {
21633 assert_eq!(
21634 wrap_with_prefix(
21635 "# ".to_string(),
21636 "# ".to_string(),
21637 "abcdefg".to_string(),
21638 4,
21639 NonZeroU32::new(4).unwrap(),
21640 false,
21641 ),
21642 "# abcdefg"
21643 );
21644 assert_eq!(
21645 wrap_with_prefix(
21646 "".to_string(),
21647 "".to_string(),
21648 "\thello world".to_string(),
21649 8,
21650 NonZeroU32::new(4).unwrap(),
21651 false,
21652 ),
21653 "hello\nworld"
21654 );
21655 assert_eq!(
21656 wrap_with_prefix(
21657 "// ".to_string(),
21658 "// ".to_string(),
21659 "xx \nyy zz aa bb cc".to_string(),
21660 12,
21661 NonZeroU32::new(4).unwrap(),
21662 false,
21663 ),
21664 "// xx yy zz\n// aa bb cc"
21665 );
21666 assert_eq!(
21667 wrap_with_prefix(
21668 String::new(),
21669 String::new(),
21670 "这是什么 \n 钢笔".to_string(),
21671 3,
21672 NonZeroU32::new(4).unwrap(),
21673 false,
21674 ),
21675 "这是什\n么 钢\n笔"
21676 );
21677}
21678
21679pub trait CollaborationHub {
21680 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21681 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21682 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21683}
21684
21685impl CollaborationHub for Entity<Project> {
21686 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21687 self.read(cx).collaborators()
21688 }
21689
21690 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21691 self.read(cx).user_store().read(cx).participant_indices()
21692 }
21693
21694 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21695 let this = self.read(cx);
21696 let user_ids = this.collaborators().values().map(|c| c.user_id);
21697 this.user_store().read(cx).participant_names(user_ids, cx)
21698 }
21699}
21700
21701pub trait SemanticsProvider {
21702 fn hover(
21703 &self,
21704 buffer: &Entity<Buffer>,
21705 position: text::Anchor,
21706 cx: &mut App,
21707 ) -> Option<Task<Vec<project::Hover>>>;
21708
21709 fn inline_values(
21710 &self,
21711 buffer_handle: Entity<Buffer>,
21712 range: Range<text::Anchor>,
21713 cx: &mut App,
21714 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21715
21716 fn inlay_hints(
21717 &self,
21718 buffer_handle: Entity<Buffer>,
21719 range: Range<text::Anchor>,
21720 cx: &mut App,
21721 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21722
21723 fn resolve_inlay_hint(
21724 &self,
21725 hint: InlayHint,
21726 buffer_handle: Entity<Buffer>,
21727 server_id: LanguageServerId,
21728 cx: &mut App,
21729 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21730
21731 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21732
21733 fn document_highlights(
21734 &self,
21735 buffer: &Entity<Buffer>,
21736 position: text::Anchor,
21737 cx: &mut App,
21738 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21739
21740 fn definitions(
21741 &self,
21742 buffer: &Entity<Buffer>,
21743 position: text::Anchor,
21744 kind: GotoDefinitionKind,
21745 cx: &mut App,
21746 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21747
21748 fn range_for_rename(
21749 &self,
21750 buffer: &Entity<Buffer>,
21751 position: text::Anchor,
21752 cx: &mut App,
21753 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21754
21755 fn perform_rename(
21756 &self,
21757 buffer: &Entity<Buffer>,
21758 position: text::Anchor,
21759 new_name: String,
21760 cx: &mut App,
21761 ) -> Option<Task<Result<ProjectTransaction>>>;
21762}
21763
21764pub trait CompletionProvider {
21765 fn completions(
21766 &self,
21767 excerpt_id: ExcerptId,
21768 buffer: &Entity<Buffer>,
21769 buffer_position: text::Anchor,
21770 trigger: CompletionContext,
21771 window: &mut Window,
21772 cx: &mut Context<Editor>,
21773 ) -> Task<Result<Vec<CompletionResponse>>>;
21774
21775 fn resolve_completions(
21776 &self,
21777 _buffer: Entity<Buffer>,
21778 _completion_indices: Vec<usize>,
21779 _completions: Rc<RefCell<Box<[Completion]>>>,
21780 _cx: &mut Context<Editor>,
21781 ) -> Task<Result<bool>> {
21782 Task::ready(Ok(false))
21783 }
21784
21785 fn apply_additional_edits_for_completion(
21786 &self,
21787 _buffer: Entity<Buffer>,
21788 _completions: Rc<RefCell<Box<[Completion]>>>,
21789 _completion_index: usize,
21790 _push_to_history: bool,
21791 _cx: &mut Context<Editor>,
21792 ) -> Task<Result<Option<language::Transaction>>> {
21793 Task::ready(Ok(None))
21794 }
21795
21796 fn is_completion_trigger(
21797 &self,
21798 buffer: &Entity<Buffer>,
21799 position: language::Anchor,
21800 text: &str,
21801 trigger_in_words: bool,
21802 menu_is_open: bool,
21803 cx: &mut Context<Editor>,
21804 ) -> bool;
21805
21806 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21807
21808 fn sort_completions(&self) -> bool {
21809 true
21810 }
21811
21812 fn filter_completions(&self) -> bool {
21813 true
21814 }
21815}
21816
21817pub trait CodeActionProvider {
21818 fn id(&self) -> Arc<str>;
21819
21820 fn code_actions(
21821 &self,
21822 buffer: &Entity<Buffer>,
21823 range: Range<text::Anchor>,
21824 window: &mut Window,
21825 cx: &mut App,
21826 ) -> Task<Result<Vec<CodeAction>>>;
21827
21828 fn apply_code_action(
21829 &self,
21830 buffer_handle: Entity<Buffer>,
21831 action: CodeAction,
21832 excerpt_id: ExcerptId,
21833 push_to_history: bool,
21834 window: &mut Window,
21835 cx: &mut App,
21836 ) -> Task<Result<ProjectTransaction>>;
21837}
21838
21839impl CodeActionProvider for Entity<Project> {
21840 fn id(&self) -> Arc<str> {
21841 "project".into()
21842 }
21843
21844 fn code_actions(
21845 &self,
21846 buffer: &Entity<Buffer>,
21847 range: Range<text::Anchor>,
21848 _window: &mut Window,
21849 cx: &mut App,
21850 ) -> Task<Result<Vec<CodeAction>>> {
21851 self.update(cx, |project, cx| {
21852 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21853 let code_actions = project.code_actions(buffer, range, None, cx);
21854 cx.background_spawn(async move {
21855 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21856 Ok(code_lens_actions
21857 .context("code lens fetch")?
21858 .into_iter()
21859 .chain(code_actions.context("code action fetch")?)
21860 .collect())
21861 })
21862 })
21863 }
21864
21865 fn apply_code_action(
21866 &self,
21867 buffer_handle: Entity<Buffer>,
21868 action: CodeAction,
21869 _excerpt_id: ExcerptId,
21870 push_to_history: bool,
21871 _window: &mut Window,
21872 cx: &mut App,
21873 ) -> Task<Result<ProjectTransaction>> {
21874 self.update(cx, |project, cx| {
21875 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21876 })
21877 }
21878}
21879
21880fn snippet_completions(
21881 project: &Project,
21882 buffer: &Entity<Buffer>,
21883 buffer_position: text::Anchor,
21884 cx: &mut App,
21885) -> Task<Result<CompletionResponse>> {
21886 let languages = buffer.read(cx).languages_at(buffer_position);
21887 let snippet_store = project.snippets().read(cx);
21888
21889 let scopes: Vec<_> = languages
21890 .iter()
21891 .filter_map(|language| {
21892 let language_name = language.lsp_id();
21893 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21894
21895 if snippets.is_empty() {
21896 None
21897 } else {
21898 Some((language.default_scope(), snippets))
21899 }
21900 })
21901 .collect();
21902
21903 if scopes.is_empty() {
21904 return Task::ready(Ok(CompletionResponse {
21905 completions: vec![],
21906 is_incomplete: false,
21907 }));
21908 }
21909
21910 let snapshot = buffer.read(cx).text_snapshot();
21911 let chars: String = snapshot
21912 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21913 .collect();
21914 let executor = cx.background_executor().clone();
21915
21916 cx.background_spawn(async move {
21917 let mut is_incomplete = false;
21918 let mut completions: Vec<Completion> = Vec::new();
21919 for (scope, snippets) in scopes.into_iter() {
21920 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21921 let mut last_word = chars
21922 .chars()
21923 .take_while(|c| classifier.is_word(*c))
21924 .collect::<String>();
21925 last_word = last_word.chars().rev().collect();
21926
21927 if last_word.is_empty() {
21928 return Ok(CompletionResponse {
21929 completions: vec![],
21930 is_incomplete: true,
21931 });
21932 }
21933
21934 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21935 let to_lsp = |point: &text::Anchor| {
21936 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21937 point_to_lsp(end)
21938 };
21939 let lsp_end = to_lsp(&buffer_position);
21940
21941 let candidates = snippets
21942 .iter()
21943 .enumerate()
21944 .flat_map(|(ix, snippet)| {
21945 snippet
21946 .prefix
21947 .iter()
21948 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21949 })
21950 .collect::<Vec<StringMatchCandidate>>();
21951
21952 const MAX_RESULTS: usize = 100;
21953 let mut matches = fuzzy::match_strings(
21954 &candidates,
21955 &last_word,
21956 last_word.chars().any(|c| c.is_uppercase()),
21957 true,
21958 MAX_RESULTS,
21959 &Default::default(),
21960 executor.clone(),
21961 )
21962 .await;
21963
21964 if matches.len() >= MAX_RESULTS {
21965 is_incomplete = true;
21966 }
21967
21968 // Remove all candidates where the query's start does not match the start of any word in the candidate
21969 if let Some(query_start) = last_word.chars().next() {
21970 matches.retain(|string_match| {
21971 split_words(&string_match.string).any(|word| {
21972 // Check that the first codepoint of the word as lowercase matches the first
21973 // codepoint of the query as lowercase
21974 word.chars()
21975 .flat_map(|codepoint| codepoint.to_lowercase())
21976 .zip(query_start.to_lowercase())
21977 .all(|(word_cp, query_cp)| word_cp == query_cp)
21978 })
21979 });
21980 }
21981
21982 let matched_strings = matches
21983 .into_iter()
21984 .map(|m| m.string)
21985 .collect::<HashSet<_>>();
21986
21987 completions.extend(snippets.iter().filter_map(|snippet| {
21988 let matching_prefix = snippet
21989 .prefix
21990 .iter()
21991 .find(|prefix| matched_strings.contains(*prefix))?;
21992 let start = as_offset - last_word.len();
21993 let start = snapshot.anchor_before(start);
21994 let range = start..buffer_position;
21995 let lsp_start = to_lsp(&start);
21996 let lsp_range = lsp::Range {
21997 start: lsp_start,
21998 end: lsp_end,
21999 };
22000 Some(Completion {
22001 replace_range: range,
22002 new_text: snippet.body.clone(),
22003 source: CompletionSource::Lsp {
22004 insert_range: None,
22005 server_id: LanguageServerId(usize::MAX),
22006 resolved: true,
22007 lsp_completion: Box::new(lsp::CompletionItem {
22008 label: snippet.prefix.first().unwrap().clone(),
22009 kind: Some(CompletionItemKind::SNIPPET),
22010 label_details: snippet.description.as_ref().map(|description| {
22011 lsp::CompletionItemLabelDetails {
22012 detail: Some(description.clone()),
22013 description: None,
22014 }
22015 }),
22016 insert_text_format: Some(InsertTextFormat::SNIPPET),
22017 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22018 lsp::InsertReplaceEdit {
22019 new_text: snippet.body.clone(),
22020 insert: lsp_range,
22021 replace: lsp_range,
22022 },
22023 )),
22024 filter_text: Some(snippet.body.clone()),
22025 sort_text: Some(char::MAX.to_string()),
22026 ..lsp::CompletionItem::default()
22027 }),
22028 lsp_defaults: None,
22029 },
22030 label: CodeLabel {
22031 text: matching_prefix.clone(),
22032 runs: Vec::new(),
22033 filter_range: 0..matching_prefix.len(),
22034 },
22035 icon_path: None,
22036 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22037 single_line: snippet.name.clone().into(),
22038 plain_text: snippet
22039 .description
22040 .clone()
22041 .map(|description| description.into()),
22042 }),
22043 insert_text_mode: None,
22044 confirm: None,
22045 })
22046 }))
22047 }
22048
22049 Ok(CompletionResponse {
22050 completions,
22051 is_incomplete,
22052 })
22053 })
22054}
22055
22056impl CompletionProvider for Entity<Project> {
22057 fn completions(
22058 &self,
22059 _excerpt_id: ExcerptId,
22060 buffer: &Entity<Buffer>,
22061 buffer_position: text::Anchor,
22062 options: CompletionContext,
22063 _window: &mut Window,
22064 cx: &mut Context<Editor>,
22065 ) -> Task<Result<Vec<CompletionResponse>>> {
22066 self.update(cx, |project, cx| {
22067 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22068 let project_completions = project.completions(buffer, buffer_position, options, cx);
22069 cx.background_spawn(async move {
22070 let mut responses = project_completions.await?;
22071 let snippets = snippets.await?;
22072 if !snippets.completions.is_empty() {
22073 responses.push(snippets);
22074 }
22075 Ok(responses)
22076 })
22077 })
22078 }
22079
22080 fn resolve_completions(
22081 &self,
22082 buffer: Entity<Buffer>,
22083 completion_indices: Vec<usize>,
22084 completions: Rc<RefCell<Box<[Completion]>>>,
22085 cx: &mut Context<Editor>,
22086 ) -> Task<Result<bool>> {
22087 self.update(cx, |project, cx| {
22088 project.lsp_store().update(cx, |lsp_store, cx| {
22089 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22090 })
22091 })
22092 }
22093
22094 fn apply_additional_edits_for_completion(
22095 &self,
22096 buffer: Entity<Buffer>,
22097 completions: Rc<RefCell<Box<[Completion]>>>,
22098 completion_index: usize,
22099 push_to_history: bool,
22100 cx: &mut Context<Editor>,
22101 ) -> Task<Result<Option<language::Transaction>>> {
22102 self.update(cx, |project, cx| {
22103 project.lsp_store().update(cx, |lsp_store, cx| {
22104 lsp_store.apply_additional_edits_for_completion(
22105 buffer,
22106 completions,
22107 completion_index,
22108 push_to_history,
22109 cx,
22110 )
22111 })
22112 })
22113 }
22114
22115 fn is_completion_trigger(
22116 &self,
22117 buffer: &Entity<Buffer>,
22118 position: language::Anchor,
22119 text: &str,
22120 trigger_in_words: bool,
22121 menu_is_open: bool,
22122 cx: &mut Context<Editor>,
22123 ) -> bool {
22124 let mut chars = text.chars();
22125 let char = if let Some(char) = chars.next() {
22126 char
22127 } else {
22128 return false;
22129 };
22130 if chars.next().is_some() {
22131 return false;
22132 }
22133
22134 let buffer = buffer.read(cx);
22135 let snapshot = buffer.snapshot();
22136 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22137 return false;
22138 }
22139 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22140 if trigger_in_words && classifier.is_word(char) {
22141 return true;
22142 }
22143
22144 buffer.completion_triggers().contains(text)
22145 }
22146}
22147
22148impl SemanticsProvider for Entity<Project> {
22149 fn hover(
22150 &self,
22151 buffer: &Entity<Buffer>,
22152 position: text::Anchor,
22153 cx: &mut App,
22154 ) -> Option<Task<Vec<project::Hover>>> {
22155 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22156 }
22157
22158 fn document_highlights(
22159 &self,
22160 buffer: &Entity<Buffer>,
22161 position: text::Anchor,
22162 cx: &mut App,
22163 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22164 Some(self.update(cx, |project, cx| {
22165 project.document_highlights(buffer, position, cx)
22166 }))
22167 }
22168
22169 fn definitions(
22170 &self,
22171 buffer: &Entity<Buffer>,
22172 position: text::Anchor,
22173 kind: GotoDefinitionKind,
22174 cx: &mut App,
22175 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22176 Some(self.update(cx, |project, cx| match kind {
22177 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22178 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22179 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22180 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22181 }))
22182 }
22183
22184 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22185 // TODO: make this work for remote projects
22186 self.update(cx, |project, cx| {
22187 if project
22188 .active_debug_session(cx)
22189 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22190 {
22191 return true;
22192 }
22193
22194 buffer.update(cx, |buffer, cx| {
22195 project.any_language_server_supports_inlay_hints(buffer, cx)
22196 })
22197 })
22198 }
22199
22200 fn inline_values(
22201 &self,
22202 buffer_handle: Entity<Buffer>,
22203 range: Range<text::Anchor>,
22204 cx: &mut App,
22205 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22206 self.update(cx, |project, cx| {
22207 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22208
22209 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22210 })
22211 }
22212
22213 fn inlay_hints(
22214 &self,
22215 buffer_handle: Entity<Buffer>,
22216 range: Range<text::Anchor>,
22217 cx: &mut App,
22218 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22219 Some(self.update(cx, |project, cx| {
22220 project.inlay_hints(buffer_handle, range, cx)
22221 }))
22222 }
22223
22224 fn resolve_inlay_hint(
22225 &self,
22226 hint: InlayHint,
22227 buffer_handle: Entity<Buffer>,
22228 server_id: LanguageServerId,
22229 cx: &mut App,
22230 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22231 Some(self.update(cx, |project, cx| {
22232 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22233 }))
22234 }
22235
22236 fn range_for_rename(
22237 &self,
22238 buffer: &Entity<Buffer>,
22239 position: text::Anchor,
22240 cx: &mut App,
22241 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22242 Some(self.update(cx, |project, cx| {
22243 let buffer = buffer.clone();
22244 let task = project.prepare_rename(buffer.clone(), position, cx);
22245 cx.spawn(async move |_, cx| {
22246 Ok(match task.await? {
22247 PrepareRenameResponse::Success(range) => Some(range),
22248 PrepareRenameResponse::InvalidPosition => None,
22249 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22250 // Fallback on using TreeSitter info to determine identifier range
22251 buffer.read_with(cx, |buffer, _| {
22252 let snapshot = buffer.snapshot();
22253 let (range, kind) = snapshot.surrounding_word(position, false);
22254 if kind != Some(CharKind::Word) {
22255 return None;
22256 }
22257 Some(
22258 snapshot.anchor_before(range.start)
22259 ..snapshot.anchor_after(range.end),
22260 )
22261 })?
22262 }
22263 })
22264 })
22265 }))
22266 }
22267
22268 fn perform_rename(
22269 &self,
22270 buffer: &Entity<Buffer>,
22271 position: text::Anchor,
22272 new_name: String,
22273 cx: &mut App,
22274 ) -> Option<Task<Result<ProjectTransaction>>> {
22275 Some(self.update(cx, |project, cx| {
22276 project.perform_rename(buffer.clone(), position, new_name, cx)
22277 }))
22278 }
22279}
22280
22281fn inlay_hint_settings(
22282 location: Anchor,
22283 snapshot: &MultiBufferSnapshot,
22284 cx: &mut Context<Editor>,
22285) -> InlayHintSettings {
22286 let file = snapshot.file_at(location);
22287 let language = snapshot.language_at(location).map(|l| l.name());
22288 language_settings(language, file, cx).inlay_hints
22289}
22290
22291fn consume_contiguous_rows(
22292 contiguous_row_selections: &mut Vec<Selection<Point>>,
22293 selection: &Selection<Point>,
22294 display_map: &DisplaySnapshot,
22295 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22296) -> (MultiBufferRow, MultiBufferRow) {
22297 contiguous_row_selections.push(selection.clone());
22298 let start_row = starting_row(selection, display_map);
22299 let mut end_row = ending_row(selection, display_map);
22300
22301 while let Some(next_selection) = selections.peek() {
22302 if next_selection.start.row <= end_row.0 {
22303 end_row = ending_row(next_selection, display_map);
22304 contiguous_row_selections.push(selections.next().unwrap().clone());
22305 } else {
22306 break;
22307 }
22308 }
22309 (start_row, end_row)
22310}
22311
22312fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22313 if selection.start.column > 0 {
22314 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22315 } else {
22316 MultiBufferRow(selection.start.row)
22317 }
22318}
22319
22320fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22321 if next_selection.end.column > 0 || next_selection.is_empty() {
22322 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22323 } else {
22324 MultiBufferRow(next_selection.end.row)
22325 }
22326}
22327
22328impl EditorSnapshot {
22329 pub fn remote_selections_in_range<'a>(
22330 &'a self,
22331 range: &'a Range<Anchor>,
22332 collaboration_hub: &dyn CollaborationHub,
22333 cx: &'a App,
22334 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22335 let participant_names = collaboration_hub.user_names(cx);
22336 let participant_indices = collaboration_hub.user_participant_indices(cx);
22337 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22338 let collaborators_by_replica_id = collaborators_by_peer_id
22339 .values()
22340 .map(|collaborator| (collaborator.replica_id, collaborator))
22341 .collect::<HashMap<_, _>>();
22342 self.buffer_snapshot
22343 .selections_in_range(range, false)
22344 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22345 if replica_id == AGENT_REPLICA_ID {
22346 Some(RemoteSelection {
22347 replica_id,
22348 selection,
22349 cursor_shape,
22350 line_mode,
22351 collaborator_id: CollaboratorId::Agent,
22352 user_name: Some("Agent".into()),
22353 color: cx.theme().players().agent(),
22354 })
22355 } else {
22356 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22357 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22358 let user_name = participant_names.get(&collaborator.user_id).cloned();
22359 Some(RemoteSelection {
22360 replica_id,
22361 selection,
22362 cursor_shape,
22363 line_mode,
22364 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22365 user_name,
22366 color: if let Some(index) = participant_index {
22367 cx.theme().players().color_for_participant(index.0)
22368 } else {
22369 cx.theme().players().absent()
22370 },
22371 })
22372 }
22373 })
22374 }
22375
22376 pub fn hunks_for_ranges(
22377 &self,
22378 ranges: impl IntoIterator<Item = Range<Point>>,
22379 ) -> Vec<MultiBufferDiffHunk> {
22380 let mut hunks = Vec::new();
22381 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22382 HashMap::default();
22383 for query_range in ranges {
22384 let query_rows =
22385 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22386 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22387 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22388 ) {
22389 // Include deleted hunks that are adjacent to the query range, because
22390 // otherwise they would be missed.
22391 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22392 if hunk.status().is_deleted() {
22393 intersects_range |= hunk.row_range.start == query_rows.end;
22394 intersects_range |= hunk.row_range.end == query_rows.start;
22395 }
22396 if intersects_range {
22397 if !processed_buffer_rows
22398 .entry(hunk.buffer_id)
22399 .or_default()
22400 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22401 {
22402 continue;
22403 }
22404 hunks.push(hunk);
22405 }
22406 }
22407 }
22408
22409 hunks
22410 }
22411
22412 fn display_diff_hunks_for_rows<'a>(
22413 &'a self,
22414 display_rows: Range<DisplayRow>,
22415 folded_buffers: &'a HashSet<BufferId>,
22416 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22417 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22418 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22419
22420 self.buffer_snapshot
22421 .diff_hunks_in_range(buffer_start..buffer_end)
22422 .filter_map(|hunk| {
22423 if folded_buffers.contains(&hunk.buffer_id) {
22424 return None;
22425 }
22426
22427 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22428 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22429
22430 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22431 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22432
22433 let display_hunk = if hunk_display_start.column() != 0 {
22434 DisplayDiffHunk::Folded {
22435 display_row: hunk_display_start.row(),
22436 }
22437 } else {
22438 let mut end_row = hunk_display_end.row();
22439 if hunk_display_end.column() > 0 {
22440 end_row.0 += 1;
22441 }
22442 let is_created_file = hunk.is_created_file();
22443 DisplayDiffHunk::Unfolded {
22444 status: hunk.status(),
22445 diff_base_byte_range: hunk.diff_base_byte_range,
22446 display_row_range: hunk_display_start.row()..end_row,
22447 multi_buffer_range: Anchor::range_in_buffer(
22448 hunk.excerpt_id,
22449 hunk.buffer_id,
22450 hunk.buffer_range,
22451 ),
22452 is_created_file,
22453 }
22454 };
22455
22456 Some(display_hunk)
22457 })
22458 }
22459
22460 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22461 self.display_snapshot.buffer_snapshot.language_at(position)
22462 }
22463
22464 pub fn is_focused(&self) -> bool {
22465 self.is_focused
22466 }
22467
22468 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22469 self.placeholder_text.as_ref()
22470 }
22471
22472 pub fn scroll_position(&self) -> gpui::Point<f32> {
22473 self.scroll_anchor.scroll_position(&self.display_snapshot)
22474 }
22475
22476 fn gutter_dimensions(
22477 &self,
22478 font_id: FontId,
22479 font_size: Pixels,
22480 max_line_number_width: Pixels,
22481 cx: &App,
22482 ) -> Option<GutterDimensions> {
22483 if !self.show_gutter {
22484 return None;
22485 }
22486
22487 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22488 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22489
22490 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22491 matches!(
22492 ProjectSettings::get_global(cx).git.git_gutter,
22493 Some(GitGutterSetting::TrackedFiles)
22494 )
22495 });
22496 let gutter_settings = EditorSettings::get_global(cx).gutter;
22497 let show_line_numbers = self
22498 .show_line_numbers
22499 .unwrap_or(gutter_settings.line_numbers);
22500 let line_gutter_width = if show_line_numbers {
22501 // Avoid flicker-like gutter resizes when the line number gains another digit by
22502 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22503 let min_width_for_number_on_gutter =
22504 ch_advance * gutter_settings.min_line_number_digits as f32;
22505 max_line_number_width.max(min_width_for_number_on_gutter)
22506 } else {
22507 0.0.into()
22508 };
22509
22510 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22511 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22512
22513 let git_blame_entries_width =
22514 self.git_blame_gutter_max_author_length
22515 .map(|max_author_length| {
22516 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22517 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22518
22519 /// The number of characters to dedicate to gaps and margins.
22520 const SPACING_WIDTH: usize = 4;
22521
22522 let max_char_count = max_author_length.min(renderer.max_author_length())
22523 + ::git::SHORT_SHA_LENGTH
22524 + MAX_RELATIVE_TIMESTAMP.len()
22525 + SPACING_WIDTH;
22526
22527 ch_advance * max_char_count
22528 });
22529
22530 let is_singleton = self.buffer_snapshot.is_singleton();
22531
22532 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22533 left_padding += if !is_singleton {
22534 ch_width * 4.0
22535 } else if show_runnables || show_breakpoints {
22536 ch_width * 3.0
22537 } else if show_git_gutter && show_line_numbers {
22538 ch_width * 2.0
22539 } else if show_git_gutter || show_line_numbers {
22540 ch_width
22541 } else {
22542 px(0.)
22543 };
22544
22545 let shows_folds = is_singleton && gutter_settings.folds;
22546
22547 let right_padding = if shows_folds && show_line_numbers {
22548 ch_width * 4.0
22549 } else if shows_folds || (!is_singleton && show_line_numbers) {
22550 ch_width * 3.0
22551 } else if show_line_numbers {
22552 ch_width
22553 } else {
22554 px(0.)
22555 };
22556
22557 Some(GutterDimensions {
22558 left_padding,
22559 right_padding,
22560 width: line_gutter_width + left_padding + right_padding,
22561 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22562 git_blame_entries_width,
22563 })
22564 }
22565
22566 pub fn render_crease_toggle(
22567 &self,
22568 buffer_row: MultiBufferRow,
22569 row_contains_cursor: bool,
22570 editor: Entity<Editor>,
22571 window: &mut Window,
22572 cx: &mut App,
22573 ) -> Option<AnyElement> {
22574 let folded = self.is_line_folded(buffer_row);
22575 let mut is_foldable = false;
22576
22577 if let Some(crease) = self
22578 .crease_snapshot
22579 .query_row(buffer_row, &self.buffer_snapshot)
22580 {
22581 is_foldable = true;
22582 match crease {
22583 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22584 if let Some(render_toggle) = render_toggle {
22585 let toggle_callback =
22586 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22587 if folded {
22588 editor.update(cx, |editor, cx| {
22589 editor.fold_at(buffer_row, window, cx)
22590 });
22591 } else {
22592 editor.update(cx, |editor, cx| {
22593 editor.unfold_at(buffer_row, window, cx)
22594 });
22595 }
22596 });
22597 return Some((render_toggle)(
22598 buffer_row,
22599 folded,
22600 toggle_callback,
22601 window,
22602 cx,
22603 ));
22604 }
22605 }
22606 }
22607 }
22608
22609 is_foldable |= self.starts_indent(buffer_row);
22610
22611 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22612 Some(
22613 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22614 .toggle_state(folded)
22615 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22616 if folded {
22617 this.unfold_at(buffer_row, window, cx);
22618 } else {
22619 this.fold_at(buffer_row, window, cx);
22620 }
22621 }))
22622 .into_any_element(),
22623 )
22624 } else {
22625 None
22626 }
22627 }
22628
22629 pub fn render_crease_trailer(
22630 &self,
22631 buffer_row: MultiBufferRow,
22632 window: &mut Window,
22633 cx: &mut App,
22634 ) -> Option<AnyElement> {
22635 let folded = self.is_line_folded(buffer_row);
22636 if let Crease::Inline { render_trailer, .. } = self
22637 .crease_snapshot
22638 .query_row(buffer_row, &self.buffer_snapshot)?
22639 {
22640 let render_trailer = render_trailer.as_ref()?;
22641 Some(render_trailer(buffer_row, folded, window, cx))
22642 } else {
22643 None
22644 }
22645 }
22646}
22647
22648impl Deref for EditorSnapshot {
22649 type Target = DisplaySnapshot;
22650
22651 fn deref(&self) -> &Self::Target {
22652 &self.display_snapshot
22653 }
22654}
22655
22656#[derive(Clone, Debug, PartialEq, Eq)]
22657pub enum EditorEvent {
22658 InputIgnored {
22659 text: Arc<str>,
22660 },
22661 InputHandled {
22662 utf16_range_to_replace: Option<Range<isize>>,
22663 text: Arc<str>,
22664 },
22665 ExcerptsAdded {
22666 buffer: Entity<Buffer>,
22667 predecessor: ExcerptId,
22668 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22669 },
22670 ExcerptsRemoved {
22671 ids: Vec<ExcerptId>,
22672 removed_buffer_ids: Vec<BufferId>,
22673 },
22674 BufferFoldToggled {
22675 ids: Vec<ExcerptId>,
22676 folded: bool,
22677 },
22678 ExcerptsEdited {
22679 ids: Vec<ExcerptId>,
22680 },
22681 ExcerptsExpanded {
22682 ids: Vec<ExcerptId>,
22683 },
22684 BufferEdited,
22685 Edited {
22686 transaction_id: clock::Lamport,
22687 },
22688 Reparsed(BufferId),
22689 Focused,
22690 FocusedIn,
22691 Blurred,
22692 DirtyChanged,
22693 Saved,
22694 TitleChanged,
22695 DiffBaseChanged,
22696 SelectionsChanged {
22697 local: bool,
22698 },
22699 ScrollPositionChanged {
22700 local: bool,
22701 autoscroll: bool,
22702 },
22703 Closed,
22704 TransactionUndone {
22705 transaction_id: clock::Lamport,
22706 },
22707 TransactionBegun {
22708 transaction_id: clock::Lamport,
22709 },
22710 Reloaded,
22711 CursorShapeChanged,
22712 PushedToNavHistory {
22713 anchor: Anchor,
22714 is_deactivate: bool,
22715 },
22716}
22717
22718impl EventEmitter<EditorEvent> for Editor {}
22719
22720impl Focusable for Editor {
22721 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22722 self.focus_handle.clone()
22723 }
22724}
22725
22726impl Render for Editor {
22727 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22728 let settings = ThemeSettings::get_global(cx);
22729
22730 let mut text_style = match self.mode {
22731 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22732 color: cx.theme().colors().editor_foreground,
22733 font_family: settings.ui_font.family.clone(),
22734 font_features: settings.ui_font.features.clone(),
22735 font_fallbacks: settings.ui_font.fallbacks.clone(),
22736 font_size: rems(0.875).into(),
22737 font_weight: settings.ui_font.weight,
22738 line_height: relative(settings.buffer_line_height.value()),
22739 ..Default::default()
22740 },
22741 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22742 color: cx.theme().colors().editor_foreground,
22743 font_family: settings.buffer_font.family.clone(),
22744 font_features: settings.buffer_font.features.clone(),
22745 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22746 font_size: settings.buffer_font_size(cx).into(),
22747 font_weight: settings.buffer_font.weight,
22748 line_height: relative(settings.buffer_line_height.value()),
22749 ..Default::default()
22750 },
22751 };
22752 if let Some(text_style_refinement) = &self.text_style_refinement {
22753 text_style.refine(text_style_refinement)
22754 }
22755
22756 let background = match self.mode {
22757 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22758 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22759 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22760 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22761 };
22762
22763 EditorElement::new(
22764 &cx.entity(),
22765 EditorStyle {
22766 background,
22767 border: cx.theme().colors().border,
22768 local_player: cx.theme().players().local(),
22769 text: text_style,
22770 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22771 syntax: cx.theme().syntax().clone(),
22772 status: cx.theme().status().clone(),
22773 inlay_hints_style: make_inlay_hints_style(cx),
22774 inline_completion_styles: make_suggestion_styles(cx),
22775 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22776 show_underlines: self.diagnostics_enabled(),
22777 },
22778 )
22779 }
22780}
22781
22782impl EntityInputHandler for Editor {
22783 fn text_for_range(
22784 &mut self,
22785 range_utf16: Range<usize>,
22786 adjusted_range: &mut Option<Range<usize>>,
22787 _: &mut Window,
22788 cx: &mut Context<Self>,
22789 ) -> Option<String> {
22790 let snapshot = self.buffer.read(cx).read(cx);
22791 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22792 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22793 if (start.0..end.0) != range_utf16 {
22794 adjusted_range.replace(start.0..end.0);
22795 }
22796 Some(snapshot.text_for_range(start..end).collect())
22797 }
22798
22799 fn selected_text_range(
22800 &mut self,
22801 ignore_disabled_input: bool,
22802 _: &mut Window,
22803 cx: &mut Context<Self>,
22804 ) -> Option<UTF16Selection> {
22805 // Prevent the IME menu from appearing when holding down an alphabetic key
22806 // while input is disabled.
22807 if !ignore_disabled_input && !self.input_enabled {
22808 return None;
22809 }
22810
22811 let selection = self.selections.newest::<OffsetUtf16>(cx);
22812 let range = selection.range();
22813
22814 Some(UTF16Selection {
22815 range: range.start.0..range.end.0,
22816 reversed: selection.reversed,
22817 })
22818 }
22819
22820 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22821 let snapshot = self.buffer.read(cx).read(cx);
22822 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22823 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22824 }
22825
22826 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22827 self.clear_highlights::<InputComposition>(cx);
22828 self.ime_transaction.take();
22829 }
22830
22831 fn replace_text_in_range(
22832 &mut self,
22833 range_utf16: Option<Range<usize>>,
22834 text: &str,
22835 window: &mut Window,
22836 cx: &mut Context<Self>,
22837 ) {
22838 if !self.input_enabled {
22839 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22840 return;
22841 }
22842
22843 self.transact(window, cx, |this, window, cx| {
22844 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22845 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22846 Some(this.selection_replacement_ranges(range_utf16, cx))
22847 } else {
22848 this.marked_text_ranges(cx)
22849 };
22850
22851 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22852 let newest_selection_id = this.selections.newest_anchor().id;
22853 this.selections
22854 .all::<OffsetUtf16>(cx)
22855 .iter()
22856 .zip(ranges_to_replace.iter())
22857 .find_map(|(selection, range)| {
22858 if selection.id == newest_selection_id {
22859 Some(
22860 (range.start.0 as isize - selection.head().0 as isize)
22861 ..(range.end.0 as isize - selection.head().0 as isize),
22862 )
22863 } else {
22864 None
22865 }
22866 })
22867 });
22868
22869 cx.emit(EditorEvent::InputHandled {
22870 utf16_range_to_replace: range_to_replace,
22871 text: text.into(),
22872 });
22873
22874 if let Some(new_selected_ranges) = new_selected_ranges {
22875 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22876 selections.select_ranges(new_selected_ranges)
22877 });
22878 this.backspace(&Default::default(), window, cx);
22879 }
22880
22881 this.handle_input(text, window, cx);
22882 });
22883
22884 if let Some(transaction) = self.ime_transaction {
22885 self.buffer.update(cx, |buffer, cx| {
22886 buffer.group_until_transaction(transaction, cx);
22887 });
22888 }
22889
22890 self.unmark_text(window, cx);
22891 }
22892
22893 fn replace_and_mark_text_in_range(
22894 &mut self,
22895 range_utf16: Option<Range<usize>>,
22896 text: &str,
22897 new_selected_range_utf16: Option<Range<usize>>,
22898 window: &mut Window,
22899 cx: &mut Context<Self>,
22900 ) {
22901 if !self.input_enabled {
22902 return;
22903 }
22904
22905 let transaction = self.transact(window, cx, |this, window, cx| {
22906 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22907 let snapshot = this.buffer.read(cx).read(cx);
22908 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22909 for marked_range in &mut marked_ranges {
22910 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22911 marked_range.start.0 += relative_range_utf16.start;
22912 marked_range.start =
22913 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22914 marked_range.end =
22915 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22916 }
22917 }
22918 Some(marked_ranges)
22919 } else if let Some(range_utf16) = range_utf16 {
22920 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22921 Some(this.selection_replacement_ranges(range_utf16, cx))
22922 } else {
22923 None
22924 };
22925
22926 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22927 let newest_selection_id = this.selections.newest_anchor().id;
22928 this.selections
22929 .all::<OffsetUtf16>(cx)
22930 .iter()
22931 .zip(ranges_to_replace.iter())
22932 .find_map(|(selection, range)| {
22933 if selection.id == newest_selection_id {
22934 Some(
22935 (range.start.0 as isize - selection.head().0 as isize)
22936 ..(range.end.0 as isize - selection.head().0 as isize),
22937 )
22938 } else {
22939 None
22940 }
22941 })
22942 });
22943
22944 cx.emit(EditorEvent::InputHandled {
22945 utf16_range_to_replace: range_to_replace,
22946 text: text.into(),
22947 });
22948
22949 if let Some(ranges) = ranges_to_replace {
22950 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22951 s.select_ranges(ranges)
22952 });
22953 }
22954
22955 let marked_ranges = {
22956 let snapshot = this.buffer.read(cx).read(cx);
22957 this.selections
22958 .disjoint_anchors()
22959 .iter()
22960 .map(|selection| {
22961 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22962 })
22963 .collect::<Vec<_>>()
22964 };
22965
22966 if text.is_empty() {
22967 this.unmark_text(window, cx);
22968 } else {
22969 this.highlight_text::<InputComposition>(
22970 marked_ranges.clone(),
22971 HighlightStyle {
22972 underline: Some(UnderlineStyle {
22973 thickness: px(1.),
22974 color: None,
22975 wavy: false,
22976 }),
22977 ..Default::default()
22978 },
22979 cx,
22980 );
22981 }
22982
22983 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22984 let use_autoclose = this.use_autoclose;
22985 let use_auto_surround = this.use_auto_surround;
22986 this.set_use_autoclose(false);
22987 this.set_use_auto_surround(false);
22988 this.handle_input(text, window, cx);
22989 this.set_use_autoclose(use_autoclose);
22990 this.set_use_auto_surround(use_auto_surround);
22991
22992 if let Some(new_selected_range) = new_selected_range_utf16 {
22993 let snapshot = this.buffer.read(cx).read(cx);
22994 let new_selected_ranges = marked_ranges
22995 .into_iter()
22996 .map(|marked_range| {
22997 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22998 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22999 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23000 snapshot.clip_offset_utf16(new_start, Bias::Left)
23001 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23002 })
23003 .collect::<Vec<_>>();
23004
23005 drop(snapshot);
23006 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23007 selections.select_ranges(new_selected_ranges)
23008 });
23009 }
23010 });
23011
23012 self.ime_transaction = self.ime_transaction.or(transaction);
23013 if let Some(transaction) = self.ime_transaction {
23014 self.buffer.update(cx, |buffer, cx| {
23015 buffer.group_until_transaction(transaction, cx);
23016 });
23017 }
23018
23019 if self.text_highlights::<InputComposition>(cx).is_none() {
23020 self.ime_transaction.take();
23021 }
23022 }
23023
23024 fn bounds_for_range(
23025 &mut self,
23026 range_utf16: Range<usize>,
23027 element_bounds: gpui::Bounds<Pixels>,
23028 window: &mut Window,
23029 cx: &mut Context<Self>,
23030 ) -> Option<gpui::Bounds<Pixels>> {
23031 let text_layout_details = self.text_layout_details(window);
23032 let CharacterDimensions {
23033 em_width,
23034 em_advance,
23035 line_height,
23036 } = self.character_dimensions(window);
23037
23038 let snapshot = self.snapshot(window, cx);
23039 let scroll_position = snapshot.scroll_position();
23040 let scroll_left = scroll_position.x * em_advance;
23041
23042 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23043 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23044 + self.gutter_dimensions.full_width();
23045 let y = line_height * (start.row().as_f32() - scroll_position.y);
23046
23047 Some(Bounds {
23048 origin: element_bounds.origin + point(x, y),
23049 size: size(em_width, line_height),
23050 })
23051 }
23052
23053 fn character_index_for_point(
23054 &mut self,
23055 point: gpui::Point<Pixels>,
23056 _window: &mut Window,
23057 _cx: &mut Context<Self>,
23058 ) -> Option<usize> {
23059 let position_map = self.last_position_map.as_ref()?;
23060 if !position_map.text_hitbox.contains(&point) {
23061 return None;
23062 }
23063 let display_point = position_map.point_for_position(point).previous_valid;
23064 let anchor = position_map
23065 .snapshot
23066 .display_point_to_anchor(display_point, Bias::Left);
23067 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23068 Some(utf16_offset.0)
23069 }
23070}
23071
23072trait SelectionExt {
23073 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23074 fn spanned_rows(
23075 &self,
23076 include_end_if_at_line_start: bool,
23077 map: &DisplaySnapshot,
23078 ) -> Range<MultiBufferRow>;
23079}
23080
23081impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23082 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23083 let start = self
23084 .start
23085 .to_point(&map.buffer_snapshot)
23086 .to_display_point(map);
23087 let end = self
23088 .end
23089 .to_point(&map.buffer_snapshot)
23090 .to_display_point(map);
23091 if self.reversed {
23092 end..start
23093 } else {
23094 start..end
23095 }
23096 }
23097
23098 fn spanned_rows(
23099 &self,
23100 include_end_if_at_line_start: bool,
23101 map: &DisplaySnapshot,
23102 ) -> Range<MultiBufferRow> {
23103 let start = self.start.to_point(&map.buffer_snapshot);
23104 let mut end = self.end.to_point(&map.buffer_snapshot);
23105 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23106 end.row -= 1;
23107 }
23108
23109 let buffer_start = map.prev_line_boundary(start).0;
23110 let buffer_end = map.next_line_boundary(end).0;
23111 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23112 }
23113}
23114
23115impl<T: InvalidationRegion> InvalidationStack<T> {
23116 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23117 where
23118 S: Clone + ToOffset,
23119 {
23120 while let Some(region) = self.last() {
23121 let all_selections_inside_invalidation_ranges =
23122 if selections.len() == region.ranges().len() {
23123 selections
23124 .iter()
23125 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23126 .all(|(selection, invalidation_range)| {
23127 let head = selection.head().to_offset(buffer);
23128 invalidation_range.start <= head && invalidation_range.end >= head
23129 })
23130 } else {
23131 false
23132 };
23133
23134 if all_selections_inside_invalidation_ranges {
23135 break;
23136 } else {
23137 self.pop();
23138 }
23139 }
23140 }
23141}
23142
23143impl<T> Default for InvalidationStack<T> {
23144 fn default() -> Self {
23145 Self(Default::default())
23146 }
23147}
23148
23149impl<T> Deref for InvalidationStack<T> {
23150 type Target = Vec<T>;
23151
23152 fn deref(&self) -> &Self::Target {
23153 &self.0
23154 }
23155}
23156
23157impl<T> DerefMut for InvalidationStack<T> {
23158 fn deref_mut(&mut self) -> &mut Self::Target {
23159 &mut self.0
23160 }
23161}
23162
23163impl InvalidationRegion for SnippetState {
23164 fn ranges(&self) -> &[Range<Anchor>] {
23165 &self.ranges[self.active_index]
23166 }
23167}
23168
23169fn inline_completion_edit_text(
23170 current_snapshot: &BufferSnapshot,
23171 edits: &[(Range<Anchor>, String)],
23172 edit_preview: &EditPreview,
23173 include_deletions: bool,
23174 cx: &App,
23175) -> HighlightedText {
23176 let edits = edits
23177 .iter()
23178 .map(|(anchor, text)| {
23179 (
23180 anchor.start.text_anchor..anchor.end.text_anchor,
23181 text.clone(),
23182 )
23183 })
23184 .collect::<Vec<_>>();
23185
23186 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23187}
23188
23189pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23190 match severity {
23191 lsp::DiagnosticSeverity::ERROR => colors.error,
23192 lsp::DiagnosticSeverity::WARNING => colors.warning,
23193 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23194 lsp::DiagnosticSeverity::HINT => colors.info,
23195 _ => colors.ignored,
23196 }
23197}
23198
23199pub fn styled_runs_for_code_label<'a>(
23200 label: &'a CodeLabel,
23201 syntax_theme: &'a theme::SyntaxTheme,
23202) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23203 let fade_out = HighlightStyle {
23204 fade_out: Some(0.35),
23205 ..Default::default()
23206 };
23207
23208 let mut prev_end = label.filter_range.end;
23209 label
23210 .runs
23211 .iter()
23212 .enumerate()
23213 .flat_map(move |(ix, (range, highlight_id))| {
23214 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23215 style
23216 } else {
23217 return Default::default();
23218 };
23219 let mut muted_style = style;
23220 muted_style.highlight(fade_out);
23221
23222 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23223 if range.start >= label.filter_range.end {
23224 if range.start > prev_end {
23225 runs.push((prev_end..range.start, fade_out));
23226 }
23227 runs.push((range.clone(), muted_style));
23228 } else if range.end <= label.filter_range.end {
23229 runs.push((range.clone(), style));
23230 } else {
23231 runs.push((range.start..label.filter_range.end, style));
23232 runs.push((label.filter_range.end..range.end, muted_style));
23233 }
23234 prev_end = cmp::max(prev_end, range.end);
23235
23236 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23237 runs.push((prev_end..label.text.len(), fade_out));
23238 }
23239
23240 runs
23241 })
23242}
23243
23244pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23245 let mut prev_index = 0;
23246 let mut prev_codepoint: Option<char> = None;
23247 text.char_indices()
23248 .chain([(text.len(), '\0')])
23249 .filter_map(move |(index, codepoint)| {
23250 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23251 let is_boundary = index == text.len()
23252 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23253 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23254 if is_boundary {
23255 let chunk = &text[prev_index..index];
23256 prev_index = index;
23257 Some(chunk)
23258 } else {
23259 None
23260 }
23261 })
23262}
23263
23264pub trait RangeToAnchorExt: Sized {
23265 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23266
23267 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23268 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23269 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23270 }
23271}
23272
23273impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23274 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23275 let start_offset = self.start.to_offset(snapshot);
23276 let end_offset = self.end.to_offset(snapshot);
23277 if start_offset == end_offset {
23278 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23279 } else {
23280 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23281 }
23282 }
23283}
23284
23285pub trait RowExt {
23286 fn as_f32(&self) -> f32;
23287
23288 fn next_row(&self) -> Self;
23289
23290 fn previous_row(&self) -> Self;
23291
23292 fn minus(&self, other: Self) -> u32;
23293}
23294
23295impl RowExt for DisplayRow {
23296 fn as_f32(&self) -> f32 {
23297 self.0 as f32
23298 }
23299
23300 fn next_row(&self) -> Self {
23301 Self(self.0 + 1)
23302 }
23303
23304 fn previous_row(&self) -> Self {
23305 Self(self.0.saturating_sub(1))
23306 }
23307
23308 fn minus(&self, other: Self) -> u32 {
23309 self.0 - other.0
23310 }
23311}
23312
23313impl RowExt for MultiBufferRow {
23314 fn as_f32(&self) -> f32 {
23315 self.0 as f32
23316 }
23317
23318 fn next_row(&self) -> Self {
23319 Self(self.0 + 1)
23320 }
23321
23322 fn previous_row(&self) -> Self {
23323 Self(self.0.saturating_sub(1))
23324 }
23325
23326 fn minus(&self, other: Self) -> u32 {
23327 self.0 - other.0
23328 }
23329}
23330
23331trait RowRangeExt {
23332 type Row;
23333
23334 fn len(&self) -> usize;
23335
23336 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23337}
23338
23339impl RowRangeExt for Range<MultiBufferRow> {
23340 type Row = MultiBufferRow;
23341
23342 fn len(&self) -> usize {
23343 (self.end.0 - self.start.0) as usize
23344 }
23345
23346 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23347 (self.start.0..self.end.0).map(MultiBufferRow)
23348 }
23349}
23350
23351impl RowRangeExt for Range<DisplayRow> {
23352 type Row = DisplayRow;
23353
23354 fn len(&self) -> usize {
23355 (self.end.0 - self.start.0) as usize
23356 }
23357
23358 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23359 (self.start.0..self.end.0).map(DisplayRow)
23360 }
23361}
23362
23363/// If select range has more than one line, we
23364/// just point the cursor to range.start.
23365fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23366 if range.start.row == range.end.row {
23367 range
23368 } else {
23369 range.start..range.start
23370 }
23371}
23372pub struct KillRing(ClipboardItem);
23373impl Global for KillRing {}
23374
23375const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23376
23377enum BreakpointPromptEditAction {
23378 Log,
23379 Condition,
23380 HitCondition,
23381}
23382
23383struct BreakpointPromptEditor {
23384 pub(crate) prompt: Entity<Editor>,
23385 editor: WeakEntity<Editor>,
23386 breakpoint_anchor: Anchor,
23387 breakpoint: Breakpoint,
23388 edit_action: BreakpointPromptEditAction,
23389 block_ids: HashSet<CustomBlockId>,
23390 editor_margins: Arc<Mutex<EditorMargins>>,
23391 _subscriptions: Vec<Subscription>,
23392}
23393
23394impl BreakpointPromptEditor {
23395 const MAX_LINES: u8 = 4;
23396
23397 fn new(
23398 editor: WeakEntity<Editor>,
23399 breakpoint_anchor: Anchor,
23400 breakpoint: Breakpoint,
23401 edit_action: BreakpointPromptEditAction,
23402 window: &mut Window,
23403 cx: &mut Context<Self>,
23404 ) -> Self {
23405 let base_text = match edit_action {
23406 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23407 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23408 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23409 }
23410 .map(|msg| msg.to_string())
23411 .unwrap_or_default();
23412
23413 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23414 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23415
23416 let prompt = cx.new(|cx| {
23417 let mut prompt = Editor::new(
23418 EditorMode::AutoHeight {
23419 min_lines: 1,
23420 max_lines: Some(Self::MAX_LINES as usize),
23421 },
23422 buffer,
23423 None,
23424 window,
23425 cx,
23426 );
23427 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23428 prompt.set_show_cursor_when_unfocused(false, cx);
23429 prompt.set_placeholder_text(
23430 match edit_action {
23431 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23432 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23433 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23434 },
23435 cx,
23436 );
23437
23438 prompt
23439 });
23440
23441 Self {
23442 prompt,
23443 editor,
23444 breakpoint_anchor,
23445 breakpoint,
23446 edit_action,
23447 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23448 block_ids: Default::default(),
23449 _subscriptions: vec![],
23450 }
23451 }
23452
23453 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23454 self.block_ids.extend(block_ids)
23455 }
23456
23457 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23458 if let Some(editor) = self.editor.upgrade() {
23459 let message = self
23460 .prompt
23461 .read(cx)
23462 .buffer
23463 .read(cx)
23464 .as_singleton()
23465 .expect("A multi buffer in breakpoint prompt isn't possible")
23466 .read(cx)
23467 .as_rope()
23468 .to_string();
23469
23470 editor.update(cx, |editor, cx| {
23471 editor.edit_breakpoint_at_anchor(
23472 self.breakpoint_anchor,
23473 self.breakpoint.clone(),
23474 match self.edit_action {
23475 BreakpointPromptEditAction::Log => {
23476 BreakpointEditAction::EditLogMessage(message.into())
23477 }
23478 BreakpointPromptEditAction::Condition => {
23479 BreakpointEditAction::EditCondition(message.into())
23480 }
23481 BreakpointPromptEditAction::HitCondition => {
23482 BreakpointEditAction::EditHitCondition(message.into())
23483 }
23484 },
23485 cx,
23486 );
23487
23488 editor.remove_blocks(self.block_ids.clone(), None, cx);
23489 cx.focus_self(window);
23490 });
23491 }
23492 }
23493
23494 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23495 self.editor
23496 .update(cx, |editor, cx| {
23497 editor.remove_blocks(self.block_ids.clone(), None, cx);
23498 window.focus(&editor.focus_handle);
23499 })
23500 .log_err();
23501 }
23502
23503 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23504 let settings = ThemeSettings::get_global(cx);
23505 let text_style = TextStyle {
23506 color: if self.prompt.read(cx).read_only(cx) {
23507 cx.theme().colors().text_disabled
23508 } else {
23509 cx.theme().colors().text
23510 },
23511 font_family: settings.buffer_font.family.clone(),
23512 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23513 font_size: settings.buffer_font_size(cx).into(),
23514 font_weight: settings.buffer_font.weight,
23515 line_height: relative(settings.buffer_line_height.value()),
23516 ..Default::default()
23517 };
23518 EditorElement::new(
23519 &self.prompt,
23520 EditorStyle {
23521 background: cx.theme().colors().editor_background,
23522 local_player: cx.theme().players().local(),
23523 text: text_style,
23524 ..Default::default()
23525 },
23526 )
23527 }
23528}
23529
23530impl Render for BreakpointPromptEditor {
23531 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23532 let editor_margins = *self.editor_margins.lock();
23533 let gutter_dimensions = editor_margins.gutter;
23534 h_flex()
23535 .key_context("Editor")
23536 .bg(cx.theme().colors().editor_background)
23537 .border_y_1()
23538 .border_color(cx.theme().status().info_border)
23539 .size_full()
23540 .py(window.line_height() / 2.5)
23541 .on_action(cx.listener(Self::confirm))
23542 .on_action(cx.listener(Self::cancel))
23543 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23544 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23545 }
23546}
23547
23548impl Focusable for BreakpointPromptEditor {
23549 fn focus_handle(&self, cx: &App) -> FocusHandle {
23550 self.prompt.focus_handle(cx)
23551 }
23552}
23553
23554fn all_edits_insertions_or_deletions(
23555 edits: &Vec<(Range<Anchor>, String)>,
23556 snapshot: &MultiBufferSnapshot,
23557) -> bool {
23558 let mut all_insertions = true;
23559 let mut all_deletions = true;
23560
23561 for (range, new_text) in edits.iter() {
23562 let range_is_empty = range.to_offset(&snapshot).is_empty();
23563 let text_is_empty = new_text.is_empty();
23564
23565 if range_is_empty != text_is_empty {
23566 if range_is_empty {
23567 all_deletions = false;
23568 } else {
23569 all_insertions = false;
23570 }
23571 } else {
23572 return false;
23573 }
23574
23575 if !all_insertions && !all_deletions {
23576 return false;
23577 }
23578 }
23579 all_insertions || all_deletions
23580}
23581
23582struct MissingEditPredictionKeybindingTooltip;
23583
23584impl Render for MissingEditPredictionKeybindingTooltip {
23585 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23586 ui::tooltip_container(window, cx, |container, _, cx| {
23587 container
23588 .flex_shrink_0()
23589 .max_w_80()
23590 .min_h(rems_from_px(124.))
23591 .justify_between()
23592 .child(
23593 v_flex()
23594 .flex_1()
23595 .text_ui_sm(cx)
23596 .child(Label::new("Conflict with Accept Keybinding"))
23597 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23598 )
23599 .child(
23600 h_flex()
23601 .pb_1()
23602 .gap_1()
23603 .items_end()
23604 .w_full()
23605 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23606 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23607 }))
23608 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23609 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23610 })),
23611 )
23612 })
23613 }
23614}
23615
23616#[derive(Debug, Clone, Copy, PartialEq)]
23617pub struct LineHighlight {
23618 pub background: Background,
23619 pub border: Option<gpui::Hsla>,
23620 pub include_gutter: bool,
23621 pub type_id: Option<TypeId>,
23622}
23623
23624struct LineManipulationResult {
23625 pub new_text: String,
23626 pub line_count_before: usize,
23627 pub line_count_after: usize,
23628}
23629
23630fn render_diff_hunk_controls(
23631 row: u32,
23632 status: &DiffHunkStatus,
23633 hunk_range: Range<Anchor>,
23634 is_created_file: bool,
23635 line_height: Pixels,
23636 editor: &Entity<Editor>,
23637 _window: &mut Window,
23638 cx: &mut App,
23639) -> AnyElement {
23640 h_flex()
23641 .h(line_height)
23642 .mr_1()
23643 .gap_1()
23644 .px_0p5()
23645 .pb_1()
23646 .border_x_1()
23647 .border_b_1()
23648 .border_color(cx.theme().colors().border_variant)
23649 .rounded_b_lg()
23650 .bg(cx.theme().colors().editor_background)
23651 .gap_1()
23652 .block_mouse_except_scroll()
23653 .shadow_md()
23654 .child(if status.has_secondary_hunk() {
23655 Button::new(("stage", row as u64), "Stage")
23656 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23657 .tooltip({
23658 let focus_handle = editor.focus_handle(cx);
23659 move |window, cx| {
23660 Tooltip::for_action_in(
23661 "Stage Hunk",
23662 &::git::ToggleStaged,
23663 &focus_handle,
23664 window,
23665 cx,
23666 )
23667 }
23668 })
23669 .on_click({
23670 let editor = editor.clone();
23671 move |_event, _window, cx| {
23672 editor.update(cx, |editor, cx| {
23673 editor.stage_or_unstage_diff_hunks(
23674 true,
23675 vec![hunk_range.start..hunk_range.start],
23676 cx,
23677 );
23678 });
23679 }
23680 })
23681 } else {
23682 Button::new(("unstage", row as u64), "Unstage")
23683 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23684 .tooltip({
23685 let focus_handle = editor.focus_handle(cx);
23686 move |window, cx| {
23687 Tooltip::for_action_in(
23688 "Unstage Hunk",
23689 &::git::ToggleStaged,
23690 &focus_handle,
23691 window,
23692 cx,
23693 )
23694 }
23695 })
23696 .on_click({
23697 let editor = editor.clone();
23698 move |_event, _window, cx| {
23699 editor.update(cx, |editor, cx| {
23700 editor.stage_or_unstage_diff_hunks(
23701 false,
23702 vec![hunk_range.start..hunk_range.start],
23703 cx,
23704 );
23705 });
23706 }
23707 })
23708 })
23709 .child(
23710 Button::new(("restore", row as u64), "Restore")
23711 .tooltip({
23712 let focus_handle = editor.focus_handle(cx);
23713 move |window, cx| {
23714 Tooltip::for_action_in(
23715 "Restore Hunk",
23716 &::git::Restore,
23717 &focus_handle,
23718 window,
23719 cx,
23720 )
23721 }
23722 })
23723 .on_click({
23724 let editor = editor.clone();
23725 move |_event, window, cx| {
23726 editor.update(cx, |editor, cx| {
23727 let snapshot = editor.snapshot(window, cx);
23728 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23729 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23730 });
23731 }
23732 })
23733 .disabled(is_created_file),
23734 )
23735 .when(
23736 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23737 |el| {
23738 el.child(
23739 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23740 .shape(IconButtonShape::Square)
23741 .icon_size(IconSize::Small)
23742 // .disabled(!has_multiple_hunks)
23743 .tooltip({
23744 let focus_handle = editor.focus_handle(cx);
23745 move |window, cx| {
23746 Tooltip::for_action_in(
23747 "Next Hunk",
23748 &GoToHunk,
23749 &focus_handle,
23750 window,
23751 cx,
23752 )
23753 }
23754 })
23755 .on_click({
23756 let editor = editor.clone();
23757 move |_event, window, cx| {
23758 editor.update(cx, |editor, cx| {
23759 let snapshot = editor.snapshot(window, cx);
23760 let position =
23761 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23762 editor.go_to_hunk_before_or_after_position(
23763 &snapshot,
23764 position,
23765 Direction::Next,
23766 window,
23767 cx,
23768 );
23769 editor.expand_selected_diff_hunks(cx);
23770 });
23771 }
23772 }),
23773 )
23774 .child(
23775 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23776 .shape(IconButtonShape::Square)
23777 .icon_size(IconSize::Small)
23778 // .disabled(!has_multiple_hunks)
23779 .tooltip({
23780 let focus_handle = editor.focus_handle(cx);
23781 move |window, cx| {
23782 Tooltip::for_action_in(
23783 "Previous Hunk",
23784 &GoToPreviousHunk,
23785 &focus_handle,
23786 window,
23787 cx,
23788 )
23789 }
23790 })
23791 .on_click({
23792 let editor = editor.clone();
23793 move |_event, window, cx| {
23794 editor.update(cx, |editor, cx| {
23795 let snapshot = editor.snapshot(window, cx);
23796 let point =
23797 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23798 editor.go_to_hunk_before_or_after_position(
23799 &snapshot,
23800 point,
23801 Direction::Prev,
23802 window,
23803 cx,
23804 );
23805 editor.expand_selected_diff_hunks(cx);
23806 });
23807 }
23808 }),
23809 )
23810 },
23811 )
23812 .into_any_element()
23813}