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 .icon_color(ui::Color::Hidden)
6407 .toggle_state(is_active)
6408 .when(show_tooltip, |this| {
6409 this.tooltip({
6410 let focus_handle = self.focus_handle.clone();
6411 move |window, cx| {
6412 Tooltip::for_action_in(
6413 "Toggle Code Actions",
6414 &ToggleCodeActions {
6415 deployed_from: None,
6416 quick_launch: false,
6417 },
6418 &focus_handle,
6419 window,
6420 cx,
6421 )
6422 }
6423 })
6424 })
6425 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6426 window.focus(&editor.focus_handle(cx));
6427 editor.toggle_code_actions(
6428 &crate::actions::ToggleCodeActions {
6429 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6430 display_row,
6431 )),
6432 quick_launch: false,
6433 },
6434 window,
6435 cx,
6436 );
6437 }))
6438 .into_any_element()
6439 }
6440
6441 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6442 &self.context_menu
6443 }
6444
6445 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6446 let newest_selection = self.selections.newest_anchor().clone();
6447 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6448 let buffer = self.buffer.read(cx);
6449 if newest_selection.head().diff_base_anchor.is_some() {
6450 return None;
6451 }
6452 let (start_buffer, start) =
6453 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6454 let (end_buffer, end) =
6455 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6456 if start_buffer != end_buffer {
6457 return None;
6458 }
6459
6460 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6461 cx.background_executor()
6462 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6463 .await;
6464
6465 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6466 let providers = this.code_action_providers.clone();
6467 let tasks = this
6468 .code_action_providers
6469 .iter()
6470 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6471 .collect::<Vec<_>>();
6472 (providers, tasks)
6473 })?;
6474
6475 let mut actions = Vec::new();
6476 for (provider, provider_actions) in
6477 providers.into_iter().zip(future::join_all(tasks).await)
6478 {
6479 if let Some(provider_actions) = provider_actions.log_err() {
6480 actions.extend(provider_actions.into_iter().map(|action| {
6481 AvailableCodeAction {
6482 excerpt_id: newest_selection.start.excerpt_id,
6483 action,
6484 provider: provider.clone(),
6485 }
6486 }));
6487 }
6488 }
6489
6490 this.update(cx, |this, cx| {
6491 this.available_code_actions = if actions.is_empty() {
6492 None
6493 } else {
6494 Some((
6495 Location {
6496 buffer: start_buffer,
6497 range: start..end,
6498 },
6499 actions.into(),
6500 ))
6501 };
6502 cx.notify();
6503 })
6504 }));
6505 None
6506 }
6507
6508 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6509 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6510 self.show_git_blame_inline = false;
6511
6512 self.show_git_blame_inline_delay_task =
6513 Some(cx.spawn_in(window, async move |this, cx| {
6514 cx.background_executor().timer(delay).await;
6515
6516 this.update(cx, |this, cx| {
6517 this.show_git_blame_inline = true;
6518 cx.notify();
6519 })
6520 .log_err();
6521 }));
6522 }
6523 }
6524
6525 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6526 let snapshot = self.snapshot(window, cx);
6527 let cursor = self.selections.newest::<Point>(cx).head();
6528 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6529 else {
6530 return;
6531 };
6532
6533 let Some(blame) = self.blame.as_ref() else {
6534 return;
6535 };
6536
6537 let row_info = RowInfo {
6538 buffer_id: Some(buffer.remote_id()),
6539 buffer_row: Some(point.row),
6540 ..Default::default()
6541 };
6542 let Some(blame_entry) = blame
6543 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6544 .flatten()
6545 else {
6546 return;
6547 };
6548
6549 let anchor = self.selections.newest_anchor().head();
6550 let position = self.to_pixel_point(anchor, &snapshot, window);
6551 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6552 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6553 };
6554 }
6555
6556 fn show_blame_popover(
6557 &mut self,
6558 blame_entry: &BlameEntry,
6559 position: gpui::Point<Pixels>,
6560 ignore_timeout: bool,
6561 cx: &mut Context<Self>,
6562 ) {
6563 if let Some(state) = &mut self.inline_blame_popover {
6564 state.hide_task.take();
6565 } else {
6566 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6567 let blame_entry = blame_entry.clone();
6568 let show_task = cx.spawn(async move |editor, cx| {
6569 if !ignore_timeout {
6570 cx.background_executor()
6571 .timer(std::time::Duration::from_millis(blame_popover_delay))
6572 .await;
6573 }
6574 editor
6575 .update(cx, |editor, cx| {
6576 editor.inline_blame_popover_show_task.take();
6577 let Some(blame) = editor.blame.as_ref() else {
6578 return;
6579 };
6580 let blame = blame.read(cx);
6581 let details = blame.details_for_entry(&blame_entry);
6582 let markdown = cx.new(|cx| {
6583 Markdown::new(
6584 details
6585 .as_ref()
6586 .map(|message| message.message.clone())
6587 .unwrap_or_default(),
6588 None,
6589 None,
6590 cx,
6591 )
6592 });
6593 editor.inline_blame_popover = Some(InlineBlamePopover {
6594 position,
6595 hide_task: None,
6596 popover_bounds: None,
6597 popover_state: InlineBlamePopoverState {
6598 scroll_handle: ScrollHandle::new(),
6599 commit_message: details,
6600 markdown,
6601 },
6602 keyboard_grace: ignore_timeout,
6603 });
6604 cx.notify();
6605 })
6606 .ok();
6607 });
6608 self.inline_blame_popover_show_task = Some(show_task);
6609 }
6610 }
6611
6612 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6613 self.inline_blame_popover_show_task.take();
6614 if let Some(state) = &mut self.inline_blame_popover {
6615 let hide_task = cx.spawn(async move |editor, cx| {
6616 cx.background_executor()
6617 .timer(std::time::Duration::from_millis(100))
6618 .await;
6619 editor
6620 .update(cx, |editor, cx| {
6621 editor.inline_blame_popover.take();
6622 cx.notify();
6623 })
6624 .ok();
6625 });
6626 state.hide_task = Some(hide_task);
6627 }
6628 }
6629
6630 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6631 if self.pending_rename.is_some() {
6632 return None;
6633 }
6634
6635 let provider = self.semantics_provider.clone()?;
6636 let buffer = self.buffer.read(cx);
6637 let newest_selection = self.selections.newest_anchor().clone();
6638 let cursor_position = newest_selection.head();
6639 let (cursor_buffer, cursor_buffer_position) =
6640 buffer.text_anchor_for_position(cursor_position, cx)?;
6641 let (tail_buffer, tail_buffer_position) =
6642 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6643 if cursor_buffer != tail_buffer {
6644 return None;
6645 }
6646
6647 let snapshot = cursor_buffer.read(cx).snapshot();
6648 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6649 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6650 if start_word_range != end_word_range {
6651 self.document_highlights_task.take();
6652 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6653 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6654 return None;
6655 }
6656
6657 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6658 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6659 cx.background_executor()
6660 .timer(Duration::from_millis(debounce))
6661 .await;
6662
6663 let highlights = if let Some(highlights) = cx
6664 .update(|cx| {
6665 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6666 })
6667 .ok()
6668 .flatten()
6669 {
6670 highlights.await.log_err()
6671 } else {
6672 None
6673 };
6674
6675 if let Some(highlights) = highlights {
6676 this.update(cx, |this, cx| {
6677 if this.pending_rename.is_some() {
6678 return;
6679 }
6680
6681 let buffer_id = cursor_position.buffer_id;
6682 let buffer = this.buffer.read(cx);
6683 if !buffer
6684 .text_anchor_for_position(cursor_position, cx)
6685 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6686 {
6687 return;
6688 }
6689
6690 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6691 let mut write_ranges = Vec::new();
6692 let mut read_ranges = Vec::new();
6693 for highlight in highlights {
6694 for (excerpt_id, excerpt_range) in
6695 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6696 {
6697 let start = highlight
6698 .range
6699 .start
6700 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6701 let end = highlight
6702 .range
6703 .end
6704 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6705 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6706 continue;
6707 }
6708
6709 let range = Anchor {
6710 buffer_id,
6711 excerpt_id,
6712 text_anchor: start,
6713 diff_base_anchor: None,
6714 }..Anchor {
6715 buffer_id,
6716 excerpt_id,
6717 text_anchor: end,
6718 diff_base_anchor: None,
6719 };
6720 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6721 write_ranges.push(range);
6722 } else {
6723 read_ranges.push(range);
6724 }
6725 }
6726 }
6727
6728 this.highlight_background::<DocumentHighlightRead>(
6729 &read_ranges,
6730 |theme| theme.colors().editor_document_highlight_read_background,
6731 cx,
6732 );
6733 this.highlight_background::<DocumentHighlightWrite>(
6734 &write_ranges,
6735 |theme| theme.colors().editor_document_highlight_write_background,
6736 cx,
6737 );
6738 cx.notify();
6739 })
6740 .log_err();
6741 }
6742 }));
6743 None
6744 }
6745
6746 fn prepare_highlight_query_from_selection(
6747 &mut self,
6748 cx: &mut Context<Editor>,
6749 ) -> Option<(String, Range<Anchor>)> {
6750 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6751 return None;
6752 }
6753 if !EditorSettings::get_global(cx).selection_highlight {
6754 return None;
6755 }
6756 if self.selections.count() != 1 || self.selections.line_mode {
6757 return None;
6758 }
6759 let selection = self.selections.newest::<Point>(cx);
6760 if selection.is_empty() || selection.start.row != selection.end.row {
6761 return None;
6762 }
6763 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6764 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6765 let query = multi_buffer_snapshot
6766 .text_for_range(selection_anchor_range.clone())
6767 .collect::<String>();
6768 if query.trim().is_empty() {
6769 return None;
6770 }
6771 Some((query, selection_anchor_range))
6772 }
6773
6774 fn update_selection_occurrence_highlights(
6775 &mut self,
6776 query_text: String,
6777 query_range: Range<Anchor>,
6778 multi_buffer_range_to_query: Range<Point>,
6779 use_debounce: bool,
6780 window: &mut Window,
6781 cx: &mut Context<Editor>,
6782 ) -> Task<()> {
6783 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6784 cx.spawn_in(window, async move |editor, cx| {
6785 if use_debounce {
6786 cx.background_executor()
6787 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6788 .await;
6789 }
6790 let match_task = cx.background_spawn(async move {
6791 let buffer_ranges = multi_buffer_snapshot
6792 .range_to_buffer_ranges(multi_buffer_range_to_query)
6793 .into_iter()
6794 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6795 let mut match_ranges = Vec::new();
6796 let Ok(regex) = project::search::SearchQuery::text(
6797 query_text.clone(),
6798 false,
6799 false,
6800 false,
6801 Default::default(),
6802 Default::default(),
6803 false,
6804 None,
6805 ) else {
6806 return Vec::default();
6807 };
6808 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6809 match_ranges.extend(
6810 regex
6811 .search(&buffer_snapshot, Some(search_range.clone()))
6812 .await
6813 .into_iter()
6814 .filter_map(|match_range| {
6815 let match_start = buffer_snapshot
6816 .anchor_after(search_range.start + match_range.start);
6817 let match_end = buffer_snapshot
6818 .anchor_before(search_range.start + match_range.end);
6819 let match_anchor_range = Anchor::range_in_buffer(
6820 excerpt_id,
6821 buffer_snapshot.remote_id(),
6822 match_start..match_end,
6823 );
6824 (match_anchor_range != query_range).then_some(match_anchor_range)
6825 }),
6826 );
6827 }
6828 match_ranges
6829 });
6830 let match_ranges = match_task.await;
6831 editor
6832 .update_in(cx, |editor, _, cx| {
6833 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6834 if !match_ranges.is_empty() {
6835 editor.highlight_background::<SelectedTextHighlight>(
6836 &match_ranges,
6837 |theme| theme.colors().editor_document_highlight_bracket_background,
6838 cx,
6839 )
6840 }
6841 })
6842 .log_err();
6843 })
6844 }
6845
6846 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6847 struct NewlineFold;
6848 let type_id = std::any::TypeId::of::<NewlineFold>();
6849 if !self.mode.is_single_line() {
6850 return;
6851 }
6852 let snapshot = self.snapshot(window, cx);
6853 if snapshot.buffer_snapshot.max_point().row == 0 {
6854 return;
6855 }
6856 let task = cx.background_spawn(async move {
6857 let new_newlines = snapshot
6858 .buffer_chars_at(0)
6859 .filter_map(|(c, i)| {
6860 if c == '\n' {
6861 Some(
6862 snapshot.buffer_snapshot.anchor_after(i)
6863 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6864 )
6865 } else {
6866 None
6867 }
6868 })
6869 .collect::<Vec<_>>();
6870 let existing_newlines = snapshot
6871 .folds_in_range(0..snapshot.buffer_snapshot.len())
6872 .filter_map(|fold| {
6873 if fold.placeholder.type_tag == Some(type_id) {
6874 Some(fold.range.start..fold.range.end)
6875 } else {
6876 None
6877 }
6878 })
6879 .collect::<Vec<_>>();
6880
6881 (new_newlines, existing_newlines)
6882 });
6883 self.folding_newlines = cx.spawn(async move |this, cx| {
6884 let (new_newlines, existing_newlines) = task.await;
6885 if new_newlines == existing_newlines {
6886 return;
6887 }
6888 let placeholder = FoldPlaceholder {
6889 render: Arc::new(move |_, _, cx| {
6890 div()
6891 .bg(cx.theme().status().hint_background)
6892 .border_b_1()
6893 .size_full()
6894 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6895 .border_color(cx.theme().status().hint)
6896 .child("\\n")
6897 .into_any()
6898 }),
6899 constrain_width: false,
6900 merge_adjacent: false,
6901 type_tag: Some(type_id),
6902 };
6903 let creases = new_newlines
6904 .into_iter()
6905 .map(|range| Crease::simple(range, placeholder.clone()))
6906 .collect();
6907 this.update(cx, |this, cx| {
6908 this.display_map.update(cx, |display_map, cx| {
6909 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6910 display_map.fold(creases, cx);
6911 });
6912 })
6913 .ok();
6914 });
6915 }
6916
6917 fn refresh_selected_text_highlights(
6918 &mut self,
6919 on_buffer_edit: bool,
6920 window: &mut Window,
6921 cx: &mut Context<Editor>,
6922 ) {
6923 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6924 else {
6925 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6926 self.quick_selection_highlight_task.take();
6927 self.debounced_selection_highlight_task.take();
6928 return;
6929 };
6930 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6931 if on_buffer_edit
6932 || self
6933 .quick_selection_highlight_task
6934 .as_ref()
6935 .map_or(true, |(prev_anchor_range, _)| {
6936 prev_anchor_range != &query_range
6937 })
6938 {
6939 let multi_buffer_visible_start = self
6940 .scroll_manager
6941 .anchor()
6942 .anchor
6943 .to_point(&multi_buffer_snapshot);
6944 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6945 multi_buffer_visible_start
6946 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6947 Bias::Left,
6948 );
6949 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6950 self.quick_selection_highlight_task = Some((
6951 query_range.clone(),
6952 self.update_selection_occurrence_highlights(
6953 query_text.clone(),
6954 query_range.clone(),
6955 multi_buffer_visible_range,
6956 false,
6957 window,
6958 cx,
6959 ),
6960 ));
6961 }
6962 if on_buffer_edit
6963 || self
6964 .debounced_selection_highlight_task
6965 .as_ref()
6966 .map_or(true, |(prev_anchor_range, _)| {
6967 prev_anchor_range != &query_range
6968 })
6969 {
6970 let multi_buffer_start = multi_buffer_snapshot
6971 .anchor_before(0)
6972 .to_point(&multi_buffer_snapshot);
6973 let multi_buffer_end = multi_buffer_snapshot
6974 .anchor_after(multi_buffer_snapshot.len())
6975 .to_point(&multi_buffer_snapshot);
6976 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6977 self.debounced_selection_highlight_task = Some((
6978 query_range.clone(),
6979 self.update_selection_occurrence_highlights(
6980 query_text,
6981 query_range,
6982 multi_buffer_full_range,
6983 true,
6984 window,
6985 cx,
6986 ),
6987 ));
6988 }
6989 }
6990
6991 pub fn refresh_inline_completion(
6992 &mut self,
6993 debounce: bool,
6994 user_requested: bool,
6995 window: &mut Window,
6996 cx: &mut Context<Self>,
6997 ) -> Option<()> {
6998 let provider = self.edit_prediction_provider()?;
6999 let cursor = self.selections.newest_anchor().head();
7000 let (buffer, cursor_buffer_position) =
7001 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7002
7003 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7004 self.discard_inline_completion(false, cx);
7005 return None;
7006 }
7007
7008 if !user_requested
7009 && (!self.should_show_edit_predictions()
7010 || !self.is_focused(window)
7011 || buffer.read(cx).is_empty())
7012 {
7013 self.discard_inline_completion(false, cx);
7014 return None;
7015 }
7016
7017 self.update_visible_inline_completion(window, cx);
7018 provider.refresh(
7019 self.project.clone(),
7020 buffer,
7021 cursor_buffer_position,
7022 debounce,
7023 cx,
7024 );
7025 Some(())
7026 }
7027
7028 fn show_edit_predictions_in_menu(&self) -> bool {
7029 match self.edit_prediction_settings {
7030 EditPredictionSettings::Disabled => false,
7031 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7032 }
7033 }
7034
7035 pub fn edit_predictions_enabled(&self) -> bool {
7036 match self.edit_prediction_settings {
7037 EditPredictionSettings::Disabled => false,
7038 EditPredictionSettings::Enabled { .. } => true,
7039 }
7040 }
7041
7042 fn edit_prediction_requires_modifier(&self) -> bool {
7043 match self.edit_prediction_settings {
7044 EditPredictionSettings::Disabled => false,
7045 EditPredictionSettings::Enabled {
7046 preview_requires_modifier,
7047 ..
7048 } => preview_requires_modifier,
7049 }
7050 }
7051
7052 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7053 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7054 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7055 } else {
7056 let selection = self.selections.newest_anchor();
7057 let cursor = selection.head();
7058
7059 if let Some((buffer, cursor_buffer_position)) =
7060 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7061 {
7062 self.edit_prediction_settings =
7063 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7064 }
7065 }
7066 }
7067
7068 fn edit_prediction_settings_at_position(
7069 &self,
7070 buffer: &Entity<Buffer>,
7071 buffer_position: language::Anchor,
7072 cx: &App,
7073 ) -> EditPredictionSettings {
7074 if !self.mode.is_full()
7075 || !self.show_inline_completions_override.unwrap_or(true)
7076 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7077 {
7078 return EditPredictionSettings::Disabled;
7079 }
7080
7081 let buffer = buffer.read(cx);
7082
7083 let file = buffer.file();
7084
7085 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7086 return EditPredictionSettings::Disabled;
7087 };
7088
7089 let by_provider = matches!(
7090 self.menu_inline_completions_policy,
7091 MenuInlineCompletionsPolicy::ByProvider
7092 );
7093
7094 let show_in_menu = by_provider
7095 && self
7096 .edit_prediction_provider
7097 .as_ref()
7098 .map_or(false, |provider| {
7099 provider.provider.show_completions_in_menu()
7100 });
7101
7102 let preview_requires_modifier =
7103 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7104
7105 EditPredictionSettings::Enabled {
7106 show_in_menu,
7107 preview_requires_modifier,
7108 }
7109 }
7110
7111 fn should_show_edit_predictions(&self) -> bool {
7112 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7113 }
7114
7115 pub fn edit_prediction_preview_is_active(&self) -> bool {
7116 matches!(
7117 self.edit_prediction_preview,
7118 EditPredictionPreview::Active { .. }
7119 )
7120 }
7121
7122 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7123 let cursor = self.selections.newest_anchor().head();
7124 if let Some((buffer, cursor_position)) =
7125 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7126 {
7127 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7128 } else {
7129 false
7130 }
7131 }
7132
7133 pub fn supports_minimap(&self, cx: &App) -> bool {
7134 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7135 }
7136
7137 fn edit_predictions_enabled_in_buffer(
7138 &self,
7139 buffer: &Entity<Buffer>,
7140 buffer_position: language::Anchor,
7141 cx: &App,
7142 ) -> bool {
7143 maybe!({
7144 if self.read_only(cx) {
7145 return Some(false);
7146 }
7147 let provider = self.edit_prediction_provider()?;
7148 if !provider.is_enabled(&buffer, buffer_position, cx) {
7149 return Some(false);
7150 }
7151 let buffer = buffer.read(cx);
7152 let Some(file) = buffer.file() else {
7153 return Some(true);
7154 };
7155 let settings = all_language_settings(Some(file), cx);
7156 Some(settings.edit_predictions_enabled_for_file(file, cx))
7157 })
7158 .unwrap_or(false)
7159 }
7160
7161 fn cycle_inline_completion(
7162 &mut self,
7163 direction: Direction,
7164 window: &mut Window,
7165 cx: &mut Context<Self>,
7166 ) -> Option<()> {
7167 let provider = self.edit_prediction_provider()?;
7168 let cursor = self.selections.newest_anchor().head();
7169 let (buffer, cursor_buffer_position) =
7170 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7171 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7172 return None;
7173 }
7174
7175 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7176 self.update_visible_inline_completion(window, cx);
7177
7178 Some(())
7179 }
7180
7181 pub fn show_inline_completion(
7182 &mut self,
7183 _: &ShowEditPrediction,
7184 window: &mut Window,
7185 cx: &mut Context<Self>,
7186 ) {
7187 if !self.has_active_inline_completion() {
7188 self.refresh_inline_completion(false, true, window, cx);
7189 return;
7190 }
7191
7192 self.update_visible_inline_completion(window, cx);
7193 }
7194
7195 pub fn display_cursor_names(
7196 &mut self,
7197 _: &DisplayCursorNames,
7198 window: &mut Window,
7199 cx: &mut Context<Self>,
7200 ) {
7201 self.show_cursor_names(window, cx);
7202 }
7203
7204 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7205 self.show_cursor_names = true;
7206 cx.notify();
7207 cx.spawn_in(window, async move |this, cx| {
7208 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7209 this.update(cx, |this, cx| {
7210 this.show_cursor_names = false;
7211 cx.notify()
7212 })
7213 .ok()
7214 })
7215 .detach();
7216 }
7217
7218 pub fn next_edit_prediction(
7219 &mut self,
7220 _: &NextEditPrediction,
7221 window: &mut Window,
7222 cx: &mut Context<Self>,
7223 ) {
7224 if self.has_active_inline_completion() {
7225 self.cycle_inline_completion(Direction::Next, window, cx);
7226 } else {
7227 let is_copilot_disabled = self
7228 .refresh_inline_completion(false, true, window, cx)
7229 .is_none();
7230 if is_copilot_disabled {
7231 cx.propagate();
7232 }
7233 }
7234 }
7235
7236 pub fn previous_edit_prediction(
7237 &mut self,
7238 _: &PreviousEditPrediction,
7239 window: &mut Window,
7240 cx: &mut Context<Self>,
7241 ) {
7242 if self.has_active_inline_completion() {
7243 self.cycle_inline_completion(Direction::Prev, window, cx);
7244 } else {
7245 let is_copilot_disabled = self
7246 .refresh_inline_completion(false, true, window, cx)
7247 .is_none();
7248 if is_copilot_disabled {
7249 cx.propagate();
7250 }
7251 }
7252 }
7253
7254 pub fn accept_edit_prediction(
7255 &mut self,
7256 _: &AcceptEditPrediction,
7257 window: &mut Window,
7258 cx: &mut Context<Self>,
7259 ) {
7260 if self.show_edit_predictions_in_menu() {
7261 self.hide_context_menu(window, cx);
7262 }
7263
7264 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7265 return;
7266 };
7267
7268 self.report_inline_completion_event(
7269 active_inline_completion.completion_id.clone(),
7270 true,
7271 cx,
7272 );
7273
7274 match &active_inline_completion.completion {
7275 InlineCompletion::Move { target, .. } => {
7276 let target = *target;
7277
7278 if let Some(position_map) = &self.last_position_map {
7279 if position_map
7280 .visible_row_range
7281 .contains(&target.to_display_point(&position_map.snapshot).row())
7282 || !self.edit_prediction_requires_modifier()
7283 {
7284 self.unfold_ranges(&[target..target], true, false, cx);
7285 // Note that this is also done in vim's handler of the Tab action.
7286 self.change_selections(
7287 SelectionEffects::scroll(Autoscroll::newest()),
7288 window,
7289 cx,
7290 |selections| {
7291 selections.select_anchor_ranges([target..target]);
7292 },
7293 );
7294 self.clear_row_highlights::<EditPredictionPreview>();
7295
7296 self.edit_prediction_preview
7297 .set_previous_scroll_position(None);
7298 } else {
7299 self.edit_prediction_preview
7300 .set_previous_scroll_position(Some(
7301 position_map.snapshot.scroll_anchor,
7302 ));
7303
7304 self.highlight_rows::<EditPredictionPreview>(
7305 target..target,
7306 cx.theme().colors().editor_highlighted_line_background,
7307 RowHighlightOptions {
7308 autoscroll: true,
7309 ..Default::default()
7310 },
7311 cx,
7312 );
7313 self.request_autoscroll(Autoscroll::fit(), cx);
7314 }
7315 }
7316 }
7317 InlineCompletion::Edit { edits, .. } => {
7318 if let Some(provider) = self.edit_prediction_provider() {
7319 provider.accept(cx);
7320 }
7321
7322 // Store the transaction ID and selections before applying the edit
7323 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7324
7325 let snapshot = self.buffer.read(cx).snapshot(cx);
7326 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7327
7328 self.buffer.update(cx, |buffer, cx| {
7329 buffer.edit(edits.iter().cloned(), None, cx)
7330 });
7331
7332 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7333 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7334 });
7335
7336 let selections = self.selections.disjoint_anchors();
7337 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7338 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7339 if has_new_transaction {
7340 self.selection_history
7341 .insert_transaction(transaction_id_now, selections);
7342 }
7343 }
7344
7345 self.update_visible_inline_completion(window, cx);
7346 if self.active_inline_completion.is_none() {
7347 self.refresh_inline_completion(true, true, window, cx);
7348 }
7349
7350 cx.notify();
7351 }
7352 }
7353
7354 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7355 }
7356
7357 pub fn accept_partial_inline_completion(
7358 &mut self,
7359 _: &AcceptPartialEditPrediction,
7360 window: &mut Window,
7361 cx: &mut Context<Self>,
7362 ) {
7363 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7364 return;
7365 };
7366 if self.selections.count() != 1 {
7367 return;
7368 }
7369
7370 self.report_inline_completion_event(
7371 active_inline_completion.completion_id.clone(),
7372 true,
7373 cx,
7374 );
7375
7376 match &active_inline_completion.completion {
7377 InlineCompletion::Move { target, .. } => {
7378 let target = *target;
7379 self.change_selections(
7380 SelectionEffects::scroll(Autoscroll::newest()),
7381 window,
7382 cx,
7383 |selections| {
7384 selections.select_anchor_ranges([target..target]);
7385 },
7386 );
7387 }
7388 InlineCompletion::Edit { edits, .. } => {
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self.selections.newest::<usize>(cx).head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_inline_completion(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_inline_completion(
7432 &mut self,
7433 should_report_inline_completion_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_inline_completion_event {
7437 let completion_id = self
7438 .active_inline_completion
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_inline_completion_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_inline_completion(cx)
7450 }
7451
7452 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 pub fn has_active_inline_completion(&self) -> bool {
7484 self.active_inline_completion.is_some()
7485 }
7486
7487 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7488 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7489 return false;
7490 };
7491
7492 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7493 self.clear_highlights::<InlineCompletionHighlight>(cx);
7494 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7495 true
7496 }
7497
7498 /// Returns true when we're displaying the edit prediction popover below the cursor
7499 /// like we are not previewing and the LSP autocomplete menu is visible
7500 /// or we are in `when_holding_modifier` mode.
7501 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7502 if self.edit_prediction_preview_is_active()
7503 || !self.show_edit_predictions_in_menu()
7504 || !self.edit_predictions_enabled()
7505 {
7506 return false;
7507 }
7508
7509 if self.has_visible_completions_menu() {
7510 return true;
7511 }
7512
7513 has_completion && self.edit_prediction_requires_modifier()
7514 }
7515
7516 fn handle_modifiers_changed(
7517 &mut self,
7518 modifiers: Modifiers,
7519 position_map: &PositionMap,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 if self.show_edit_predictions_in_menu() {
7524 self.update_edit_prediction_preview(&modifiers, window, cx);
7525 }
7526
7527 self.update_selection_mode(&modifiers, position_map, window, cx);
7528
7529 let mouse_position = window.mouse_position();
7530 if !position_map.text_hitbox.is_hovered(window) {
7531 return;
7532 }
7533
7534 self.update_hovered_link(
7535 position_map.point_for_position(mouse_position),
7536 &position_map.snapshot,
7537 modifiers,
7538 window,
7539 cx,
7540 )
7541 }
7542
7543 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7544 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7545 if invert {
7546 match multi_cursor_setting {
7547 MultiCursorModifier::Alt => modifiers.alt,
7548 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7549 }
7550 } else {
7551 match multi_cursor_setting {
7552 MultiCursorModifier::Alt => modifiers.secondary(),
7553 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7554 }
7555 }
7556 }
7557
7558 fn columnar_selection_mode(
7559 modifiers: &Modifiers,
7560 cx: &mut Context<Self>,
7561 ) -> Option<ColumnarMode> {
7562 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7563 if Self::multi_cursor_modifier(false, modifiers, cx) {
7564 Some(ColumnarMode::FromMouse)
7565 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7566 Some(ColumnarMode::FromSelection)
7567 } else {
7568 None
7569 }
7570 } else {
7571 None
7572 }
7573 }
7574
7575 fn update_selection_mode(
7576 &mut self,
7577 modifiers: &Modifiers,
7578 position_map: &PositionMap,
7579 window: &mut Window,
7580 cx: &mut Context<Self>,
7581 ) {
7582 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7583 return;
7584 };
7585 if self.selections.pending.is_none() {
7586 return;
7587 }
7588
7589 let mouse_position = window.mouse_position();
7590 let point_for_position = position_map.point_for_position(mouse_position);
7591 let position = point_for_position.previous_valid;
7592
7593 self.select(
7594 SelectPhase::BeginColumnar {
7595 position,
7596 reset: false,
7597 mode,
7598 goal_column: point_for_position.exact_unclipped.column(),
7599 },
7600 window,
7601 cx,
7602 );
7603 }
7604
7605 fn update_edit_prediction_preview(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 window: &mut Window,
7609 cx: &mut Context<Self>,
7610 ) {
7611 let mut modifiers_held = false;
7612 if let Some(accept_keystroke) = self
7613 .accept_edit_prediction_keybind(false, window, cx)
7614 .keystroke()
7615 {
7616 modifiers_held = modifiers_held
7617 || (&accept_keystroke.modifiers == modifiers
7618 && accept_keystroke.modifiers.modified());
7619 };
7620 if let Some(accept_partial_keystroke) = self
7621 .accept_edit_prediction_keybind(true, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_partial_keystroke.modifiers == modifiers
7626 && accept_partial_keystroke.modifiers.modified());
7627 }
7628
7629 if modifiers_held {
7630 if matches!(
7631 self.edit_prediction_preview,
7632 EditPredictionPreview::Inactive { .. }
7633 ) {
7634 self.edit_prediction_preview = EditPredictionPreview::Active {
7635 previous_scroll_position: None,
7636 since: Instant::now(),
7637 };
7638
7639 self.update_visible_inline_completion(window, cx);
7640 cx.notify();
7641 }
7642 } else if let EditPredictionPreview::Active {
7643 previous_scroll_position,
7644 since,
7645 } = self.edit_prediction_preview
7646 {
7647 if let (Some(previous_scroll_position), Some(position_map)) =
7648 (previous_scroll_position, self.last_position_map.as_ref())
7649 {
7650 self.set_scroll_position(
7651 previous_scroll_position
7652 .scroll_position(&position_map.snapshot.display_snapshot),
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7659 released_too_fast: since.elapsed() < Duration::from_millis(200),
7660 };
7661 self.clear_row_highlights::<EditPredictionPreview>();
7662 self.update_visible_inline_completion(window, cx);
7663 cx.notify();
7664 }
7665 }
7666
7667 fn update_visible_inline_completion(
7668 &mut self,
7669 _window: &mut Window,
7670 cx: &mut Context<Self>,
7671 ) -> Option<()> {
7672 let selection = self.selections.newest_anchor();
7673 let cursor = selection.head();
7674 let multibuffer = self.buffer.read(cx).snapshot(cx);
7675 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7676 let excerpt_id = cursor.excerpt_id;
7677
7678 let show_in_menu = self.show_edit_predictions_in_menu();
7679 let completions_menu_has_precedence = !show_in_menu
7680 && (self.context_menu.borrow().is_some()
7681 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7682
7683 if completions_menu_has_precedence
7684 || !offset_selection.is_empty()
7685 || self
7686 .active_inline_completion
7687 .as_ref()
7688 .map_or(false, |completion| {
7689 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7690 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7691 !invalidation_range.contains(&offset_selection.head())
7692 })
7693 {
7694 self.discard_inline_completion(false, cx);
7695 return None;
7696 }
7697
7698 self.take_active_inline_completion(cx);
7699 let Some(provider) = self.edit_prediction_provider() else {
7700 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7701 return None;
7702 };
7703
7704 let (buffer, cursor_buffer_position) =
7705 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7706
7707 self.edit_prediction_settings =
7708 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7709
7710 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7711
7712 if self.edit_prediction_indent_conflict {
7713 let cursor_point = cursor.to_point(&multibuffer);
7714
7715 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7716
7717 if let Some((_, indent)) = indents.iter().next() {
7718 if indent.len == cursor_point.column {
7719 self.edit_prediction_indent_conflict = false;
7720 }
7721 }
7722 }
7723
7724 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7725 let edits = inline_completion
7726 .edits
7727 .into_iter()
7728 .flat_map(|(range, new_text)| {
7729 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7730 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7731 Some((start..end, new_text))
7732 })
7733 .collect::<Vec<_>>();
7734 if edits.is_empty() {
7735 return None;
7736 }
7737
7738 let first_edit_start = edits.first().unwrap().0.start;
7739 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7740 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7741
7742 let last_edit_end = edits.last().unwrap().0.end;
7743 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7744 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7745
7746 let cursor_row = cursor.to_point(&multibuffer).row;
7747
7748 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7749
7750 let mut inlay_ids = Vec::new();
7751 let invalidation_row_range;
7752 let move_invalidation_row_range = if cursor_row < edit_start_row {
7753 Some(cursor_row..edit_end_row)
7754 } else if cursor_row > edit_end_row {
7755 Some(edit_start_row..cursor_row)
7756 } else {
7757 None
7758 };
7759 let is_move =
7760 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7761 let completion = if is_move {
7762 invalidation_row_range =
7763 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7764 let target = first_edit_start;
7765 InlineCompletion::Move { target, snapshot }
7766 } else {
7767 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7768 && !self.inline_completions_hidden_for_vim_mode;
7769
7770 if show_completions_in_buffer {
7771 if edits
7772 .iter()
7773 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7774 {
7775 let mut inlays = Vec::new();
7776 for (range, new_text) in &edits {
7777 let inlay = Inlay::inline_completion(
7778 post_inc(&mut self.next_inlay_id),
7779 range.start,
7780 new_text.as_str(),
7781 );
7782 inlay_ids.push(inlay.id);
7783 inlays.push(inlay);
7784 }
7785
7786 self.splice_inlays(&[], inlays, cx);
7787 } else {
7788 let background_color = cx.theme().status().deleted_background;
7789 self.highlight_text::<InlineCompletionHighlight>(
7790 edits.iter().map(|(range, _)| range.clone()).collect(),
7791 HighlightStyle {
7792 background_color: Some(background_color),
7793 ..Default::default()
7794 },
7795 cx,
7796 );
7797 }
7798 }
7799
7800 invalidation_row_range = edit_start_row..edit_end_row;
7801
7802 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7803 if provider.show_tab_accept_marker() {
7804 EditDisplayMode::TabAccept
7805 } else {
7806 EditDisplayMode::Inline
7807 }
7808 } else {
7809 EditDisplayMode::DiffPopover
7810 };
7811
7812 InlineCompletion::Edit {
7813 edits,
7814 edit_preview: inline_completion.edit_preview,
7815 display_mode,
7816 snapshot,
7817 }
7818 };
7819
7820 let invalidation_range = multibuffer
7821 .anchor_before(Point::new(invalidation_row_range.start, 0))
7822 ..multibuffer.anchor_after(Point::new(
7823 invalidation_row_range.end,
7824 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7825 ));
7826
7827 self.stale_inline_completion_in_menu = None;
7828 self.active_inline_completion = Some(InlineCompletionState {
7829 inlay_ids,
7830 completion,
7831 completion_id: inline_completion.id,
7832 invalidation_range,
7833 });
7834
7835 cx.notify();
7836
7837 Some(())
7838 }
7839
7840 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7841 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7842 }
7843
7844 fn clear_tasks(&mut self) {
7845 self.tasks.clear()
7846 }
7847
7848 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7849 if self.tasks.insert(key, value).is_some() {
7850 // This case should hopefully be rare, but just in case...
7851 log::error!(
7852 "multiple different run targets found on a single line, only the last target will be rendered"
7853 )
7854 }
7855 }
7856
7857 /// Get all display points of breakpoints that will be rendered within editor
7858 ///
7859 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7860 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7861 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7862 fn active_breakpoints(
7863 &self,
7864 range: Range<DisplayRow>,
7865 window: &mut Window,
7866 cx: &mut Context<Self>,
7867 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7868 let mut breakpoint_display_points = HashMap::default();
7869
7870 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7871 return breakpoint_display_points;
7872 };
7873
7874 let snapshot = self.snapshot(window, cx);
7875
7876 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7877 let Some(project) = self.project.as_ref() else {
7878 return breakpoint_display_points;
7879 };
7880
7881 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7882 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7883
7884 for (buffer_snapshot, range, excerpt_id) in
7885 multi_buffer_snapshot.range_to_buffer_ranges(range)
7886 {
7887 let Some(buffer) = project
7888 .read(cx)
7889 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7890 else {
7891 continue;
7892 };
7893 let breakpoints = breakpoint_store.read(cx).breakpoints(
7894 &buffer,
7895 Some(
7896 buffer_snapshot.anchor_before(range.start)
7897 ..buffer_snapshot.anchor_after(range.end),
7898 ),
7899 buffer_snapshot,
7900 cx,
7901 );
7902 for (breakpoint, state) in breakpoints {
7903 let multi_buffer_anchor =
7904 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7905 let position = multi_buffer_anchor
7906 .to_point(&multi_buffer_snapshot)
7907 .to_display_point(&snapshot);
7908
7909 breakpoint_display_points.insert(
7910 position.row(),
7911 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7912 );
7913 }
7914 }
7915
7916 breakpoint_display_points
7917 }
7918
7919 fn breakpoint_context_menu(
7920 &self,
7921 anchor: Anchor,
7922 window: &mut Window,
7923 cx: &mut Context<Self>,
7924 ) -> Entity<ui::ContextMenu> {
7925 let weak_editor = cx.weak_entity();
7926 let focus_handle = self.focus_handle(cx);
7927
7928 let row = self
7929 .buffer
7930 .read(cx)
7931 .snapshot(cx)
7932 .summary_for_anchor::<Point>(&anchor)
7933 .row;
7934
7935 let breakpoint = self
7936 .breakpoint_at_row(row, window, cx)
7937 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7938
7939 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7940 "Edit Log Breakpoint"
7941 } else {
7942 "Set Log Breakpoint"
7943 };
7944
7945 let condition_breakpoint_msg = if breakpoint
7946 .as_ref()
7947 .is_some_and(|bp| bp.1.condition.is_some())
7948 {
7949 "Edit Condition Breakpoint"
7950 } else {
7951 "Set Condition Breakpoint"
7952 };
7953
7954 let hit_condition_breakpoint_msg = if breakpoint
7955 .as_ref()
7956 .is_some_and(|bp| bp.1.hit_condition.is_some())
7957 {
7958 "Edit Hit Condition Breakpoint"
7959 } else {
7960 "Set Hit Condition Breakpoint"
7961 };
7962
7963 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7964 "Unset Breakpoint"
7965 } else {
7966 "Set Breakpoint"
7967 };
7968
7969 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7970
7971 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7972 BreakpointState::Enabled => Some("Disable"),
7973 BreakpointState::Disabled => Some("Enable"),
7974 });
7975
7976 let (anchor, breakpoint) =
7977 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7978
7979 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7980 menu.on_blur_subscription(Subscription::new(|| {}))
7981 .context(focus_handle)
7982 .when(run_to_cursor, |this| {
7983 let weak_editor = weak_editor.clone();
7984 this.entry("Run to cursor", None, move |window, cx| {
7985 weak_editor
7986 .update(cx, |editor, cx| {
7987 editor.change_selections(
7988 SelectionEffects::no_scroll(),
7989 window,
7990 cx,
7991 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7992 );
7993 })
7994 .ok();
7995
7996 window.dispatch_action(Box::new(RunToCursor), cx);
7997 })
7998 .separator()
7999 })
8000 .when_some(toggle_state_msg, |this, msg| {
8001 this.entry(msg, None, {
8002 let weak_editor = weak_editor.clone();
8003 let breakpoint = breakpoint.clone();
8004 move |_window, cx| {
8005 weak_editor
8006 .update(cx, |this, cx| {
8007 this.edit_breakpoint_at_anchor(
8008 anchor,
8009 breakpoint.as_ref().clone(),
8010 BreakpointEditAction::InvertState,
8011 cx,
8012 );
8013 })
8014 .log_err();
8015 }
8016 })
8017 })
8018 .entry(set_breakpoint_msg, None, {
8019 let weak_editor = weak_editor.clone();
8020 let breakpoint = breakpoint.clone();
8021 move |_window, cx| {
8022 weak_editor
8023 .update(cx, |this, cx| {
8024 this.edit_breakpoint_at_anchor(
8025 anchor,
8026 breakpoint.as_ref().clone(),
8027 BreakpointEditAction::Toggle,
8028 cx,
8029 );
8030 })
8031 .log_err();
8032 }
8033 })
8034 .entry(log_breakpoint_msg, None, {
8035 let breakpoint = breakpoint.clone();
8036 let weak_editor = weak_editor.clone();
8037 move |window, cx| {
8038 weak_editor
8039 .update(cx, |this, cx| {
8040 this.add_edit_breakpoint_block(
8041 anchor,
8042 breakpoint.as_ref(),
8043 BreakpointPromptEditAction::Log,
8044 window,
8045 cx,
8046 );
8047 })
8048 .log_err();
8049 }
8050 })
8051 .entry(condition_breakpoint_msg, None, {
8052 let breakpoint = breakpoint.clone();
8053 let weak_editor = weak_editor.clone();
8054 move |window, cx| {
8055 weak_editor
8056 .update(cx, |this, cx| {
8057 this.add_edit_breakpoint_block(
8058 anchor,
8059 breakpoint.as_ref(),
8060 BreakpointPromptEditAction::Condition,
8061 window,
8062 cx,
8063 );
8064 })
8065 .log_err();
8066 }
8067 })
8068 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8069 weak_editor
8070 .update(cx, |this, cx| {
8071 this.add_edit_breakpoint_block(
8072 anchor,
8073 breakpoint.as_ref(),
8074 BreakpointPromptEditAction::HitCondition,
8075 window,
8076 cx,
8077 );
8078 })
8079 .log_err();
8080 })
8081 })
8082 }
8083
8084 fn render_breakpoint(
8085 &self,
8086 position: Anchor,
8087 row: DisplayRow,
8088 breakpoint: &Breakpoint,
8089 state: Option<BreakpointSessionState>,
8090 cx: &mut Context<Self>,
8091 ) -> IconButton {
8092 let is_rejected = state.is_some_and(|s| !s.verified);
8093 // Is it a breakpoint that shows up when hovering over gutter?
8094 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8095 (false, false),
8096 |PhantomBreakpointIndicator {
8097 is_active,
8098 display_row,
8099 collides_with_existing_breakpoint,
8100 }| {
8101 (
8102 is_active && display_row == row,
8103 collides_with_existing_breakpoint,
8104 )
8105 },
8106 );
8107
8108 let (color, icon) = {
8109 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8110 (false, false) => ui::IconName::DebugBreakpoint,
8111 (true, false) => ui::IconName::DebugLogBreakpoint,
8112 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8113 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8114 };
8115
8116 let color = if is_phantom {
8117 Color::Hint
8118 } else if is_rejected {
8119 Color::Disabled
8120 } else {
8121 Color::Debugger
8122 };
8123
8124 (color, icon)
8125 };
8126
8127 let breakpoint = Arc::from(breakpoint.clone());
8128
8129 let alt_as_text = gpui::Keystroke {
8130 modifiers: Modifiers::secondary_key(),
8131 ..Default::default()
8132 };
8133 let primary_action_text = if breakpoint.is_disabled() {
8134 "Enable breakpoint"
8135 } else if is_phantom && !collides_with_existing {
8136 "Set breakpoint"
8137 } else {
8138 "Unset breakpoint"
8139 };
8140 let focus_handle = self.focus_handle.clone();
8141
8142 let meta = if is_rejected {
8143 SharedString::from("No executable code is associated with this line.")
8144 } else if collides_with_existing && !breakpoint.is_disabled() {
8145 SharedString::from(format!(
8146 "{alt_as_text}-click to disable,\nright-click for more options."
8147 ))
8148 } else {
8149 SharedString::from("Right-click for more options.")
8150 };
8151 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8152 .icon_size(IconSize::XSmall)
8153 .size(ui::ButtonSize::None)
8154 .when(is_rejected, |this| {
8155 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8156 })
8157 .icon_color(color)
8158 .style(ButtonStyle::Transparent)
8159 .on_click(cx.listener({
8160 let breakpoint = breakpoint.clone();
8161
8162 move |editor, event: &ClickEvent, window, cx| {
8163 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8164 BreakpointEditAction::InvertState
8165 } else {
8166 BreakpointEditAction::Toggle
8167 };
8168
8169 window.focus(&editor.focus_handle(cx));
8170 editor.edit_breakpoint_at_anchor(
8171 position,
8172 breakpoint.as_ref().clone(),
8173 edit_action,
8174 cx,
8175 );
8176 }
8177 }))
8178 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8179 editor.set_breakpoint_context_menu(
8180 row,
8181 Some(position),
8182 event.down.position,
8183 window,
8184 cx,
8185 );
8186 }))
8187 .tooltip(move |window, cx| {
8188 Tooltip::with_meta_in(
8189 primary_action_text,
8190 Some(&ToggleBreakpoint),
8191 meta.clone(),
8192 &focus_handle,
8193 window,
8194 cx,
8195 )
8196 })
8197 }
8198
8199 fn build_tasks_context(
8200 project: &Entity<Project>,
8201 buffer: &Entity<Buffer>,
8202 buffer_row: u32,
8203 tasks: &Arc<RunnableTasks>,
8204 cx: &mut Context<Self>,
8205 ) -> Task<Option<task::TaskContext>> {
8206 let position = Point::new(buffer_row, tasks.column);
8207 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8208 let location = Location {
8209 buffer: buffer.clone(),
8210 range: range_start..range_start,
8211 };
8212 // Fill in the environmental variables from the tree-sitter captures
8213 let mut captured_task_variables = TaskVariables::default();
8214 for (capture_name, value) in tasks.extra_variables.clone() {
8215 captured_task_variables.insert(
8216 task::VariableName::Custom(capture_name.into()),
8217 value.clone(),
8218 );
8219 }
8220 project.update(cx, |project, cx| {
8221 project.task_store().update(cx, |task_store, cx| {
8222 task_store.task_context_for_location(captured_task_variables, location, cx)
8223 })
8224 })
8225 }
8226
8227 pub fn spawn_nearest_task(
8228 &mut self,
8229 action: &SpawnNearestTask,
8230 window: &mut Window,
8231 cx: &mut Context<Self>,
8232 ) {
8233 let Some((workspace, _)) = self.workspace.clone() else {
8234 return;
8235 };
8236 let Some(project) = self.project.clone() else {
8237 return;
8238 };
8239
8240 // Try to find a closest, enclosing node using tree-sitter that has a task
8241 let Some((buffer, buffer_row, tasks)) = self
8242 .find_enclosing_node_task(cx)
8243 // Or find the task that's closest in row-distance.
8244 .or_else(|| self.find_closest_task(cx))
8245 else {
8246 return;
8247 };
8248
8249 let reveal_strategy = action.reveal;
8250 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8251 cx.spawn_in(window, async move |_, cx| {
8252 let context = task_context.await?;
8253 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8254
8255 let resolved = &mut resolved_task.resolved;
8256 resolved.reveal = reveal_strategy;
8257
8258 workspace
8259 .update_in(cx, |workspace, window, cx| {
8260 workspace.schedule_resolved_task(
8261 task_source_kind,
8262 resolved_task,
8263 false,
8264 window,
8265 cx,
8266 );
8267 })
8268 .ok()
8269 })
8270 .detach();
8271 }
8272
8273 fn find_closest_task(
8274 &mut self,
8275 cx: &mut Context<Self>,
8276 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8277 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8278
8279 let ((buffer_id, row), tasks) = self
8280 .tasks
8281 .iter()
8282 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8283
8284 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8285 let tasks = Arc::new(tasks.to_owned());
8286 Some((buffer, *row, tasks))
8287 }
8288
8289 fn find_enclosing_node_task(
8290 &mut self,
8291 cx: &mut Context<Self>,
8292 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8293 let snapshot = self.buffer.read(cx).snapshot(cx);
8294 let offset = self.selections.newest::<usize>(cx).head();
8295 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8296 let buffer_id = excerpt.buffer().remote_id();
8297
8298 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8299 let mut cursor = layer.node().walk();
8300
8301 while cursor.goto_first_child_for_byte(offset).is_some() {
8302 if cursor.node().end_byte() == offset {
8303 cursor.goto_next_sibling();
8304 }
8305 }
8306
8307 // Ascend to the smallest ancestor that contains the range and has a task.
8308 loop {
8309 let node = cursor.node();
8310 let node_range = node.byte_range();
8311 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8312
8313 // Check if this node contains our offset
8314 if node_range.start <= offset && node_range.end >= offset {
8315 // If it contains offset, check for task
8316 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8317 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8318 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8319 }
8320 }
8321
8322 if !cursor.goto_parent() {
8323 break;
8324 }
8325 }
8326 None
8327 }
8328
8329 fn render_run_indicator(
8330 &self,
8331 _style: &EditorStyle,
8332 is_active: bool,
8333 row: DisplayRow,
8334 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8335 cx: &mut Context<Self>,
8336 ) -> IconButton {
8337 let color = Color::Muted;
8338 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8339
8340 IconButton::new(
8341 ("run_indicator", row.0 as usize),
8342 ui::IconName::PlayOutlined,
8343 )
8344 .shape(ui::IconButtonShape::Square)
8345 .icon_size(IconSize::XSmall)
8346 .icon_color(color)
8347 .toggle_state(is_active)
8348 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8349 let quick_launch = e.down.button == MouseButton::Left;
8350 window.focus(&editor.focus_handle(cx));
8351 editor.toggle_code_actions(
8352 &ToggleCodeActions {
8353 deployed_from: Some(CodeActionSource::RunMenu(row)),
8354 quick_launch,
8355 },
8356 window,
8357 cx,
8358 );
8359 }))
8360 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8361 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8362 }))
8363 }
8364
8365 pub fn context_menu_visible(&self) -> bool {
8366 !self.edit_prediction_preview_is_active()
8367 && self
8368 .context_menu
8369 .borrow()
8370 .as_ref()
8371 .map_or(false, |menu| menu.visible())
8372 }
8373
8374 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8375 self.context_menu
8376 .borrow()
8377 .as_ref()
8378 .map(|menu| menu.origin())
8379 }
8380
8381 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8382 self.context_menu_options = Some(options);
8383 }
8384
8385 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8386 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8387
8388 fn render_edit_prediction_popover(
8389 &mut self,
8390 text_bounds: &Bounds<Pixels>,
8391 content_origin: gpui::Point<Pixels>,
8392 right_margin: Pixels,
8393 editor_snapshot: &EditorSnapshot,
8394 visible_row_range: Range<DisplayRow>,
8395 scroll_top: f32,
8396 scroll_bottom: f32,
8397 line_layouts: &[LineWithInvisibles],
8398 line_height: Pixels,
8399 scroll_pixel_position: gpui::Point<Pixels>,
8400 newest_selection_head: Option<DisplayPoint>,
8401 editor_width: Pixels,
8402 style: &EditorStyle,
8403 window: &mut Window,
8404 cx: &mut App,
8405 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8406 if self.mode().is_minimap() {
8407 return None;
8408 }
8409 let active_inline_completion = self.active_inline_completion.as_ref()?;
8410
8411 if self.edit_prediction_visible_in_cursor_popover(true) {
8412 return None;
8413 }
8414
8415 match &active_inline_completion.completion {
8416 InlineCompletion::Move { target, .. } => {
8417 let target_display_point = target.to_display_point(editor_snapshot);
8418
8419 if self.edit_prediction_requires_modifier() {
8420 if !self.edit_prediction_preview_is_active() {
8421 return None;
8422 }
8423
8424 self.render_edit_prediction_modifier_jump_popover(
8425 text_bounds,
8426 content_origin,
8427 visible_row_range,
8428 line_layouts,
8429 line_height,
8430 scroll_pixel_position,
8431 newest_selection_head,
8432 target_display_point,
8433 window,
8434 cx,
8435 )
8436 } else {
8437 self.render_edit_prediction_eager_jump_popover(
8438 text_bounds,
8439 content_origin,
8440 editor_snapshot,
8441 visible_row_range,
8442 scroll_top,
8443 scroll_bottom,
8444 line_height,
8445 scroll_pixel_position,
8446 target_display_point,
8447 editor_width,
8448 window,
8449 cx,
8450 )
8451 }
8452 }
8453 InlineCompletion::Edit {
8454 display_mode: EditDisplayMode::Inline,
8455 ..
8456 } => None,
8457 InlineCompletion::Edit {
8458 display_mode: EditDisplayMode::TabAccept,
8459 edits,
8460 ..
8461 } => {
8462 let range = &edits.first()?.0;
8463 let target_display_point = range.end.to_display_point(editor_snapshot);
8464
8465 self.render_edit_prediction_end_of_line_popover(
8466 "Accept",
8467 editor_snapshot,
8468 visible_row_range,
8469 target_display_point,
8470 line_height,
8471 scroll_pixel_position,
8472 content_origin,
8473 editor_width,
8474 window,
8475 cx,
8476 )
8477 }
8478 InlineCompletion::Edit {
8479 edits,
8480 edit_preview,
8481 display_mode: EditDisplayMode::DiffPopover,
8482 snapshot,
8483 } => self.render_edit_prediction_diff_popover(
8484 text_bounds,
8485 content_origin,
8486 right_margin,
8487 editor_snapshot,
8488 visible_row_range,
8489 line_layouts,
8490 line_height,
8491 scroll_pixel_position,
8492 newest_selection_head,
8493 editor_width,
8494 style,
8495 edits,
8496 edit_preview,
8497 snapshot,
8498 window,
8499 cx,
8500 ),
8501 }
8502 }
8503
8504 fn render_edit_prediction_modifier_jump_popover(
8505 &mut self,
8506 text_bounds: &Bounds<Pixels>,
8507 content_origin: gpui::Point<Pixels>,
8508 visible_row_range: Range<DisplayRow>,
8509 line_layouts: &[LineWithInvisibles],
8510 line_height: Pixels,
8511 scroll_pixel_position: gpui::Point<Pixels>,
8512 newest_selection_head: Option<DisplayPoint>,
8513 target_display_point: DisplayPoint,
8514 window: &mut Window,
8515 cx: &mut App,
8516 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8517 let scrolled_content_origin =
8518 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8519
8520 const SCROLL_PADDING_Y: Pixels = px(12.);
8521
8522 if target_display_point.row() < visible_row_range.start {
8523 return self.render_edit_prediction_scroll_popover(
8524 |_| SCROLL_PADDING_Y,
8525 IconName::ArrowUp,
8526 visible_row_range,
8527 line_layouts,
8528 newest_selection_head,
8529 scrolled_content_origin,
8530 window,
8531 cx,
8532 );
8533 } else if target_display_point.row() >= visible_row_range.end {
8534 return self.render_edit_prediction_scroll_popover(
8535 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8536 IconName::ArrowDown,
8537 visible_row_range,
8538 line_layouts,
8539 newest_selection_head,
8540 scrolled_content_origin,
8541 window,
8542 cx,
8543 );
8544 }
8545
8546 const POLE_WIDTH: Pixels = px(2.);
8547
8548 let line_layout =
8549 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8550 let target_column = target_display_point.column() as usize;
8551
8552 let target_x = line_layout.x_for_index(target_column);
8553 let target_y =
8554 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8555
8556 let flag_on_right = target_x < text_bounds.size.width / 2.;
8557
8558 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8559 border_color.l += 0.001;
8560
8561 let mut element = v_flex()
8562 .items_end()
8563 .when(flag_on_right, |el| el.items_start())
8564 .child(if flag_on_right {
8565 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8566 .rounded_bl(px(0.))
8567 .rounded_tl(px(0.))
8568 .border_l_2()
8569 .border_color(border_color)
8570 } else {
8571 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8572 .rounded_br(px(0.))
8573 .rounded_tr(px(0.))
8574 .border_r_2()
8575 .border_color(border_color)
8576 })
8577 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8578 .into_any();
8579
8580 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8581
8582 let mut origin = scrolled_content_origin + point(target_x, target_y)
8583 - point(
8584 if flag_on_right {
8585 POLE_WIDTH
8586 } else {
8587 size.width - POLE_WIDTH
8588 },
8589 size.height - line_height,
8590 );
8591
8592 origin.x = origin.x.max(content_origin.x);
8593
8594 element.prepaint_at(origin, window, cx);
8595
8596 Some((element, origin))
8597 }
8598
8599 fn render_edit_prediction_scroll_popover(
8600 &mut self,
8601 to_y: impl Fn(Size<Pixels>) -> Pixels,
8602 scroll_icon: IconName,
8603 visible_row_range: Range<DisplayRow>,
8604 line_layouts: &[LineWithInvisibles],
8605 newest_selection_head: Option<DisplayPoint>,
8606 scrolled_content_origin: gpui::Point<Pixels>,
8607 window: &mut Window,
8608 cx: &mut App,
8609 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8610 let mut element = self
8611 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8612 .into_any();
8613
8614 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8615
8616 let cursor = newest_selection_head?;
8617 let cursor_row_layout =
8618 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8619 let cursor_column = cursor.column() as usize;
8620
8621 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8622
8623 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8624
8625 element.prepaint_at(origin, window, cx);
8626 Some((element, origin))
8627 }
8628
8629 fn render_edit_prediction_eager_jump_popover(
8630 &mut self,
8631 text_bounds: &Bounds<Pixels>,
8632 content_origin: gpui::Point<Pixels>,
8633 editor_snapshot: &EditorSnapshot,
8634 visible_row_range: Range<DisplayRow>,
8635 scroll_top: f32,
8636 scroll_bottom: f32,
8637 line_height: Pixels,
8638 scroll_pixel_position: gpui::Point<Pixels>,
8639 target_display_point: DisplayPoint,
8640 editor_width: Pixels,
8641 window: &mut Window,
8642 cx: &mut App,
8643 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8644 if target_display_point.row().as_f32() < scroll_top {
8645 let mut element = self
8646 .render_edit_prediction_line_popover(
8647 "Jump to Edit",
8648 Some(IconName::ArrowUp),
8649 window,
8650 cx,
8651 )?
8652 .into_any();
8653
8654 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8655 let offset = point(
8656 (text_bounds.size.width - size.width) / 2.,
8657 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8658 );
8659
8660 let origin = text_bounds.origin + offset;
8661 element.prepaint_at(origin, window, cx);
8662 Some((element, origin))
8663 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8664 let mut element = self
8665 .render_edit_prediction_line_popover(
8666 "Jump to Edit",
8667 Some(IconName::ArrowDown),
8668 window,
8669 cx,
8670 )?
8671 .into_any();
8672
8673 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8674 let offset = point(
8675 (text_bounds.size.width - size.width) / 2.,
8676 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8677 );
8678
8679 let origin = text_bounds.origin + offset;
8680 element.prepaint_at(origin, window, cx);
8681 Some((element, origin))
8682 } else {
8683 self.render_edit_prediction_end_of_line_popover(
8684 "Jump to Edit",
8685 editor_snapshot,
8686 visible_row_range,
8687 target_display_point,
8688 line_height,
8689 scroll_pixel_position,
8690 content_origin,
8691 editor_width,
8692 window,
8693 cx,
8694 )
8695 }
8696 }
8697
8698 fn render_edit_prediction_end_of_line_popover(
8699 self: &mut Editor,
8700 label: &'static str,
8701 editor_snapshot: &EditorSnapshot,
8702 visible_row_range: Range<DisplayRow>,
8703 target_display_point: DisplayPoint,
8704 line_height: Pixels,
8705 scroll_pixel_position: gpui::Point<Pixels>,
8706 content_origin: gpui::Point<Pixels>,
8707 editor_width: Pixels,
8708 window: &mut Window,
8709 cx: &mut App,
8710 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8711 let target_line_end = DisplayPoint::new(
8712 target_display_point.row(),
8713 editor_snapshot.line_len(target_display_point.row()),
8714 );
8715
8716 let mut element = self
8717 .render_edit_prediction_line_popover(label, None, window, cx)?
8718 .into_any();
8719
8720 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8721
8722 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8723
8724 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8725 let mut origin = start_point
8726 + line_origin
8727 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8728 origin.x = origin.x.max(content_origin.x);
8729
8730 let max_x = content_origin.x + editor_width - size.width;
8731
8732 if origin.x > max_x {
8733 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8734
8735 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8736 origin.y += offset;
8737 IconName::ArrowUp
8738 } else {
8739 origin.y -= offset;
8740 IconName::ArrowDown
8741 };
8742
8743 element = self
8744 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8745 .into_any();
8746
8747 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8748
8749 origin.x = content_origin.x + editor_width - size.width - px(2.);
8750 }
8751
8752 element.prepaint_at(origin, window, cx);
8753 Some((element, origin))
8754 }
8755
8756 fn render_edit_prediction_diff_popover(
8757 self: &Editor,
8758 text_bounds: &Bounds<Pixels>,
8759 content_origin: gpui::Point<Pixels>,
8760 right_margin: Pixels,
8761 editor_snapshot: &EditorSnapshot,
8762 visible_row_range: Range<DisplayRow>,
8763 line_layouts: &[LineWithInvisibles],
8764 line_height: Pixels,
8765 scroll_pixel_position: gpui::Point<Pixels>,
8766 newest_selection_head: Option<DisplayPoint>,
8767 editor_width: Pixels,
8768 style: &EditorStyle,
8769 edits: &Vec<(Range<Anchor>, String)>,
8770 edit_preview: &Option<language::EditPreview>,
8771 snapshot: &language::BufferSnapshot,
8772 window: &mut Window,
8773 cx: &mut App,
8774 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8775 let edit_start = edits
8776 .first()
8777 .unwrap()
8778 .0
8779 .start
8780 .to_display_point(editor_snapshot);
8781 let edit_end = edits
8782 .last()
8783 .unwrap()
8784 .0
8785 .end
8786 .to_display_point(editor_snapshot);
8787
8788 let is_visible = visible_row_range.contains(&edit_start.row())
8789 || visible_row_range.contains(&edit_end.row());
8790 if !is_visible {
8791 return None;
8792 }
8793
8794 let highlighted_edits =
8795 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8796
8797 let styled_text = highlighted_edits.to_styled_text(&style.text);
8798 let line_count = highlighted_edits.text.lines().count();
8799
8800 const BORDER_WIDTH: Pixels = px(1.);
8801
8802 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8803 let has_keybind = keybind.is_some();
8804
8805 let mut element = h_flex()
8806 .items_start()
8807 .child(
8808 h_flex()
8809 .bg(cx.theme().colors().editor_background)
8810 .border(BORDER_WIDTH)
8811 .shadow_xs()
8812 .border_color(cx.theme().colors().border)
8813 .rounded_l_lg()
8814 .when(line_count > 1, |el| el.rounded_br_lg())
8815 .pr_1()
8816 .child(styled_text),
8817 )
8818 .child(
8819 h_flex()
8820 .h(line_height + BORDER_WIDTH * 2.)
8821 .px_1p5()
8822 .gap_1()
8823 // Workaround: For some reason, there's a gap if we don't do this
8824 .ml(-BORDER_WIDTH)
8825 .shadow(vec![gpui::BoxShadow {
8826 color: gpui::black().opacity(0.05),
8827 offset: point(px(1.), px(1.)),
8828 blur_radius: px(2.),
8829 spread_radius: px(0.),
8830 }])
8831 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8832 .border(BORDER_WIDTH)
8833 .border_color(cx.theme().colors().border)
8834 .rounded_r_lg()
8835 .id("edit_prediction_diff_popover_keybind")
8836 .when(!has_keybind, |el| {
8837 let status_colors = cx.theme().status();
8838
8839 el.bg(status_colors.error_background)
8840 .border_color(status_colors.error.opacity(0.6))
8841 .child(Icon::new(IconName::Info).color(Color::Error))
8842 .cursor_default()
8843 .hoverable_tooltip(move |_window, cx| {
8844 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8845 })
8846 })
8847 .children(keybind),
8848 )
8849 .into_any();
8850
8851 let longest_row =
8852 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8853 let longest_line_width = if visible_row_range.contains(&longest_row) {
8854 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8855 } else {
8856 layout_line(
8857 longest_row,
8858 editor_snapshot,
8859 style,
8860 editor_width,
8861 |_| false,
8862 window,
8863 cx,
8864 )
8865 .width
8866 };
8867
8868 let viewport_bounds =
8869 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8870 right: -right_margin,
8871 ..Default::default()
8872 });
8873
8874 let x_after_longest =
8875 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8876 - scroll_pixel_position.x;
8877
8878 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 // Fully visible if it can be displayed within the window (allow overlapping other
8881 // panes). However, this is only allowed if the popover starts within text_bounds.
8882 let can_position_to_the_right = x_after_longest < text_bounds.right()
8883 && x_after_longest + element_bounds.width < viewport_bounds.right();
8884
8885 let mut origin = if can_position_to_the_right {
8886 point(
8887 x_after_longest,
8888 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8889 - scroll_pixel_position.y,
8890 )
8891 } else {
8892 let cursor_row = newest_selection_head.map(|head| head.row());
8893 let above_edit = edit_start
8894 .row()
8895 .0
8896 .checked_sub(line_count as u32)
8897 .map(DisplayRow);
8898 let below_edit = Some(edit_end.row() + 1);
8899 let above_cursor =
8900 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8901 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8902
8903 // Place the edit popover adjacent to the edit if there is a location
8904 // available that is onscreen and does not obscure the cursor. Otherwise,
8905 // place it adjacent to the cursor.
8906 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8907 .into_iter()
8908 .flatten()
8909 .find(|&start_row| {
8910 let end_row = start_row + line_count as u32;
8911 visible_row_range.contains(&start_row)
8912 && visible_row_range.contains(&end_row)
8913 && cursor_row.map_or(true, |cursor_row| {
8914 !((start_row..end_row).contains(&cursor_row))
8915 })
8916 })?;
8917
8918 content_origin
8919 + point(
8920 -scroll_pixel_position.x,
8921 row_target.as_f32() * line_height - scroll_pixel_position.y,
8922 )
8923 };
8924
8925 origin.x -= BORDER_WIDTH;
8926
8927 window.defer_draw(element, origin, 1);
8928
8929 // Do not return an element, since it will already be drawn due to defer_draw.
8930 None
8931 }
8932
8933 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8934 px(30.)
8935 }
8936
8937 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8938 if self.read_only(cx) {
8939 cx.theme().players().read_only()
8940 } else {
8941 self.style.as_ref().unwrap().local_player
8942 }
8943 }
8944
8945 fn render_edit_prediction_accept_keybind(
8946 &self,
8947 window: &mut Window,
8948 cx: &App,
8949 ) -> Option<AnyElement> {
8950 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8951 let accept_keystroke = accept_binding.keystroke()?;
8952
8953 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8954
8955 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8956 Color::Accent
8957 } else {
8958 Color::Muted
8959 };
8960
8961 h_flex()
8962 .px_0p5()
8963 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8964 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8965 .text_size(TextSize::XSmall.rems(cx))
8966 .child(h_flex().children(ui::render_modifiers(
8967 &accept_keystroke.modifiers,
8968 PlatformStyle::platform(),
8969 Some(modifiers_color),
8970 Some(IconSize::XSmall.rems().into()),
8971 true,
8972 )))
8973 .when(is_platform_style_mac, |parent| {
8974 parent.child(accept_keystroke.key.clone())
8975 })
8976 .when(!is_platform_style_mac, |parent| {
8977 parent.child(
8978 Key::new(
8979 util::capitalize(&accept_keystroke.key),
8980 Some(Color::Default),
8981 )
8982 .size(Some(IconSize::XSmall.rems().into())),
8983 )
8984 })
8985 .into_any()
8986 .into()
8987 }
8988
8989 fn render_edit_prediction_line_popover(
8990 &self,
8991 label: impl Into<SharedString>,
8992 icon: Option<IconName>,
8993 window: &mut Window,
8994 cx: &App,
8995 ) -> Option<Stateful<Div>> {
8996 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8997
8998 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8999 let has_keybind = keybind.is_some();
9000
9001 let result = h_flex()
9002 .id("ep-line-popover")
9003 .py_0p5()
9004 .pl_1()
9005 .pr(padding_right)
9006 .gap_1()
9007 .rounded_md()
9008 .border_1()
9009 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9010 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9011 .shadow_xs()
9012 .when(!has_keybind, |el| {
9013 let status_colors = cx.theme().status();
9014
9015 el.bg(status_colors.error_background)
9016 .border_color(status_colors.error.opacity(0.6))
9017 .pl_2()
9018 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9019 .cursor_default()
9020 .hoverable_tooltip(move |_window, cx| {
9021 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9022 })
9023 })
9024 .children(keybind)
9025 .child(
9026 Label::new(label)
9027 .size(LabelSize::Small)
9028 .when(!has_keybind, |el| {
9029 el.color(cx.theme().status().error.into()).strikethrough()
9030 }),
9031 )
9032 .when(!has_keybind, |el| {
9033 el.child(
9034 h_flex().ml_1().child(
9035 Icon::new(IconName::Info)
9036 .size(IconSize::Small)
9037 .color(cx.theme().status().error.into()),
9038 ),
9039 )
9040 })
9041 .when_some(icon, |element, icon| {
9042 element.child(
9043 div()
9044 .mt(px(1.5))
9045 .child(Icon::new(icon).size(IconSize::Small)),
9046 )
9047 });
9048
9049 Some(result)
9050 }
9051
9052 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9053 let accent_color = cx.theme().colors().text_accent;
9054 let editor_bg_color = cx.theme().colors().editor_background;
9055 editor_bg_color.blend(accent_color.opacity(0.1))
9056 }
9057
9058 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9059 let accent_color = cx.theme().colors().text_accent;
9060 let editor_bg_color = cx.theme().colors().editor_background;
9061 editor_bg_color.blend(accent_color.opacity(0.6))
9062 }
9063
9064 fn render_edit_prediction_cursor_popover(
9065 &self,
9066 min_width: Pixels,
9067 max_width: Pixels,
9068 cursor_point: Point,
9069 style: &EditorStyle,
9070 accept_keystroke: Option<&gpui::Keystroke>,
9071 _window: &Window,
9072 cx: &mut Context<Editor>,
9073 ) -> Option<AnyElement> {
9074 let provider = self.edit_prediction_provider.as_ref()?;
9075
9076 if provider.provider.needs_terms_acceptance(cx) {
9077 return Some(
9078 h_flex()
9079 .min_w(min_width)
9080 .flex_1()
9081 .px_2()
9082 .py_1()
9083 .gap_3()
9084 .elevation_2(cx)
9085 .hover(|style| style.bg(cx.theme().colors().element_hover))
9086 .id("accept-terms")
9087 .cursor_pointer()
9088 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9089 .on_click(cx.listener(|this, _event, window, cx| {
9090 cx.stop_propagation();
9091 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9092 window.dispatch_action(
9093 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9094 cx,
9095 );
9096 }))
9097 .child(
9098 h_flex()
9099 .flex_1()
9100 .gap_2()
9101 .child(Icon::new(IconName::ZedPredict))
9102 .child(Label::new("Accept Terms of Service"))
9103 .child(div().w_full())
9104 .child(
9105 Icon::new(IconName::ArrowUpRight)
9106 .color(Color::Muted)
9107 .size(IconSize::Small),
9108 )
9109 .into_any_element(),
9110 )
9111 .into_any(),
9112 );
9113 }
9114
9115 let is_refreshing = provider.provider.is_refreshing(cx);
9116
9117 fn pending_completion_container() -> Div {
9118 h_flex()
9119 .h_full()
9120 .flex_1()
9121 .gap_2()
9122 .child(Icon::new(IconName::ZedPredict))
9123 }
9124
9125 let completion = match &self.active_inline_completion {
9126 Some(prediction) => {
9127 if !self.has_visible_completions_menu() {
9128 const RADIUS: Pixels = px(6.);
9129 const BORDER_WIDTH: Pixels = px(1.);
9130
9131 return Some(
9132 h_flex()
9133 .elevation_2(cx)
9134 .border(BORDER_WIDTH)
9135 .border_color(cx.theme().colors().border)
9136 .when(accept_keystroke.is_none(), |el| {
9137 el.border_color(cx.theme().status().error)
9138 })
9139 .rounded(RADIUS)
9140 .rounded_tl(px(0.))
9141 .overflow_hidden()
9142 .child(div().px_1p5().child(match &prediction.completion {
9143 InlineCompletion::Move { target, snapshot } => {
9144 use text::ToPoint as _;
9145 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9146 {
9147 Icon::new(IconName::ZedPredictDown)
9148 } else {
9149 Icon::new(IconName::ZedPredictUp)
9150 }
9151 }
9152 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9153 }))
9154 .child(
9155 h_flex()
9156 .gap_1()
9157 .py_1()
9158 .px_2()
9159 .rounded_r(RADIUS - BORDER_WIDTH)
9160 .border_l_1()
9161 .border_color(cx.theme().colors().border)
9162 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9163 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9164 el.child(
9165 Label::new("Hold")
9166 .size(LabelSize::Small)
9167 .when(accept_keystroke.is_none(), |el| {
9168 el.strikethrough()
9169 })
9170 .line_height_style(LineHeightStyle::UiLabel),
9171 )
9172 })
9173 .id("edit_prediction_cursor_popover_keybind")
9174 .when(accept_keystroke.is_none(), |el| {
9175 let status_colors = cx.theme().status();
9176
9177 el.bg(status_colors.error_background)
9178 .border_color(status_colors.error.opacity(0.6))
9179 .child(Icon::new(IconName::Info).color(Color::Error))
9180 .cursor_default()
9181 .hoverable_tooltip(move |_window, cx| {
9182 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9183 .into()
9184 })
9185 })
9186 .when_some(
9187 accept_keystroke.as_ref(),
9188 |el, accept_keystroke| {
9189 el.child(h_flex().children(ui::render_modifiers(
9190 &accept_keystroke.modifiers,
9191 PlatformStyle::platform(),
9192 Some(Color::Default),
9193 Some(IconSize::XSmall.rems().into()),
9194 false,
9195 )))
9196 },
9197 ),
9198 )
9199 .into_any(),
9200 );
9201 }
9202
9203 self.render_edit_prediction_cursor_popover_preview(
9204 prediction,
9205 cursor_point,
9206 style,
9207 cx,
9208 )?
9209 }
9210
9211 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9212 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9213 stale_completion,
9214 cursor_point,
9215 style,
9216 cx,
9217 )?,
9218
9219 None => {
9220 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9221 }
9222 },
9223
9224 None => pending_completion_container().child(Label::new("No Prediction")),
9225 };
9226
9227 let completion = if is_refreshing {
9228 completion
9229 .with_animation(
9230 "loading-completion",
9231 Animation::new(Duration::from_secs(2))
9232 .repeat()
9233 .with_easing(pulsating_between(0.4, 0.8)),
9234 |label, delta| label.opacity(delta),
9235 )
9236 .into_any_element()
9237 } else {
9238 completion.into_any_element()
9239 };
9240
9241 let has_completion = self.active_inline_completion.is_some();
9242
9243 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9244 Some(
9245 h_flex()
9246 .min_w(min_width)
9247 .max_w(max_width)
9248 .flex_1()
9249 .elevation_2(cx)
9250 .border_color(cx.theme().colors().border)
9251 .child(
9252 div()
9253 .flex_1()
9254 .py_1()
9255 .px_2()
9256 .overflow_hidden()
9257 .child(completion),
9258 )
9259 .when_some(accept_keystroke, |el, accept_keystroke| {
9260 if !accept_keystroke.modifiers.modified() {
9261 return el;
9262 }
9263
9264 el.child(
9265 h_flex()
9266 .h_full()
9267 .border_l_1()
9268 .rounded_r_lg()
9269 .border_color(cx.theme().colors().border)
9270 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9271 .gap_1()
9272 .py_1()
9273 .px_2()
9274 .child(
9275 h_flex()
9276 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9277 .when(is_platform_style_mac, |parent| parent.gap_1())
9278 .child(h_flex().children(ui::render_modifiers(
9279 &accept_keystroke.modifiers,
9280 PlatformStyle::platform(),
9281 Some(if !has_completion {
9282 Color::Muted
9283 } else {
9284 Color::Default
9285 }),
9286 None,
9287 false,
9288 ))),
9289 )
9290 .child(Label::new("Preview").into_any_element())
9291 .opacity(if has_completion { 1.0 } else { 0.4 }),
9292 )
9293 })
9294 .into_any(),
9295 )
9296 }
9297
9298 fn render_edit_prediction_cursor_popover_preview(
9299 &self,
9300 completion: &InlineCompletionState,
9301 cursor_point: Point,
9302 style: &EditorStyle,
9303 cx: &mut Context<Editor>,
9304 ) -> Option<Div> {
9305 use text::ToPoint as _;
9306
9307 fn render_relative_row_jump(
9308 prefix: impl Into<String>,
9309 current_row: u32,
9310 target_row: u32,
9311 ) -> Div {
9312 let (row_diff, arrow) = if target_row < current_row {
9313 (current_row - target_row, IconName::ArrowUp)
9314 } else {
9315 (target_row - current_row, IconName::ArrowDown)
9316 };
9317
9318 h_flex()
9319 .child(
9320 Label::new(format!("{}{}", prefix.into(), row_diff))
9321 .color(Color::Muted)
9322 .size(LabelSize::Small),
9323 )
9324 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9325 }
9326
9327 match &completion.completion {
9328 InlineCompletion::Move {
9329 target, snapshot, ..
9330 } => Some(
9331 h_flex()
9332 .px_2()
9333 .gap_2()
9334 .flex_1()
9335 .child(
9336 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9337 Icon::new(IconName::ZedPredictDown)
9338 } else {
9339 Icon::new(IconName::ZedPredictUp)
9340 },
9341 )
9342 .child(Label::new("Jump to Edit")),
9343 ),
9344
9345 InlineCompletion::Edit {
9346 edits,
9347 edit_preview,
9348 snapshot,
9349 display_mode: _,
9350 } => {
9351 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9352
9353 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9354 &snapshot,
9355 &edits,
9356 edit_preview.as_ref()?,
9357 true,
9358 cx,
9359 )
9360 .first_line_preview();
9361
9362 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9363 .with_default_highlights(&style.text, highlighted_edits.highlights);
9364
9365 let preview = h_flex()
9366 .gap_1()
9367 .min_w_16()
9368 .child(styled_text)
9369 .when(has_more_lines, |parent| parent.child("…"));
9370
9371 let left = if first_edit_row != cursor_point.row {
9372 render_relative_row_jump("", cursor_point.row, first_edit_row)
9373 .into_any_element()
9374 } else {
9375 Icon::new(IconName::ZedPredict).into_any_element()
9376 };
9377
9378 Some(
9379 h_flex()
9380 .h_full()
9381 .flex_1()
9382 .gap_2()
9383 .pr_1()
9384 .overflow_x_hidden()
9385 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9386 .child(left)
9387 .child(preview),
9388 )
9389 }
9390 }
9391 }
9392
9393 pub fn render_context_menu(
9394 &self,
9395 style: &EditorStyle,
9396 max_height_in_lines: u32,
9397 window: &mut Window,
9398 cx: &mut Context<Editor>,
9399 ) -> Option<AnyElement> {
9400 let menu = self.context_menu.borrow();
9401 let menu = menu.as_ref()?;
9402 if !menu.visible() {
9403 return None;
9404 };
9405 Some(menu.render(style, max_height_in_lines, window, cx))
9406 }
9407
9408 fn render_context_menu_aside(
9409 &mut self,
9410 max_size: Size<Pixels>,
9411 window: &mut Window,
9412 cx: &mut Context<Editor>,
9413 ) -> Option<AnyElement> {
9414 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9415 if menu.visible() {
9416 menu.render_aside(max_size, window, cx)
9417 } else {
9418 None
9419 }
9420 })
9421 }
9422
9423 fn hide_context_menu(
9424 &mut self,
9425 window: &mut Window,
9426 cx: &mut Context<Self>,
9427 ) -> Option<CodeContextMenu> {
9428 cx.notify();
9429 self.completion_tasks.clear();
9430 let context_menu = self.context_menu.borrow_mut().take();
9431 self.stale_inline_completion_in_menu.take();
9432 self.update_visible_inline_completion(window, cx);
9433 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9434 if let Some(completion_provider) = &self.completion_provider {
9435 completion_provider.selection_changed(None, window, cx);
9436 }
9437 }
9438 context_menu
9439 }
9440
9441 fn show_snippet_choices(
9442 &mut self,
9443 choices: &Vec<String>,
9444 selection: Range<Anchor>,
9445 cx: &mut Context<Self>,
9446 ) {
9447 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9448 (Some(a), Some(b)) if a == b => a,
9449 _ => {
9450 log::error!("expected anchor range to have matching buffer IDs");
9451 return;
9452 }
9453 };
9454 let multi_buffer = self.buffer().read(cx);
9455 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9456 return;
9457 };
9458
9459 let id = post_inc(&mut self.next_completion_id);
9460 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9461 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9462 CompletionsMenu::new_snippet_choices(
9463 id,
9464 true,
9465 choices,
9466 selection,
9467 buffer,
9468 snippet_sort_order,
9469 ),
9470 ));
9471 }
9472
9473 pub fn insert_snippet(
9474 &mut self,
9475 insertion_ranges: &[Range<usize>],
9476 snippet: Snippet,
9477 window: &mut Window,
9478 cx: &mut Context<Self>,
9479 ) -> Result<()> {
9480 struct Tabstop<T> {
9481 is_end_tabstop: bool,
9482 ranges: Vec<Range<T>>,
9483 choices: Option<Vec<String>>,
9484 }
9485
9486 let tabstops = self.buffer.update(cx, |buffer, cx| {
9487 let snippet_text: Arc<str> = snippet.text.clone().into();
9488 let edits = insertion_ranges
9489 .iter()
9490 .cloned()
9491 .map(|range| (range, snippet_text.clone()));
9492 let autoindent_mode = AutoindentMode::Block {
9493 original_indent_columns: Vec::new(),
9494 };
9495 buffer.edit(edits, Some(autoindent_mode), cx);
9496
9497 let snapshot = &*buffer.read(cx);
9498 let snippet = &snippet;
9499 snippet
9500 .tabstops
9501 .iter()
9502 .map(|tabstop| {
9503 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9504 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9505 });
9506 let mut tabstop_ranges = tabstop
9507 .ranges
9508 .iter()
9509 .flat_map(|tabstop_range| {
9510 let mut delta = 0_isize;
9511 insertion_ranges.iter().map(move |insertion_range| {
9512 let insertion_start = insertion_range.start as isize + delta;
9513 delta +=
9514 snippet.text.len() as isize - insertion_range.len() as isize;
9515
9516 let start = ((insertion_start + tabstop_range.start) as usize)
9517 .min(snapshot.len());
9518 let end = ((insertion_start + tabstop_range.end) as usize)
9519 .min(snapshot.len());
9520 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9521 })
9522 })
9523 .collect::<Vec<_>>();
9524 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9525
9526 Tabstop {
9527 is_end_tabstop,
9528 ranges: tabstop_ranges,
9529 choices: tabstop.choices.clone(),
9530 }
9531 })
9532 .collect::<Vec<_>>()
9533 });
9534 if let Some(tabstop) = tabstops.first() {
9535 self.change_selections(Default::default(), window, cx, |s| {
9536 // Reverse order so that the first range is the newest created selection.
9537 // Completions will use it and autoscroll will prioritize it.
9538 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9539 });
9540
9541 if let Some(choices) = &tabstop.choices {
9542 if let Some(selection) = tabstop.ranges.first() {
9543 self.show_snippet_choices(choices, selection.clone(), cx)
9544 }
9545 }
9546
9547 // If we're already at the last tabstop and it's at the end of the snippet,
9548 // we're done, we don't need to keep the state around.
9549 if !tabstop.is_end_tabstop {
9550 let choices = tabstops
9551 .iter()
9552 .map(|tabstop| tabstop.choices.clone())
9553 .collect();
9554
9555 let ranges = tabstops
9556 .into_iter()
9557 .map(|tabstop| tabstop.ranges)
9558 .collect::<Vec<_>>();
9559
9560 self.snippet_stack.push(SnippetState {
9561 active_index: 0,
9562 ranges,
9563 choices,
9564 });
9565 }
9566
9567 // Check whether the just-entered snippet ends with an auto-closable bracket.
9568 if self.autoclose_regions.is_empty() {
9569 let snapshot = self.buffer.read(cx).snapshot(cx);
9570 let mut all_selections = self.selections.all::<Point>(cx);
9571 for selection in &mut all_selections {
9572 let selection_head = selection.head();
9573 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9574 continue;
9575 };
9576
9577 let mut bracket_pair = None;
9578 let max_lookup_length = scope
9579 .brackets()
9580 .map(|(pair, _)| {
9581 pair.start
9582 .as_str()
9583 .chars()
9584 .count()
9585 .max(pair.end.as_str().chars().count())
9586 })
9587 .max();
9588 if let Some(max_lookup_length) = max_lookup_length {
9589 let next_text = snapshot
9590 .chars_at(selection_head)
9591 .take(max_lookup_length)
9592 .collect::<String>();
9593 let prev_text = snapshot
9594 .reversed_chars_at(selection_head)
9595 .take(max_lookup_length)
9596 .collect::<String>();
9597
9598 for (pair, enabled) in scope.brackets() {
9599 if enabled
9600 && pair.close
9601 && prev_text.starts_with(pair.start.as_str())
9602 && next_text.starts_with(pair.end.as_str())
9603 {
9604 bracket_pair = Some(pair.clone());
9605 break;
9606 }
9607 }
9608 }
9609
9610 if let Some(pair) = bracket_pair {
9611 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9612 let autoclose_enabled =
9613 self.use_autoclose && snapshot_settings.use_autoclose;
9614 if autoclose_enabled {
9615 let start = snapshot.anchor_after(selection_head);
9616 let end = snapshot.anchor_after(selection_head);
9617 self.autoclose_regions.push(AutocloseRegion {
9618 selection_id: selection.id,
9619 range: start..end,
9620 pair,
9621 });
9622 }
9623 }
9624 }
9625 }
9626 }
9627 Ok(())
9628 }
9629
9630 pub fn move_to_next_snippet_tabstop(
9631 &mut self,
9632 window: &mut Window,
9633 cx: &mut Context<Self>,
9634 ) -> bool {
9635 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9636 }
9637
9638 pub fn move_to_prev_snippet_tabstop(
9639 &mut self,
9640 window: &mut Window,
9641 cx: &mut Context<Self>,
9642 ) -> bool {
9643 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9644 }
9645
9646 pub fn move_to_snippet_tabstop(
9647 &mut self,
9648 bias: Bias,
9649 window: &mut Window,
9650 cx: &mut Context<Self>,
9651 ) -> bool {
9652 if let Some(mut snippet) = self.snippet_stack.pop() {
9653 match bias {
9654 Bias::Left => {
9655 if snippet.active_index > 0 {
9656 snippet.active_index -= 1;
9657 } else {
9658 self.snippet_stack.push(snippet);
9659 return false;
9660 }
9661 }
9662 Bias::Right => {
9663 if snippet.active_index + 1 < snippet.ranges.len() {
9664 snippet.active_index += 1;
9665 } else {
9666 self.snippet_stack.push(snippet);
9667 return false;
9668 }
9669 }
9670 }
9671 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9672 self.change_selections(Default::default(), window, cx, |s| {
9673 // Reverse order so that the first range is the newest created selection.
9674 // Completions will use it and autoscroll will prioritize it.
9675 s.select_ranges(current_ranges.iter().rev().cloned())
9676 });
9677
9678 if let Some(choices) = &snippet.choices[snippet.active_index] {
9679 if let Some(selection) = current_ranges.first() {
9680 self.show_snippet_choices(&choices, selection.clone(), cx);
9681 }
9682 }
9683
9684 // If snippet state is not at the last tabstop, push it back on the stack
9685 if snippet.active_index + 1 < snippet.ranges.len() {
9686 self.snippet_stack.push(snippet);
9687 }
9688 return true;
9689 }
9690 }
9691
9692 false
9693 }
9694
9695 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9696 self.transact(window, cx, |this, window, cx| {
9697 this.select_all(&SelectAll, window, cx);
9698 this.insert("", window, cx);
9699 });
9700 }
9701
9702 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9704 self.transact(window, cx, |this, window, cx| {
9705 this.select_autoclose_pair(window, cx);
9706 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9707 if !this.linked_edit_ranges.is_empty() {
9708 let selections = this.selections.all::<MultiBufferPoint>(cx);
9709 let snapshot = this.buffer.read(cx).snapshot(cx);
9710
9711 for selection in selections.iter() {
9712 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9713 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9714 if selection_start.buffer_id != selection_end.buffer_id {
9715 continue;
9716 }
9717 if let Some(ranges) =
9718 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9719 {
9720 for (buffer, entries) in ranges {
9721 linked_ranges.entry(buffer).or_default().extend(entries);
9722 }
9723 }
9724 }
9725 }
9726
9727 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9728 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9729 for selection in &mut selections {
9730 if selection.is_empty() {
9731 let old_head = selection.head();
9732 let mut new_head =
9733 movement::left(&display_map, old_head.to_display_point(&display_map))
9734 .to_point(&display_map);
9735 if let Some((buffer, line_buffer_range)) = display_map
9736 .buffer_snapshot
9737 .buffer_line_for_row(MultiBufferRow(old_head.row))
9738 {
9739 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9740 let indent_len = match indent_size.kind {
9741 IndentKind::Space => {
9742 buffer.settings_at(line_buffer_range.start, cx).tab_size
9743 }
9744 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9745 };
9746 if old_head.column <= indent_size.len && old_head.column > 0 {
9747 let indent_len = indent_len.get();
9748 new_head = cmp::min(
9749 new_head,
9750 MultiBufferPoint::new(
9751 old_head.row,
9752 ((old_head.column - 1) / indent_len) * indent_len,
9753 ),
9754 );
9755 }
9756 }
9757
9758 selection.set_head(new_head, SelectionGoal::None);
9759 }
9760 }
9761
9762 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9763 this.insert("", window, cx);
9764 let empty_str: Arc<str> = Arc::from("");
9765 for (buffer, edits) in linked_ranges {
9766 let snapshot = buffer.read(cx).snapshot();
9767 use text::ToPoint as TP;
9768
9769 let edits = edits
9770 .into_iter()
9771 .map(|range| {
9772 let end_point = TP::to_point(&range.end, &snapshot);
9773 let mut start_point = TP::to_point(&range.start, &snapshot);
9774
9775 if end_point == start_point {
9776 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9777 .saturating_sub(1);
9778 start_point =
9779 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9780 };
9781
9782 (start_point..end_point, empty_str.clone())
9783 })
9784 .sorted_by_key(|(range, _)| range.start)
9785 .collect::<Vec<_>>();
9786 buffer.update(cx, |this, cx| {
9787 this.edit(edits, None, cx);
9788 })
9789 }
9790 this.refresh_inline_completion(true, false, window, cx);
9791 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9792 });
9793 }
9794
9795 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9797 self.transact(window, cx, |this, window, cx| {
9798 this.change_selections(Default::default(), window, cx, |s| {
9799 s.move_with(|map, selection| {
9800 if selection.is_empty() {
9801 let cursor = movement::right(map, selection.head());
9802 selection.end = cursor;
9803 selection.reversed = true;
9804 selection.goal = SelectionGoal::None;
9805 }
9806 })
9807 });
9808 this.insert("", window, cx);
9809 this.refresh_inline_completion(true, false, window, cx);
9810 });
9811 }
9812
9813 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9814 if self.mode.is_single_line() {
9815 cx.propagate();
9816 return;
9817 }
9818
9819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9820 if self.move_to_prev_snippet_tabstop(window, cx) {
9821 return;
9822 }
9823 self.outdent(&Outdent, window, cx);
9824 }
9825
9826 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9827 if self.mode.is_single_line() {
9828 cx.propagate();
9829 return;
9830 }
9831
9832 if self.move_to_next_snippet_tabstop(window, cx) {
9833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9834 return;
9835 }
9836 if self.read_only(cx) {
9837 return;
9838 }
9839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9840 let mut selections = self.selections.all_adjusted(cx);
9841 let buffer = self.buffer.read(cx);
9842 let snapshot = buffer.snapshot(cx);
9843 let rows_iter = selections.iter().map(|s| s.head().row);
9844 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9845
9846 let has_some_cursor_in_whitespace = selections
9847 .iter()
9848 .filter(|selection| selection.is_empty())
9849 .any(|selection| {
9850 let cursor = selection.head();
9851 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9852 cursor.column < current_indent.len
9853 });
9854
9855 let mut edits = Vec::new();
9856 let mut prev_edited_row = 0;
9857 let mut row_delta = 0;
9858 for selection in &mut selections {
9859 if selection.start.row != prev_edited_row {
9860 row_delta = 0;
9861 }
9862 prev_edited_row = selection.end.row;
9863
9864 // If the selection is non-empty, then increase the indentation of the selected lines.
9865 if !selection.is_empty() {
9866 row_delta =
9867 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9868 continue;
9869 }
9870
9871 let cursor = selection.head();
9872 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9873 if let Some(suggested_indent) =
9874 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9875 {
9876 // Don't do anything if already at suggested indent
9877 // and there is any other cursor which is not
9878 if has_some_cursor_in_whitespace
9879 && cursor.column == current_indent.len
9880 && current_indent.len == suggested_indent.len
9881 {
9882 continue;
9883 }
9884
9885 // Adjust line and move cursor to suggested indent
9886 // if cursor is not at suggested indent
9887 if cursor.column < suggested_indent.len
9888 && cursor.column <= current_indent.len
9889 && current_indent.len <= suggested_indent.len
9890 {
9891 selection.start = Point::new(cursor.row, suggested_indent.len);
9892 selection.end = selection.start;
9893 if row_delta == 0 {
9894 edits.extend(Buffer::edit_for_indent_size_adjustment(
9895 cursor.row,
9896 current_indent,
9897 suggested_indent,
9898 ));
9899 row_delta = suggested_indent.len - current_indent.len;
9900 }
9901 continue;
9902 }
9903
9904 // If current indent is more than suggested indent
9905 // only move cursor to current indent and skip indent
9906 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9907 selection.start = Point::new(cursor.row, current_indent.len);
9908 selection.end = selection.start;
9909 continue;
9910 }
9911 }
9912
9913 // Otherwise, insert a hard or soft tab.
9914 let settings = buffer.language_settings_at(cursor, cx);
9915 let tab_size = if settings.hard_tabs {
9916 IndentSize::tab()
9917 } else {
9918 let tab_size = settings.tab_size.get();
9919 let indent_remainder = snapshot
9920 .text_for_range(Point::new(cursor.row, 0)..cursor)
9921 .flat_map(str::chars)
9922 .fold(row_delta % tab_size, |counter: u32, c| {
9923 if c == '\t' {
9924 0
9925 } else {
9926 (counter + 1) % tab_size
9927 }
9928 });
9929
9930 let chars_to_next_tab_stop = tab_size - indent_remainder;
9931 IndentSize::spaces(chars_to_next_tab_stop)
9932 };
9933 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9934 selection.end = selection.start;
9935 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9936 row_delta += tab_size.len;
9937 }
9938
9939 self.transact(window, cx, |this, window, cx| {
9940 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9941 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9942 this.refresh_inline_completion(true, false, window, cx);
9943 });
9944 }
9945
9946 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9947 if self.read_only(cx) {
9948 return;
9949 }
9950 if self.mode.is_single_line() {
9951 cx.propagate();
9952 return;
9953 }
9954
9955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9956 let mut selections = self.selections.all::<Point>(cx);
9957 let mut prev_edited_row = 0;
9958 let mut row_delta = 0;
9959 let mut edits = Vec::new();
9960 let buffer = self.buffer.read(cx);
9961 let snapshot = buffer.snapshot(cx);
9962 for selection in &mut selections {
9963 if selection.start.row != prev_edited_row {
9964 row_delta = 0;
9965 }
9966 prev_edited_row = selection.end.row;
9967
9968 row_delta =
9969 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9970 }
9971
9972 self.transact(window, cx, |this, window, cx| {
9973 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9974 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9975 });
9976 }
9977
9978 fn indent_selection(
9979 buffer: &MultiBuffer,
9980 snapshot: &MultiBufferSnapshot,
9981 selection: &mut Selection<Point>,
9982 edits: &mut Vec<(Range<Point>, String)>,
9983 delta_for_start_row: u32,
9984 cx: &App,
9985 ) -> u32 {
9986 let settings = buffer.language_settings_at(selection.start, cx);
9987 let tab_size = settings.tab_size.get();
9988 let indent_kind = if settings.hard_tabs {
9989 IndentKind::Tab
9990 } else {
9991 IndentKind::Space
9992 };
9993 let mut start_row = selection.start.row;
9994 let mut end_row = selection.end.row + 1;
9995
9996 // If a selection ends at the beginning of a line, don't indent
9997 // that last line.
9998 if selection.end.column == 0 && selection.end.row > selection.start.row {
9999 end_row -= 1;
10000 }
10001
10002 // Avoid re-indenting a row that has already been indented by a
10003 // previous selection, but still update this selection's column
10004 // to reflect that indentation.
10005 if delta_for_start_row > 0 {
10006 start_row += 1;
10007 selection.start.column += delta_for_start_row;
10008 if selection.end.row == selection.start.row {
10009 selection.end.column += delta_for_start_row;
10010 }
10011 }
10012
10013 let mut delta_for_end_row = 0;
10014 let has_multiple_rows = start_row + 1 != end_row;
10015 for row in start_row..end_row {
10016 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10017 let indent_delta = match (current_indent.kind, indent_kind) {
10018 (IndentKind::Space, IndentKind::Space) => {
10019 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10020 IndentSize::spaces(columns_to_next_tab_stop)
10021 }
10022 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10023 (_, IndentKind::Tab) => IndentSize::tab(),
10024 };
10025
10026 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10027 0
10028 } else {
10029 selection.start.column
10030 };
10031 let row_start = Point::new(row, start);
10032 edits.push((
10033 row_start..row_start,
10034 indent_delta.chars().collect::<String>(),
10035 ));
10036
10037 // Update this selection's endpoints to reflect the indentation.
10038 if row == selection.start.row {
10039 selection.start.column += indent_delta.len;
10040 }
10041 if row == selection.end.row {
10042 selection.end.column += indent_delta.len;
10043 delta_for_end_row = indent_delta.len;
10044 }
10045 }
10046
10047 if selection.start.row == selection.end.row {
10048 delta_for_start_row + delta_for_end_row
10049 } else {
10050 delta_for_end_row
10051 }
10052 }
10053
10054 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10055 if self.read_only(cx) {
10056 return;
10057 }
10058 if self.mode.is_single_line() {
10059 cx.propagate();
10060 return;
10061 }
10062
10063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10065 let selections = self.selections.all::<Point>(cx);
10066 let mut deletion_ranges = Vec::new();
10067 let mut last_outdent = None;
10068 {
10069 let buffer = self.buffer.read(cx);
10070 let snapshot = buffer.snapshot(cx);
10071 for selection in &selections {
10072 let settings = buffer.language_settings_at(selection.start, cx);
10073 let tab_size = settings.tab_size.get();
10074 let mut rows = selection.spanned_rows(false, &display_map);
10075
10076 // Avoid re-outdenting a row that has already been outdented by a
10077 // previous selection.
10078 if let Some(last_row) = last_outdent {
10079 if last_row == rows.start {
10080 rows.start = rows.start.next_row();
10081 }
10082 }
10083 let has_multiple_rows = rows.len() > 1;
10084 for row in rows.iter_rows() {
10085 let indent_size = snapshot.indent_size_for_line(row);
10086 if indent_size.len > 0 {
10087 let deletion_len = match indent_size.kind {
10088 IndentKind::Space => {
10089 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10090 if columns_to_prev_tab_stop == 0 {
10091 tab_size
10092 } else {
10093 columns_to_prev_tab_stop
10094 }
10095 }
10096 IndentKind::Tab => 1,
10097 };
10098 let start = if has_multiple_rows
10099 || deletion_len > selection.start.column
10100 || indent_size.len < selection.start.column
10101 {
10102 0
10103 } else {
10104 selection.start.column - deletion_len
10105 };
10106 deletion_ranges.push(
10107 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10108 );
10109 last_outdent = Some(row);
10110 }
10111 }
10112 }
10113 }
10114
10115 self.transact(window, cx, |this, window, cx| {
10116 this.buffer.update(cx, |buffer, cx| {
10117 let empty_str: Arc<str> = Arc::default();
10118 buffer.edit(
10119 deletion_ranges
10120 .into_iter()
10121 .map(|range| (range, empty_str.clone())),
10122 None,
10123 cx,
10124 );
10125 });
10126 let selections = this.selections.all::<usize>(cx);
10127 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10128 });
10129 }
10130
10131 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10132 if self.read_only(cx) {
10133 return;
10134 }
10135 if self.mode.is_single_line() {
10136 cx.propagate();
10137 return;
10138 }
10139
10140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10141 let selections = self
10142 .selections
10143 .all::<usize>(cx)
10144 .into_iter()
10145 .map(|s| s.range());
10146
10147 self.transact(window, cx, |this, window, cx| {
10148 this.buffer.update(cx, |buffer, cx| {
10149 buffer.autoindent_ranges(selections, cx);
10150 });
10151 let selections = this.selections.all::<usize>(cx);
10152 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10153 });
10154 }
10155
10156 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10159 let selections = self.selections.all::<Point>(cx);
10160
10161 let mut new_cursors = Vec::new();
10162 let mut edit_ranges = Vec::new();
10163 let mut selections = selections.iter().peekable();
10164 while let Some(selection) = selections.next() {
10165 let mut rows = selection.spanned_rows(false, &display_map);
10166 let goal_display_column = selection.head().to_display_point(&display_map).column();
10167
10168 // Accumulate contiguous regions of rows that we want to delete.
10169 while let Some(next_selection) = selections.peek() {
10170 let next_rows = next_selection.spanned_rows(false, &display_map);
10171 if next_rows.start <= rows.end {
10172 rows.end = next_rows.end;
10173 selections.next().unwrap();
10174 } else {
10175 break;
10176 }
10177 }
10178
10179 let buffer = &display_map.buffer_snapshot;
10180 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10181 let edit_end;
10182 let cursor_buffer_row;
10183 if buffer.max_point().row >= rows.end.0 {
10184 // If there's a line after the range, delete the \n from the end of the row range
10185 // and position the cursor on the next line.
10186 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10187 cursor_buffer_row = rows.end;
10188 } else {
10189 // If there isn't a line after the range, delete the \n from the line before the
10190 // start of the row range and position the cursor there.
10191 edit_start = edit_start.saturating_sub(1);
10192 edit_end = buffer.len();
10193 cursor_buffer_row = rows.start.previous_row();
10194 }
10195
10196 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10197 *cursor.column_mut() =
10198 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10199
10200 new_cursors.push((
10201 selection.id,
10202 buffer.anchor_after(cursor.to_point(&display_map)),
10203 ));
10204 edit_ranges.push(edit_start..edit_end);
10205 }
10206
10207 self.transact(window, cx, |this, window, cx| {
10208 let buffer = this.buffer.update(cx, |buffer, cx| {
10209 let empty_str: Arc<str> = Arc::default();
10210 buffer.edit(
10211 edit_ranges
10212 .into_iter()
10213 .map(|range| (range, empty_str.clone())),
10214 None,
10215 cx,
10216 );
10217 buffer.snapshot(cx)
10218 });
10219 let new_selections = new_cursors
10220 .into_iter()
10221 .map(|(id, cursor)| {
10222 let cursor = cursor.to_point(&buffer);
10223 Selection {
10224 id,
10225 start: cursor,
10226 end: cursor,
10227 reversed: false,
10228 goal: SelectionGoal::None,
10229 }
10230 })
10231 .collect();
10232
10233 this.change_selections(Default::default(), window, cx, |s| {
10234 s.select(new_selections);
10235 });
10236 });
10237 }
10238
10239 pub fn join_lines_impl(
10240 &mut self,
10241 insert_whitespace: bool,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 if self.read_only(cx) {
10246 return;
10247 }
10248 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10249 for selection in self.selections.all::<Point>(cx) {
10250 let start = MultiBufferRow(selection.start.row);
10251 // Treat single line selections as if they include the next line. Otherwise this action
10252 // would do nothing for single line selections individual cursors.
10253 let end = if selection.start.row == selection.end.row {
10254 MultiBufferRow(selection.start.row + 1)
10255 } else {
10256 MultiBufferRow(selection.end.row)
10257 };
10258
10259 if let Some(last_row_range) = row_ranges.last_mut() {
10260 if start <= last_row_range.end {
10261 last_row_range.end = end;
10262 continue;
10263 }
10264 }
10265 row_ranges.push(start..end);
10266 }
10267
10268 let snapshot = self.buffer.read(cx).snapshot(cx);
10269 let mut cursor_positions = Vec::new();
10270 for row_range in &row_ranges {
10271 let anchor = snapshot.anchor_before(Point::new(
10272 row_range.end.previous_row().0,
10273 snapshot.line_len(row_range.end.previous_row()),
10274 ));
10275 cursor_positions.push(anchor..anchor);
10276 }
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 for row_range in row_ranges.into_iter().rev() {
10280 for row in row_range.iter_rows().rev() {
10281 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10282 let next_line_row = row.next_row();
10283 let indent = snapshot.indent_size_for_line(next_line_row);
10284 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10285
10286 let replace =
10287 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10288 " "
10289 } else {
10290 ""
10291 };
10292
10293 this.buffer.update(cx, |buffer, cx| {
10294 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10295 });
10296 }
10297 }
10298
10299 this.change_selections(Default::default(), window, cx, |s| {
10300 s.select_anchor_ranges(cursor_positions)
10301 });
10302 });
10303 }
10304
10305 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10307 self.join_lines_impl(true, window, cx);
10308 }
10309
10310 pub fn sort_lines_case_sensitive(
10311 &mut self,
10312 _: &SortLinesCaseSensitive,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) {
10316 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10317 }
10318
10319 pub fn sort_lines_by_length(
10320 &mut self,
10321 _: &SortLinesByLength,
10322 window: &mut Window,
10323 cx: &mut Context<Self>,
10324 ) {
10325 self.manipulate_immutable_lines(window, cx, |lines| {
10326 lines.sort_by_key(|&line| line.chars().count())
10327 })
10328 }
10329
10330 pub fn sort_lines_case_insensitive(
10331 &mut self,
10332 _: &SortLinesCaseInsensitive,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 self.manipulate_immutable_lines(window, cx, |lines| {
10337 lines.sort_by_key(|line| line.to_lowercase())
10338 })
10339 }
10340
10341 pub fn unique_lines_case_insensitive(
10342 &mut self,
10343 _: &UniqueLinesCaseInsensitive,
10344 window: &mut Window,
10345 cx: &mut Context<Self>,
10346 ) {
10347 self.manipulate_immutable_lines(window, cx, |lines| {
10348 let mut seen = HashSet::default();
10349 lines.retain(|line| seen.insert(line.to_lowercase()));
10350 })
10351 }
10352
10353 pub fn unique_lines_case_sensitive(
10354 &mut self,
10355 _: &UniqueLinesCaseSensitive,
10356 window: &mut Window,
10357 cx: &mut Context<Self>,
10358 ) {
10359 self.manipulate_immutable_lines(window, cx, |lines| {
10360 let mut seen = HashSet::default();
10361 lines.retain(|line| seen.insert(*line));
10362 })
10363 }
10364
10365 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10366 let Some(project) = self.project.clone() else {
10367 return;
10368 };
10369 self.reload(project, window, cx)
10370 .detach_and_notify_err(window, cx);
10371 }
10372
10373 pub fn restore_file(
10374 &mut self,
10375 _: &::git::RestoreFile,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10380 let mut buffer_ids = HashSet::default();
10381 let snapshot = self.buffer().read(cx).snapshot(cx);
10382 for selection in self.selections.all::<usize>(cx) {
10383 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10384 }
10385
10386 let buffer = self.buffer().read(cx);
10387 let ranges = buffer_ids
10388 .into_iter()
10389 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10390 .collect::<Vec<_>>();
10391
10392 self.restore_hunks_in_ranges(ranges, window, cx);
10393 }
10394
10395 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10397 let selections = self
10398 .selections
10399 .all(cx)
10400 .into_iter()
10401 .map(|s| s.range())
10402 .collect();
10403 self.restore_hunks_in_ranges(selections, window, cx);
10404 }
10405
10406 pub fn restore_hunks_in_ranges(
10407 &mut self,
10408 ranges: Vec<Range<Point>>,
10409 window: &mut Window,
10410 cx: &mut Context<Editor>,
10411 ) {
10412 let mut revert_changes = HashMap::default();
10413 let chunk_by = self
10414 .snapshot(window, cx)
10415 .hunks_for_ranges(ranges)
10416 .into_iter()
10417 .chunk_by(|hunk| hunk.buffer_id);
10418 for (buffer_id, hunks) in &chunk_by {
10419 let hunks = hunks.collect::<Vec<_>>();
10420 for hunk in &hunks {
10421 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10422 }
10423 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10424 }
10425 drop(chunk_by);
10426 if !revert_changes.is_empty() {
10427 self.transact(window, cx, |editor, window, cx| {
10428 editor.restore(revert_changes, window, cx);
10429 });
10430 }
10431 }
10432
10433 pub fn open_active_item_in_terminal(
10434 &mut self,
10435 _: &OpenInTerminal,
10436 window: &mut Window,
10437 cx: &mut Context<Self>,
10438 ) {
10439 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10440 let project_path = buffer.read(cx).project_path(cx)?;
10441 let project = self.project.as_ref()?.read(cx);
10442 let entry = project.entry_for_path(&project_path, cx)?;
10443 let parent = match &entry.canonical_path {
10444 Some(canonical_path) => canonical_path.to_path_buf(),
10445 None => project.absolute_path(&project_path, cx)?,
10446 }
10447 .parent()?
10448 .to_path_buf();
10449 Some(parent)
10450 }) {
10451 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10452 }
10453 }
10454
10455 fn set_breakpoint_context_menu(
10456 &mut self,
10457 display_row: DisplayRow,
10458 position: Option<Anchor>,
10459 clicked_point: gpui::Point<Pixels>,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 let source = self
10464 .buffer
10465 .read(cx)
10466 .snapshot(cx)
10467 .anchor_before(Point::new(display_row.0, 0u32));
10468
10469 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10470
10471 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10472 self,
10473 source,
10474 clicked_point,
10475 context_menu,
10476 window,
10477 cx,
10478 );
10479 }
10480
10481 fn add_edit_breakpoint_block(
10482 &mut self,
10483 anchor: Anchor,
10484 breakpoint: &Breakpoint,
10485 edit_action: BreakpointPromptEditAction,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 let weak_editor = cx.weak_entity();
10490 let bp_prompt = cx.new(|cx| {
10491 BreakpointPromptEditor::new(
10492 weak_editor,
10493 anchor,
10494 breakpoint.clone(),
10495 edit_action,
10496 window,
10497 cx,
10498 )
10499 });
10500
10501 let height = bp_prompt.update(cx, |this, cx| {
10502 this.prompt
10503 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10504 });
10505 let cloned_prompt = bp_prompt.clone();
10506 let blocks = vec![BlockProperties {
10507 style: BlockStyle::Sticky,
10508 placement: BlockPlacement::Above(anchor),
10509 height: Some(height),
10510 render: Arc::new(move |cx| {
10511 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10512 cloned_prompt.clone().into_any_element()
10513 }),
10514 priority: 0,
10515 }];
10516
10517 let focus_handle = bp_prompt.focus_handle(cx);
10518 window.focus(&focus_handle);
10519
10520 let block_ids = self.insert_blocks(blocks, None, cx);
10521 bp_prompt.update(cx, |prompt, _| {
10522 prompt.add_block_ids(block_ids);
10523 });
10524 }
10525
10526 pub(crate) fn breakpoint_at_row(
10527 &self,
10528 row: u32,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) -> Option<(Anchor, Breakpoint)> {
10532 let snapshot = self.snapshot(window, cx);
10533 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10534
10535 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10536 }
10537
10538 pub(crate) fn breakpoint_at_anchor(
10539 &self,
10540 breakpoint_position: Anchor,
10541 snapshot: &EditorSnapshot,
10542 cx: &mut Context<Self>,
10543 ) -> Option<(Anchor, Breakpoint)> {
10544 let project = self.project.clone()?;
10545
10546 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10547 snapshot
10548 .buffer_snapshot
10549 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10550 })?;
10551
10552 let enclosing_excerpt = breakpoint_position.excerpt_id;
10553 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10554 let buffer_snapshot = buffer.read(cx).snapshot();
10555
10556 let row = buffer_snapshot
10557 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10558 .row;
10559
10560 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10561 let anchor_end = snapshot
10562 .buffer_snapshot
10563 .anchor_after(Point::new(row, line_len));
10564
10565 let bp = self
10566 .breakpoint_store
10567 .as_ref()?
10568 .read_with(cx, |breakpoint_store, cx| {
10569 breakpoint_store
10570 .breakpoints(
10571 &buffer,
10572 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10573 &buffer_snapshot,
10574 cx,
10575 )
10576 .next()
10577 .and_then(|(bp, _)| {
10578 let breakpoint_row = buffer_snapshot
10579 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10580 .row;
10581
10582 if breakpoint_row == row {
10583 snapshot
10584 .buffer_snapshot
10585 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10586 .map(|position| (position, bp.bp.clone()))
10587 } else {
10588 None
10589 }
10590 })
10591 });
10592 bp
10593 }
10594
10595 pub fn edit_log_breakpoint(
10596 &mut self,
10597 _: &EditLogBreakpoint,
10598 window: &mut Window,
10599 cx: &mut Context<Self>,
10600 ) {
10601 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10602 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10603 message: None,
10604 state: BreakpointState::Enabled,
10605 condition: None,
10606 hit_condition: None,
10607 });
10608
10609 self.add_edit_breakpoint_block(
10610 anchor,
10611 &breakpoint,
10612 BreakpointPromptEditAction::Log,
10613 window,
10614 cx,
10615 );
10616 }
10617 }
10618
10619 fn breakpoints_at_cursors(
10620 &self,
10621 window: &mut Window,
10622 cx: &mut Context<Self>,
10623 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10624 let snapshot = self.snapshot(window, cx);
10625 let cursors = self
10626 .selections
10627 .disjoint_anchors()
10628 .into_iter()
10629 .map(|selection| {
10630 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10631
10632 let breakpoint_position = self
10633 .breakpoint_at_row(cursor_position.row, window, cx)
10634 .map(|bp| bp.0)
10635 .unwrap_or_else(|| {
10636 snapshot
10637 .display_snapshot
10638 .buffer_snapshot
10639 .anchor_after(Point::new(cursor_position.row, 0))
10640 });
10641
10642 let breakpoint = self
10643 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10644 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10645
10646 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10647 })
10648 // 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.
10649 .collect::<HashMap<Anchor, _>>();
10650
10651 cursors.into_iter().collect()
10652 }
10653
10654 pub fn enable_breakpoint(
10655 &mut self,
10656 _: &crate::actions::EnableBreakpoint,
10657 window: &mut Window,
10658 cx: &mut Context<Self>,
10659 ) {
10660 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10661 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10662 continue;
10663 };
10664 self.edit_breakpoint_at_anchor(
10665 anchor,
10666 breakpoint,
10667 BreakpointEditAction::InvertState,
10668 cx,
10669 );
10670 }
10671 }
10672
10673 pub fn disable_breakpoint(
10674 &mut self,
10675 _: &crate::actions::DisableBreakpoint,
10676 window: &mut Window,
10677 cx: &mut Context<Self>,
10678 ) {
10679 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10680 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10681 continue;
10682 };
10683 self.edit_breakpoint_at_anchor(
10684 anchor,
10685 breakpoint,
10686 BreakpointEditAction::InvertState,
10687 cx,
10688 );
10689 }
10690 }
10691
10692 pub fn toggle_breakpoint(
10693 &mut self,
10694 _: &crate::actions::ToggleBreakpoint,
10695 window: &mut Window,
10696 cx: &mut Context<Self>,
10697 ) {
10698 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10699 if let Some(breakpoint) = breakpoint {
10700 self.edit_breakpoint_at_anchor(
10701 anchor,
10702 breakpoint,
10703 BreakpointEditAction::Toggle,
10704 cx,
10705 );
10706 } else {
10707 self.edit_breakpoint_at_anchor(
10708 anchor,
10709 Breakpoint::new_standard(),
10710 BreakpointEditAction::Toggle,
10711 cx,
10712 );
10713 }
10714 }
10715 }
10716
10717 pub fn edit_breakpoint_at_anchor(
10718 &mut self,
10719 breakpoint_position: Anchor,
10720 breakpoint: Breakpoint,
10721 edit_action: BreakpointEditAction,
10722 cx: &mut Context<Self>,
10723 ) {
10724 let Some(breakpoint_store) = &self.breakpoint_store else {
10725 return;
10726 };
10727
10728 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10729 if breakpoint_position == Anchor::min() {
10730 self.buffer()
10731 .read(cx)
10732 .excerpt_buffer_ids()
10733 .into_iter()
10734 .next()
10735 } else {
10736 None
10737 }
10738 }) else {
10739 return;
10740 };
10741
10742 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10743 return;
10744 };
10745
10746 breakpoint_store.update(cx, |breakpoint_store, cx| {
10747 breakpoint_store.toggle_breakpoint(
10748 buffer,
10749 BreakpointWithPosition {
10750 position: breakpoint_position.text_anchor,
10751 bp: breakpoint,
10752 },
10753 edit_action,
10754 cx,
10755 );
10756 });
10757
10758 cx.notify();
10759 }
10760
10761 #[cfg(any(test, feature = "test-support"))]
10762 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10763 self.breakpoint_store.clone()
10764 }
10765
10766 pub fn prepare_restore_change(
10767 &self,
10768 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10769 hunk: &MultiBufferDiffHunk,
10770 cx: &mut App,
10771 ) -> Option<()> {
10772 if hunk.is_created_file() {
10773 return None;
10774 }
10775 let buffer = self.buffer.read(cx);
10776 let diff = buffer.diff_for(hunk.buffer_id)?;
10777 let buffer = buffer.buffer(hunk.buffer_id)?;
10778 let buffer = buffer.read(cx);
10779 let original_text = diff
10780 .read(cx)
10781 .base_text()
10782 .as_rope()
10783 .slice(hunk.diff_base_byte_range.clone());
10784 let buffer_snapshot = buffer.snapshot();
10785 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10786 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10787 probe
10788 .0
10789 .start
10790 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10791 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10792 }) {
10793 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10794 Some(())
10795 } else {
10796 None
10797 }
10798 }
10799
10800 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10801 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10802 }
10803
10804 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10805 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10806 }
10807
10808 fn manipulate_lines<M>(
10809 &mut self,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 mut manipulate: M,
10813 ) where
10814 M: FnMut(&str) -> LineManipulationResult,
10815 {
10816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10817
10818 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10819 let buffer = self.buffer.read(cx).snapshot(cx);
10820
10821 let mut edits = Vec::new();
10822
10823 let selections = self.selections.all::<Point>(cx);
10824 let mut selections = selections.iter().peekable();
10825 let mut contiguous_row_selections = Vec::new();
10826 let mut new_selections = Vec::new();
10827 let mut added_lines = 0;
10828 let mut removed_lines = 0;
10829
10830 while let Some(selection) = selections.next() {
10831 let (start_row, end_row) = consume_contiguous_rows(
10832 &mut contiguous_row_selections,
10833 selection,
10834 &display_map,
10835 &mut selections,
10836 );
10837
10838 let start_point = Point::new(start_row.0, 0);
10839 let end_point = Point::new(
10840 end_row.previous_row().0,
10841 buffer.line_len(end_row.previous_row()),
10842 );
10843 let text = buffer
10844 .text_for_range(start_point..end_point)
10845 .collect::<String>();
10846
10847 let LineManipulationResult {
10848 new_text,
10849 line_count_before,
10850 line_count_after,
10851 } = manipulate(&text);
10852
10853 edits.push((start_point..end_point, new_text));
10854
10855 // Selections must change based on added and removed line count
10856 let start_row =
10857 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10858 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10859 new_selections.push(Selection {
10860 id: selection.id,
10861 start: start_row,
10862 end: end_row,
10863 goal: SelectionGoal::None,
10864 reversed: selection.reversed,
10865 });
10866
10867 if line_count_after > line_count_before {
10868 added_lines += line_count_after - line_count_before;
10869 } else if line_count_before > line_count_after {
10870 removed_lines += line_count_before - line_count_after;
10871 }
10872 }
10873
10874 self.transact(window, cx, |this, window, cx| {
10875 let buffer = this.buffer.update(cx, |buffer, cx| {
10876 buffer.edit(edits, None, cx);
10877 buffer.snapshot(cx)
10878 });
10879
10880 // Recalculate offsets on newly edited buffer
10881 let new_selections = new_selections
10882 .iter()
10883 .map(|s| {
10884 let start_point = Point::new(s.start.0, 0);
10885 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10886 Selection {
10887 id: s.id,
10888 start: buffer.point_to_offset(start_point),
10889 end: buffer.point_to_offset(end_point),
10890 goal: s.goal,
10891 reversed: s.reversed,
10892 }
10893 })
10894 .collect();
10895
10896 this.change_selections(Default::default(), window, cx, |s| {
10897 s.select(new_selections);
10898 });
10899
10900 this.request_autoscroll(Autoscroll::fit(), cx);
10901 });
10902 }
10903
10904 fn manipulate_immutable_lines<Fn>(
10905 &mut self,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 mut callback: Fn,
10909 ) where
10910 Fn: FnMut(&mut Vec<&str>),
10911 {
10912 self.manipulate_lines(window, cx, |text| {
10913 let mut lines: Vec<&str> = text.split('\n').collect();
10914 let line_count_before = lines.len();
10915
10916 callback(&mut lines);
10917
10918 LineManipulationResult {
10919 new_text: lines.join("\n"),
10920 line_count_before,
10921 line_count_after: lines.len(),
10922 }
10923 });
10924 }
10925
10926 fn manipulate_mutable_lines<Fn>(
10927 &mut self,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 mut callback: Fn,
10931 ) where
10932 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10933 {
10934 self.manipulate_lines(window, cx, |text| {
10935 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10936 let line_count_before = lines.len();
10937
10938 callback(&mut lines);
10939
10940 LineManipulationResult {
10941 new_text: lines.join("\n"),
10942 line_count_before,
10943 line_count_after: lines.len(),
10944 }
10945 });
10946 }
10947
10948 pub fn convert_indentation_to_spaces(
10949 &mut self,
10950 _: &ConvertIndentationToSpaces,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 let settings = self.buffer.read(cx).language_settings(cx);
10955 let tab_size = settings.tab_size.get() as usize;
10956
10957 self.manipulate_mutable_lines(window, cx, |lines| {
10958 // Allocates a reasonably sized scratch buffer once for the whole loop
10959 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10960 // Avoids recomputing spaces that could be inserted many times
10961 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10962 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10963 .collect();
10964
10965 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10966 let mut chars = line.as_ref().chars();
10967 let mut col = 0;
10968 let mut changed = false;
10969
10970 while let Some(ch) = chars.next() {
10971 match ch {
10972 ' ' => {
10973 reindented_line.push(' ');
10974 col += 1;
10975 }
10976 '\t' => {
10977 // \t are converted to spaces depending on the current column
10978 let spaces_len = tab_size - (col % tab_size);
10979 reindented_line.extend(&space_cache[spaces_len - 1]);
10980 col += spaces_len;
10981 changed = true;
10982 }
10983 _ => {
10984 // If we dont append before break, the character is consumed
10985 reindented_line.push(ch);
10986 break;
10987 }
10988 }
10989 }
10990
10991 if !changed {
10992 reindented_line.clear();
10993 continue;
10994 }
10995 // Append the rest of the line and replace old reference with new one
10996 reindented_line.extend(chars);
10997 *line = Cow::Owned(reindented_line.clone());
10998 reindented_line.clear();
10999 }
11000 });
11001 }
11002
11003 pub fn convert_indentation_to_tabs(
11004 &mut self,
11005 _: &ConvertIndentationToTabs,
11006 window: &mut Window,
11007 cx: &mut Context<Self>,
11008 ) {
11009 let settings = self.buffer.read(cx).language_settings(cx);
11010 let tab_size = settings.tab_size.get() as usize;
11011
11012 self.manipulate_mutable_lines(window, cx, |lines| {
11013 // Allocates a reasonably sized buffer once for the whole loop
11014 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11015 // Avoids recomputing spaces that could be inserted many times
11016 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11017 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11018 .collect();
11019
11020 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11021 let mut chars = line.chars();
11022 let mut spaces_count = 0;
11023 let mut first_non_indent_char = None;
11024 let mut changed = false;
11025
11026 while let Some(ch) = chars.next() {
11027 match ch {
11028 ' ' => {
11029 // Keep track of spaces. Append \t when we reach tab_size
11030 spaces_count += 1;
11031 changed = true;
11032 if spaces_count == tab_size {
11033 reindented_line.push('\t');
11034 spaces_count = 0;
11035 }
11036 }
11037 '\t' => {
11038 reindented_line.push('\t');
11039 spaces_count = 0;
11040 }
11041 _ => {
11042 // Dont append it yet, we might have remaining spaces
11043 first_non_indent_char = Some(ch);
11044 break;
11045 }
11046 }
11047 }
11048
11049 if !changed {
11050 reindented_line.clear();
11051 continue;
11052 }
11053 // Remaining spaces that didn't make a full tab stop
11054 if spaces_count > 0 {
11055 reindented_line.extend(&space_cache[spaces_count - 1]);
11056 }
11057 // If we consume an extra character that was not indentation, add it back
11058 if let Some(extra_char) = first_non_indent_char {
11059 reindented_line.push(extra_char);
11060 }
11061 // Append the rest of the line and replace old reference with new one
11062 reindented_line.extend(chars);
11063 *line = Cow::Owned(reindented_line.clone());
11064 reindented_line.clear();
11065 }
11066 });
11067 }
11068
11069 pub fn convert_to_upper_case(
11070 &mut self,
11071 _: &ConvertToUpperCase,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) {
11075 self.manipulate_text(window, cx, |text| text.to_uppercase())
11076 }
11077
11078 pub fn convert_to_lower_case(
11079 &mut self,
11080 _: &ConvertToLowerCase,
11081 window: &mut Window,
11082 cx: &mut Context<Self>,
11083 ) {
11084 self.manipulate_text(window, cx, |text| text.to_lowercase())
11085 }
11086
11087 pub fn convert_to_title_case(
11088 &mut self,
11089 _: &ConvertToTitleCase,
11090 window: &mut Window,
11091 cx: &mut Context<Self>,
11092 ) {
11093 self.manipulate_text(window, cx, |text| {
11094 text.split('\n')
11095 .map(|line| line.to_case(Case::Title))
11096 .join("\n")
11097 })
11098 }
11099
11100 pub fn convert_to_snake_case(
11101 &mut self,
11102 _: &ConvertToSnakeCase,
11103 window: &mut Window,
11104 cx: &mut Context<Self>,
11105 ) {
11106 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11107 }
11108
11109 pub fn convert_to_kebab_case(
11110 &mut self,
11111 _: &ConvertToKebabCase,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11116 }
11117
11118 pub fn convert_to_upper_camel_case(
11119 &mut self,
11120 _: &ConvertToUpperCamelCase,
11121 window: &mut Window,
11122 cx: &mut Context<Self>,
11123 ) {
11124 self.manipulate_text(window, cx, |text| {
11125 text.split('\n')
11126 .map(|line| line.to_case(Case::UpperCamel))
11127 .join("\n")
11128 })
11129 }
11130
11131 pub fn convert_to_lower_camel_case(
11132 &mut self,
11133 _: &ConvertToLowerCamelCase,
11134 window: &mut Window,
11135 cx: &mut Context<Self>,
11136 ) {
11137 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11138 }
11139
11140 pub fn convert_to_opposite_case(
11141 &mut self,
11142 _: &ConvertToOppositeCase,
11143 window: &mut Window,
11144 cx: &mut Context<Self>,
11145 ) {
11146 self.manipulate_text(window, cx, |text| {
11147 text.chars()
11148 .fold(String::with_capacity(text.len()), |mut t, c| {
11149 if c.is_uppercase() {
11150 t.extend(c.to_lowercase());
11151 } else {
11152 t.extend(c.to_uppercase());
11153 }
11154 t
11155 })
11156 })
11157 }
11158
11159 pub fn convert_to_sentence_case(
11160 &mut self,
11161 _: &ConvertToSentenceCase,
11162 window: &mut Window,
11163 cx: &mut Context<Self>,
11164 ) {
11165 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11166 }
11167
11168 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11169 self.manipulate_text(window, cx, |text| {
11170 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11171 if has_upper_case_characters {
11172 text.to_lowercase()
11173 } else {
11174 text.to_uppercase()
11175 }
11176 })
11177 }
11178
11179 pub fn convert_to_rot13(
11180 &mut self,
11181 _: &ConvertToRot13,
11182 window: &mut Window,
11183 cx: &mut Context<Self>,
11184 ) {
11185 self.manipulate_text(window, cx, |text| {
11186 text.chars()
11187 .map(|c| match c {
11188 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11189 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11190 _ => c,
11191 })
11192 .collect()
11193 })
11194 }
11195
11196 pub fn convert_to_rot47(
11197 &mut self,
11198 _: &ConvertToRot47,
11199 window: &mut Window,
11200 cx: &mut Context<Self>,
11201 ) {
11202 self.manipulate_text(window, cx, |text| {
11203 text.chars()
11204 .map(|c| {
11205 let code_point = c as u32;
11206 if code_point >= 33 && code_point <= 126 {
11207 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11208 }
11209 c
11210 })
11211 .collect()
11212 })
11213 }
11214
11215 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11216 where
11217 Fn: FnMut(&str) -> String,
11218 {
11219 let buffer = self.buffer.read(cx).snapshot(cx);
11220
11221 let mut new_selections = Vec::new();
11222 let mut edits = Vec::new();
11223 let mut selection_adjustment = 0i32;
11224
11225 for selection in self.selections.all::<usize>(cx) {
11226 let selection_is_empty = selection.is_empty();
11227
11228 let (start, end) = if selection_is_empty {
11229 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11230 (word_range.start, word_range.end)
11231 } else {
11232 (selection.start, selection.end)
11233 };
11234
11235 let text = buffer.text_for_range(start..end).collect::<String>();
11236 let old_length = text.len() as i32;
11237 let text = callback(&text);
11238
11239 new_selections.push(Selection {
11240 start: (start as i32 - selection_adjustment) as usize,
11241 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11242 goal: SelectionGoal::None,
11243 ..selection
11244 });
11245
11246 selection_adjustment += old_length - text.len() as i32;
11247
11248 edits.push((start..end, text));
11249 }
11250
11251 self.transact(window, cx, |this, window, cx| {
11252 this.buffer.update(cx, |buffer, cx| {
11253 buffer.edit(edits, None, cx);
11254 });
11255
11256 this.change_selections(Default::default(), window, cx, |s| {
11257 s.select(new_selections);
11258 });
11259
11260 this.request_autoscroll(Autoscroll::fit(), cx);
11261 });
11262 }
11263
11264 pub fn move_selection_on_drop(
11265 &mut self,
11266 selection: &Selection<Anchor>,
11267 target: DisplayPoint,
11268 is_cut: bool,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11273 let buffer = &display_map.buffer_snapshot;
11274 let mut edits = Vec::new();
11275 let insert_point = display_map
11276 .clip_point(target, Bias::Left)
11277 .to_point(&display_map);
11278 let text = buffer
11279 .text_for_range(selection.start..selection.end)
11280 .collect::<String>();
11281 if is_cut {
11282 edits.push(((selection.start..selection.end), String::new()));
11283 }
11284 let insert_anchor = buffer.anchor_before(insert_point);
11285 edits.push(((insert_anchor..insert_anchor), text));
11286 let last_edit_start = insert_anchor.bias_left(buffer);
11287 let last_edit_end = insert_anchor.bias_right(buffer);
11288 self.transact(window, cx, |this, window, cx| {
11289 this.buffer.update(cx, |buffer, cx| {
11290 buffer.edit(edits, None, cx);
11291 });
11292 this.change_selections(Default::default(), window, cx, |s| {
11293 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11294 });
11295 });
11296 }
11297
11298 pub fn clear_selection_drag_state(&mut self) {
11299 self.selection_drag_state = SelectionDragState::None;
11300 }
11301
11302 pub fn duplicate(
11303 &mut self,
11304 upwards: bool,
11305 whole_lines: bool,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11310
11311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11312 let buffer = &display_map.buffer_snapshot;
11313 let selections = self.selections.all::<Point>(cx);
11314
11315 let mut edits = Vec::new();
11316 let mut selections_iter = selections.iter().peekable();
11317 while let Some(selection) = selections_iter.next() {
11318 let mut rows = selection.spanned_rows(false, &display_map);
11319 // duplicate line-wise
11320 if whole_lines || selection.start == selection.end {
11321 // Avoid duplicating the same lines twice.
11322 while let Some(next_selection) = selections_iter.peek() {
11323 let next_rows = next_selection.spanned_rows(false, &display_map);
11324 if next_rows.start < rows.end {
11325 rows.end = next_rows.end;
11326 selections_iter.next().unwrap();
11327 } else {
11328 break;
11329 }
11330 }
11331
11332 // Copy the text from the selected row region and splice it either at the start
11333 // or end of the region.
11334 let start = Point::new(rows.start.0, 0);
11335 let end = Point::new(
11336 rows.end.previous_row().0,
11337 buffer.line_len(rows.end.previous_row()),
11338 );
11339 let text = buffer
11340 .text_for_range(start..end)
11341 .chain(Some("\n"))
11342 .collect::<String>();
11343 let insert_location = if upwards {
11344 Point::new(rows.end.0, 0)
11345 } else {
11346 start
11347 };
11348 edits.push((insert_location..insert_location, text));
11349 } else {
11350 // duplicate character-wise
11351 let start = selection.start;
11352 let end = selection.end;
11353 let text = buffer.text_for_range(start..end).collect::<String>();
11354 edits.push((selection.end..selection.end, text));
11355 }
11356 }
11357
11358 self.transact(window, cx, |this, _, cx| {
11359 this.buffer.update(cx, |buffer, cx| {
11360 buffer.edit(edits, None, cx);
11361 });
11362
11363 this.request_autoscroll(Autoscroll::fit(), cx);
11364 });
11365 }
11366
11367 pub fn duplicate_line_up(
11368 &mut self,
11369 _: &DuplicateLineUp,
11370 window: &mut Window,
11371 cx: &mut Context<Self>,
11372 ) {
11373 self.duplicate(true, true, window, cx);
11374 }
11375
11376 pub fn duplicate_line_down(
11377 &mut self,
11378 _: &DuplicateLineDown,
11379 window: &mut Window,
11380 cx: &mut Context<Self>,
11381 ) {
11382 self.duplicate(false, true, window, cx);
11383 }
11384
11385 pub fn duplicate_selection(
11386 &mut self,
11387 _: &DuplicateSelection,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 self.duplicate(false, false, window, cx);
11392 }
11393
11394 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11396 if self.mode.is_single_line() {
11397 cx.propagate();
11398 return;
11399 }
11400
11401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11402 let buffer = self.buffer.read(cx).snapshot(cx);
11403
11404 let mut edits = Vec::new();
11405 let mut unfold_ranges = Vec::new();
11406 let mut refold_creases = Vec::new();
11407
11408 let selections = self.selections.all::<Point>(cx);
11409 let mut selections = selections.iter().peekable();
11410 let mut contiguous_row_selections = Vec::new();
11411 let mut new_selections = Vec::new();
11412
11413 while let Some(selection) = selections.next() {
11414 // Find all the selections that span a contiguous row range
11415 let (start_row, end_row) = consume_contiguous_rows(
11416 &mut contiguous_row_selections,
11417 selection,
11418 &display_map,
11419 &mut selections,
11420 );
11421
11422 // Move the text spanned by the row range to be before the line preceding the row range
11423 if start_row.0 > 0 {
11424 let range_to_move = Point::new(
11425 start_row.previous_row().0,
11426 buffer.line_len(start_row.previous_row()),
11427 )
11428 ..Point::new(
11429 end_row.previous_row().0,
11430 buffer.line_len(end_row.previous_row()),
11431 );
11432 let insertion_point = display_map
11433 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11434 .0;
11435
11436 // Don't move lines across excerpts
11437 if buffer
11438 .excerpt_containing(insertion_point..range_to_move.end)
11439 .is_some()
11440 {
11441 let text = buffer
11442 .text_for_range(range_to_move.clone())
11443 .flat_map(|s| s.chars())
11444 .skip(1)
11445 .chain(['\n'])
11446 .collect::<String>();
11447
11448 edits.push((
11449 buffer.anchor_after(range_to_move.start)
11450 ..buffer.anchor_before(range_to_move.end),
11451 String::new(),
11452 ));
11453 let insertion_anchor = buffer.anchor_after(insertion_point);
11454 edits.push((insertion_anchor..insertion_anchor, text));
11455
11456 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11457
11458 // Move selections up
11459 new_selections.extend(contiguous_row_selections.drain(..).map(
11460 |mut selection| {
11461 selection.start.row -= row_delta;
11462 selection.end.row -= row_delta;
11463 selection
11464 },
11465 ));
11466
11467 // Move folds up
11468 unfold_ranges.push(range_to_move.clone());
11469 for fold in display_map.folds_in_range(
11470 buffer.anchor_before(range_to_move.start)
11471 ..buffer.anchor_after(range_to_move.end),
11472 ) {
11473 let mut start = fold.range.start.to_point(&buffer);
11474 let mut end = fold.range.end.to_point(&buffer);
11475 start.row -= row_delta;
11476 end.row -= row_delta;
11477 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11478 }
11479 }
11480 }
11481
11482 // If we didn't move line(s), preserve the existing selections
11483 new_selections.append(&mut contiguous_row_selections);
11484 }
11485
11486 self.transact(window, cx, |this, window, cx| {
11487 this.unfold_ranges(&unfold_ranges, true, true, cx);
11488 this.buffer.update(cx, |buffer, cx| {
11489 for (range, text) in edits {
11490 buffer.edit([(range, text)], None, cx);
11491 }
11492 });
11493 this.fold_creases(refold_creases, true, window, cx);
11494 this.change_selections(Default::default(), window, cx, |s| {
11495 s.select(new_selections);
11496 })
11497 });
11498 }
11499
11500 pub fn move_line_down(
11501 &mut self,
11502 _: &MoveLineDown,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11507 if self.mode.is_single_line() {
11508 cx.propagate();
11509 return;
11510 }
11511
11512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11513 let buffer = self.buffer.read(cx).snapshot(cx);
11514
11515 let mut edits = Vec::new();
11516 let mut unfold_ranges = Vec::new();
11517 let mut refold_creases = Vec::new();
11518
11519 let selections = self.selections.all::<Point>(cx);
11520 let mut selections = selections.iter().peekable();
11521 let mut contiguous_row_selections = Vec::new();
11522 let mut new_selections = Vec::new();
11523
11524 while let Some(selection) = selections.next() {
11525 // Find all the selections that span a contiguous row range
11526 let (start_row, end_row) = consume_contiguous_rows(
11527 &mut contiguous_row_selections,
11528 selection,
11529 &display_map,
11530 &mut selections,
11531 );
11532
11533 // Move the text spanned by the row range to be after the last line of the row range
11534 if end_row.0 <= buffer.max_point().row {
11535 let range_to_move =
11536 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11537 let insertion_point = display_map
11538 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11539 .0;
11540
11541 // Don't move lines across excerpt boundaries
11542 if buffer
11543 .excerpt_containing(range_to_move.start..insertion_point)
11544 .is_some()
11545 {
11546 let mut text = String::from("\n");
11547 text.extend(buffer.text_for_range(range_to_move.clone()));
11548 text.pop(); // Drop trailing newline
11549 edits.push((
11550 buffer.anchor_after(range_to_move.start)
11551 ..buffer.anchor_before(range_to_move.end),
11552 String::new(),
11553 ));
11554 let insertion_anchor = buffer.anchor_after(insertion_point);
11555 edits.push((insertion_anchor..insertion_anchor, text));
11556
11557 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11558
11559 // Move selections down
11560 new_selections.extend(contiguous_row_selections.drain(..).map(
11561 |mut selection| {
11562 selection.start.row += row_delta;
11563 selection.end.row += row_delta;
11564 selection
11565 },
11566 ));
11567
11568 // Move folds down
11569 unfold_ranges.push(range_to_move.clone());
11570 for fold in display_map.folds_in_range(
11571 buffer.anchor_before(range_to_move.start)
11572 ..buffer.anchor_after(range_to_move.end),
11573 ) {
11574 let mut start = fold.range.start.to_point(&buffer);
11575 let mut end = fold.range.end.to_point(&buffer);
11576 start.row += row_delta;
11577 end.row += row_delta;
11578 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11579 }
11580 }
11581 }
11582
11583 // If we didn't move line(s), preserve the existing selections
11584 new_selections.append(&mut contiguous_row_selections);
11585 }
11586
11587 self.transact(window, cx, |this, window, cx| {
11588 this.unfold_ranges(&unfold_ranges, true, true, cx);
11589 this.buffer.update(cx, |buffer, cx| {
11590 for (range, text) in edits {
11591 buffer.edit([(range, text)], None, cx);
11592 }
11593 });
11594 this.fold_creases(refold_creases, true, window, cx);
11595 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11596 });
11597 }
11598
11599 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11601 let text_layout_details = &self.text_layout_details(window);
11602 self.transact(window, cx, |this, window, cx| {
11603 let edits = this.change_selections(Default::default(), window, cx, |s| {
11604 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11605 s.move_with(|display_map, selection| {
11606 if !selection.is_empty() {
11607 return;
11608 }
11609
11610 let mut head = selection.head();
11611 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11612 if head.column() == display_map.line_len(head.row()) {
11613 transpose_offset = display_map
11614 .buffer_snapshot
11615 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11616 }
11617
11618 if transpose_offset == 0 {
11619 return;
11620 }
11621
11622 *head.column_mut() += 1;
11623 head = display_map.clip_point(head, Bias::Right);
11624 let goal = SelectionGoal::HorizontalPosition(
11625 display_map
11626 .x_for_display_point(head, text_layout_details)
11627 .into(),
11628 );
11629 selection.collapse_to(head, goal);
11630
11631 let transpose_start = display_map
11632 .buffer_snapshot
11633 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11634 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11635 let transpose_end = display_map
11636 .buffer_snapshot
11637 .clip_offset(transpose_offset + 1, Bias::Right);
11638 if let Some(ch) =
11639 display_map.buffer_snapshot.chars_at(transpose_start).next()
11640 {
11641 edits.push((transpose_start..transpose_offset, String::new()));
11642 edits.push((transpose_end..transpose_end, ch.to_string()));
11643 }
11644 }
11645 });
11646 edits
11647 });
11648 this.buffer
11649 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11650 let selections = this.selections.all::<usize>(cx);
11651 this.change_selections(Default::default(), window, cx, |s| {
11652 s.select(selections);
11653 });
11654 });
11655 }
11656
11657 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11658 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11659 if self.mode.is_single_line() {
11660 cx.propagate();
11661 return;
11662 }
11663
11664 self.rewrap_impl(RewrapOptions::default(), cx)
11665 }
11666
11667 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11668 let buffer = self.buffer.read(cx).snapshot(cx);
11669 let selections = self.selections.all::<Point>(cx);
11670
11671 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11672 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11673 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11674 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11675 .peekable();
11676
11677 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11678 row
11679 } else {
11680 return Vec::new();
11681 };
11682
11683 let language_settings = buffer.language_settings_at(selection.head(), cx);
11684 let language_scope = buffer.language_scope_at(selection.head());
11685
11686 let indent_and_prefix_for_row =
11687 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11688 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11689 let (comment_prefix, rewrap_prefix) =
11690 if let Some(language_scope) = &language_scope {
11691 let indent_end = Point::new(row, indent.len);
11692 let comment_prefix = language_scope
11693 .line_comment_prefixes()
11694 .iter()
11695 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11696 .map(|prefix| prefix.to_string());
11697 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11698 let line_text_after_indent = buffer
11699 .text_for_range(indent_end..line_end)
11700 .collect::<String>();
11701 let rewrap_prefix = language_scope
11702 .rewrap_prefixes()
11703 .iter()
11704 .find_map(|prefix_regex| {
11705 prefix_regex.find(&line_text_after_indent).map(|mat| {
11706 if mat.start() == 0 {
11707 Some(mat.as_str().to_string())
11708 } else {
11709 None
11710 }
11711 })
11712 })
11713 .flatten();
11714 (comment_prefix, rewrap_prefix)
11715 } else {
11716 (None, None)
11717 };
11718 (indent, comment_prefix, rewrap_prefix)
11719 };
11720
11721 let mut ranges = Vec::new();
11722 let from_empty_selection = selection.is_empty();
11723
11724 let mut current_range_start = first_row;
11725 let mut prev_row = first_row;
11726 let (
11727 mut current_range_indent,
11728 mut current_range_comment_prefix,
11729 mut current_range_rewrap_prefix,
11730 ) = indent_and_prefix_for_row(first_row);
11731
11732 for row in non_blank_rows_iter.skip(1) {
11733 let has_paragraph_break = row > prev_row + 1;
11734
11735 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11736 indent_and_prefix_for_row(row);
11737
11738 let has_indent_change = row_indent != current_range_indent;
11739 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11740
11741 let has_boundary_change = has_comment_change
11742 || row_rewrap_prefix.is_some()
11743 || (has_indent_change && current_range_comment_prefix.is_some());
11744
11745 if has_paragraph_break || has_boundary_change {
11746 ranges.push((
11747 language_settings.clone(),
11748 Point::new(current_range_start, 0)
11749 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11750 current_range_indent,
11751 current_range_comment_prefix.clone(),
11752 current_range_rewrap_prefix.clone(),
11753 from_empty_selection,
11754 ));
11755 current_range_start = row;
11756 current_range_indent = row_indent;
11757 current_range_comment_prefix = row_comment_prefix;
11758 current_range_rewrap_prefix = row_rewrap_prefix;
11759 }
11760 prev_row = row;
11761 }
11762
11763 ranges.push((
11764 language_settings.clone(),
11765 Point::new(current_range_start, 0)
11766 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11767 current_range_indent,
11768 current_range_comment_prefix,
11769 current_range_rewrap_prefix,
11770 from_empty_selection,
11771 ));
11772
11773 ranges
11774 });
11775
11776 let mut edits = Vec::new();
11777 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11778
11779 for (
11780 language_settings,
11781 wrap_range,
11782 indent_size,
11783 comment_prefix,
11784 rewrap_prefix,
11785 from_empty_selection,
11786 ) in wrap_ranges
11787 {
11788 let mut start_row = wrap_range.start.row;
11789 let mut end_row = wrap_range.end.row;
11790
11791 // Skip selections that overlap with a range that has already been rewrapped.
11792 let selection_range = start_row..end_row;
11793 if rewrapped_row_ranges
11794 .iter()
11795 .any(|range| range.overlaps(&selection_range))
11796 {
11797 continue;
11798 }
11799
11800 let tab_size = language_settings.tab_size;
11801
11802 let indent_prefix = indent_size.chars().collect::<String>();
11803 let mut line_prefix = indent_prefix.clone();
11804 let mut inside_comment = false;
11805 if let Some(prefix) = &comment_prefix {
11806 line_prefix.push_str(prefix);
11807 inside_comment = true;
11808 }
11809 if let Some(prefix) = &rewrap_prefix {
11810 line_prefix.push_str(prefix);
11811 }
11812
11813 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11814 RewrapBehavior::InComments => inside_comment,
11815 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11816 RewrapBehavior::Anywhere => true,
11817 };
11818
11819 let should_rewrap = options.override_language_settings
11820 || allow_rewrap_based_on_language
11821 || self.hard_wrap.is_some();
11822 if !should_rewrap {
11823 continue;
11824 }
11825
11826 if from_empty_selection {
11827 'expand_upwards: while start_row > 0 {
11828 let prev_row = start_row - 1;
11829 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11830 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11831 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11832 {
11833 start_row = prev_row;
11834 } else {
11835 break 'expand_upwards;
11836 }
11837 }
11838
11839 'expand_downwards: while end_row < buffer.max_point().row {
11840 let next_row = end_row + 1;
11841 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11842 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11843 && !buffer.is_line_blank(MultiBufferRow(next_row))
11844 {
11845 end_row = next_row;
11846 } else {
11847 break 'expand_downwards;
11848 }
11849 }
11850 }
11851
11852 let start = Point::new(start_row, 0);
11853 let start_offset = start.to_offset(&buffer);
11854 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11855 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11856 let Some(lines_without_prefixes) = selection_text
11857 .lines()
11858 .enumerate()
11859 .map(|(ix, line)| {
11860 let line_trimmed = line.trim_start();
11861 if rewrap_prefix.is_some() && ix > 0 {
11862 Ok(line_trimmed)
11863 } else {
11864 line_trimmed
11865 .strip_prefix(&line_prefix.trim_start())
11866 .with_context(|| {
11867 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11868 })
11869 }
11870 })
11871 .collect::<Result<Vec<_>, _>>()
11872 .log_err()
11873 else {
11874 continue;
11875 };
11876
11877 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11878 buffer
11879 .language_settings_at(Point::new(start_row, 0), cx)
11880 .preferred_line_length as usize
11881 });
11882
11883 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11884 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11885 } else {
11886 line_prefix.clone()
11887 };
11888
11889 let wrapped_text = wrap_with_prefix(
11890 line_prefix,
11891 subsequent_lines_prefix,
11892 lines_without_prefixes.join("\n"),
11893 wrap_column,
11894 tab_size,
11895 options.preserve_existing_whitespace,
11896 );
11897
11898 // TODO: should always use char-based diff while still supporting cursor behavior that
11899 // matches vim.
11900 let mut diff_options = DiffOptions::default();
11901 if options.override_language_settings {
11902 diff_options.max_word_diff_len = 0;
11903 diff_options.max_word_diff_line_count = 0;
11904 } else {
11905 diff_options.max_word_diff_len = usize::MAX;
11906 diff_options.max_word_diff_line_count = usize::MAX;
11907 }
11908
11909 for (old_range, new_text) in
11910 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11911 {
11912 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11913 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11914 edits.push((edit_start..edit_end, new_text));
11915 }
11916
11917 rewrapped_row_ranges.push(start_row..=end_row);
11918 }
11919
11920 self.buffer
11921 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11922 }
11923
11924 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11925 let mut text = String::new();
11926 let buffer = self.buffer.read(cx).snapshot(cx);
11927 let mut selections = self.selections.all::<Point>(cx);
11928 let mut clipboard_selections = Vec::with_capacity(selections.len());
11929 {
11930 let max_point = buffer.max_point();
11931 let mut is_first = true;
11932 for selection in &mut selections {
11933 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11934 if is_entire_line {
11935 selection.start = Point::new(selection.start.row, 0);
11936 if !selection.is_empty() && selection.end.column == 0 {
11937 selection.end = cmp::min(max_point, selection.end);
11938 } else {
11939 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11940 }
11941 selection.goal = SelectionGoal::None;
11942 }
11943 if is_first {
11944 is_first = false;
11945 } else {
11946 text += "\n";
11947 }
11948 let mut len = 0;
11949 for chunk in buffer.text_for_range(selection.start..selection.end) {
11950 text.push_str(chunk);
11951 len += chunk.len();
11952 }
11953 clipboard_selections.push(ClipboardSelection {
11954 len,
11955 is_entire_line,
11956 first_line_indent: buffer
11957 .indent_size_for_line(MultiBufferRow(selection.start.row))
11958 .len,
11959 });
11960 }
11961 }
11962
11963 self.transact(window, cx, |this, window, cx| {
11964 this.change_selections(Default::default(), window, cx, |s| {
11965 s.select(selections);
11966 });
11967 this.insert("", window, cx);
11968 });
11969 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11970 }
11971
11972 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11973 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11974 let item = self.cut_common(window, cx);
11975 cx.write_to_clipboard(item);
11976 }
11977
11978 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11980 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11981 s.move_with(|snapshot, sel| {
11982 if sel.is_empty() {
11983 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11984 }
11985 });
11986 });
11987 let item = self.cut_common(window, cx);
11988 cx.set_global(KillRing(item))
11989 }
11990
11991 pub fn kill_ring_yank(
11992 &mut self,
11993 _: &KillRingYank,
11994 window: &mut Window,
11995 cx: &mut Context<Self>,
11996 ) {
11997 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11998 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11999 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12000 (kill_ring.text().to_string(), kill_ring.metadata_json())
12001 } else {
12002 return;
12003 }
12004 } else {
12005 return;
12006 };
12007 self.do_paste(&text, metadata, false, window, cx);
12008 }
12009
12010 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12011 self.do_copy(true, cx);
12012 }
12013
12014 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12015 self.do_copy(false, cx);
12016 }
12017
12018 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12019 let selections = self.selections.all::<Point>(cx);
12020 let buffer = self.buffer.read(cx).read(cx);
12021 let mut text = String::new();
12022
12023 let mut clipboard_selections = Vec::with_capacity(selections.len());
12024 {
12025 let max_point = buffer.max_point();
12026 let mut is_first = true;
12027 for selection in &selections {
12028 let mut start = selection.start;
12029 let mut end = selection.end;
12030 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12031 if is_entire_line {
12032 start = Point::new(start.row, 0);
12033 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12034 }
12035
12036 let mut trimmed_selections = Vec::new();
12037 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12038 let row = MultiBufferRow(start.row);
12039 let first_indent = buffer.indent_size_for_line(row);
12040 if first_indent.len == 0 || start.column > first_indent.len {
12041 trimmed_selections.push(start..end);
12042 } else {
12043 trimmed_selections.push(
12044 Point::new(row.0, first_indent.len)
12045 ..Point::new(row.0, buffer.line_len(row)),
12046 );
12047 for row in start.row + 1..=end.row {
12048 let mut line_len = buffer.line_len(MultiBufferRow(row));
12049 if row == end.row {
12050 line_len = end.column;
12051 }
12052 if line_len == 0 {
12053 trimmed_selections
12054 .push(Point::new(row, 0)..Point::new(row, line_len));
12055 continue;
12056 }
12057 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12058 if row_indent_size.len >= first_indent.len {
12059 trimmed_selections.push(
12060 Point::new(row, first_indent.len)..Point::new(row, line_len),
12061 );
12062 } else {
12063 trimmed_selections.clear();
12064 trimmed_selections.push(start..end);
12065 break;
12066 }
12067 }
12068 }
12069 } else {
12070 trimmed_selections.push(start..end);
12071 }
12072
12073 for trimmed_range in trimmed_selections {
12074 if is_first {
12075 is_first = false;
12076 } else {
12077 text += "\n";
12078 }
12079 let mut len = 0;
12080 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12081 text.push_str(chunk);
12082 len += chunk.len();
12083 }
12084 clipboard_selections.push(ClipboardSelection {
12085 len,
12086 is_entire_line,
12087 first_line_indent: buffer
12088 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12089 .len,
12090 });
12091 }
12092 }
12093 }
12094
12095 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12096 text,
12097 clipboard_selections,
12098 ));
12099 }
12100
12101 pub fn do_paste(
12102 &mut self,
12103 text: &String,
12104 clipboard_selections: Option<Vec<ClipboardSelection>>,
12105 handle_entire_lines: bool,
12106 window: &mut Window,
12107 cx: &mut Context<Self>,
12108 ) {
12109 if self.read_only(cx) {
12110 return;
12111 }
12112
12113 let clipboard_text = Cow::Borrowed(text);
12114
12115 self.transact(window, cx, |this, window, cx| {
12116 if let Some(mut clipboard_selections) = clipboard_selections {
12117 let old_selections = this.selections.all::<usize>(cx);
12118 let all_selections_were_entire_line =
12119 clipboard_selections.iter().all(|s| s.is_entire_line);
12120 let first_selection_indent_column =
12121 clipboard_selections.first().map(|s| s.first_line_indent);
12122 if clipboard_selections.len() != old_selections.len() {
12123 clipboard_selections.drain(..);
12124 }
12125 let cursor_offset = this.selections.last::<usize>(cx).head();
12126 let mut auto_indent_on_paste = true;
12127
12128 this.buffer.update(cx, |buffer, cx| {
12129 let snapshot = buffer.read(cx);
12130 auto_indent_on_paste = snapshot
12131 .language_settings_at(cursor_offset, cx)
12132 .auto_indent_on_paste;
12133
12134 let mut start_offset = 0;
12135 let mut edits = Vec::new();
12136 let mut original_indent_columns = Vec::new();
12137 for (ix, selection) in old_selections.iter().enumerate() {
12138 let to_insert;
12139 let entire_line;
12140 let original_indent_column;
12141 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12142 let end_offset = start_offset + clipboard_selection.len;
12143 to_insert = &clipboard_text[start_offset..end_offset];
12144 entire_line = clipboard_selection.is_entire_line;
12145 start_offset = end_offset + 1;
12146 original_indent_column = Some(clipboard_selection.first_line_indent);
12147 } else {
12148 to_insert = clipboard_text.as_str();
12149 entire_line = all_selections_were_entire_line;
12150 original_indent_column = first_selection_indent_column
12151 }
12152
12153 // If the corresponding selection was empty when this slice of the
12154 // clipboard text was written, then the entire line containing the
12155 // selection was copied. If this selection is also currently empty,
12156 // then paste the line before the current line of the buffer.
12157 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12158 let column = selection.start.to_point(&snapshot).column as usize;
12159 let line_start = selection.start - column;
12160 line_start..line_start
12161 } else {
12162 selection.range()
12163 };
12164
12165 edits.push((range, to_insert));
12166 original_indent_columns.push(original_indent_column);
12167 }
12168 drop(snapshot);
12169
12170 buffer.edit(
12171 edits,
12172 if auto_indent_on_paste {
12173 Some(AutoindentMode::Block {
12174 original_indent_columns,
12175 })
12176 } else {
12177 None
12178 },
12179 cx,
12180 );
12181 });
12182
12183 let selections = this.selections.all::<usize>(cx);
12184 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12185 } else {
12186 this.insert(&clipboard_text, window, cx);
12187 }
12188 });
12189 }
12190
12191 pub fn diff_clipboard_with_selection(
12192 &mut self,
12193 _: &DiffClipboardWithSelection,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 let selections = self.selections.all::<usize>(cx);
12198
12199 if selections.is_empty() {
12200 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12201 return;
12202 };
12203
12204 let clipboard_text = match cx.read_from_clipboard() {
12205 Some(item) => match item.entries().first() {
12206 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12207 _ => None,
12208 },
12209 None => None,
12210 };
12211
12212 let Some(clipboard_text) = clipboard_text else {
12213 log::warn!("Clipboard doesn't contain text.");
12214 return;
12215 };
12216
12217 window.dispatch_action(
12218 Box::new(DiffClipboardWithSelectionData {
12219 clipboard_text,
12220 editor: cx.entity(),
12221 }),
12222 cx,
12223 );
12224 }
12225
12226 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12228 if let Some(item) = cx.read_from_clipboard() {
12229 let entries = item.entries();
12230
12231 match entries.first() {
12232 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12233 // of all the pasted entries.
12234 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12235 .do_paste(
12236 clipboard_string.text(),
12237 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12238 true,
12239 window,
12240 cx,
12241 ),
12242 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12243 }
12244 }
12245 }
12246
12247 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12248 if self.read_only(cx) {
12249 return;
12250 }
12251
12252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12253
12254 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12255 if let Some((selections, _)) =
12256 self.selection_history.transaction(transaction_id).cloned()
12257 {
12258 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12259 s.select_anchors(selections.to_vec());
12260 });
12261 } else {
12262 log::error!(
12263 "No entry in selection_history found for undo. \
12264 This may correspond to a bug where undo does not update the selection. \
12265 If this is occurring, please add details to \
12266 https://github.com/zed-industries/zed/issues/22692"
12267 );
12268 }
12269 self.request_autoscroll(Autoscroll::fit(), cx);
12270 self.unmark_text(window, cx);
12271 self.refresh_inline_completion(true, false, window, cx);
12272 cx.emit(EditorEvent::Edited { transaction_id });
12273 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12274 }
12275 }
12276
12277 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12278 if self.read_only(cx) {
12279 return;
12280 }
12281
12282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12283
12284 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12285 if let Some((_, Some(selections))) =
12286 self.selection_history.transaction(transaction_id).cloned()
12287 {
12288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12289 s.select_anchors(selections.to_vec());
12290 });
12291 } else {
12292 log::error!(
12293 "No entry in selection_history found for redo. \
12294 This may correspond to a bug where undo does not update the selection. \
12295 If this is occurring, please add details to \
12296 https://github.com/zed-industries/zed/issues/22692"
12297 );
12298 }
12299 self.request_autoscroll(Autoscroll::fit(), cx);
12300 self.unmark_text(window, cx);
12301 self.refresh_inline_completion(true, false, window, cx);
12302 cx.emit(EditorEvent::Edited { transaction_id });
12303 }
12304 }
12305
12306 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12307 self.buffer
12308 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12309 }
12310
12311 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12312 self.buffer
12313 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12314 }
12315
12316 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12318 self.change_selections(Default::default(), window, cx, |s| {
12319 s.move_with(|map, selection| {
12320 let cursor = if selection.is_empty() {
12321 movement::left(map, selection.start)
12322 } else {
12323 selection.start
12324 };
12325 selection.collapse_to(cursor, SelectionGoal::None);
12326 });
12327 })
12328 }
12329
12330 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12332 self.change_selections(Default::default(), window, cx, |s| {
12333 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12334 })
12335 }
12336
12337 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12339 self.change_selections(Default::default(), window, cx, |s| {
12340 s.move_with(|map, selection| {
12341 let cursor = if selection.is_empty() {
12342 movement::right(map, selection.end)
12343 } else {
12344 selection.end
12345 };
12346 selection.collapse_to(cursor, SelectionGoal::None)
12347 });
12348 })
12349 }
12350
12351 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12353 self.change_selections(Default::default(), window, cx, |s| {
12354 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12355 })
12356 }
12357
12358 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12359 if self.take_rename(true, window, cx).is_some() {
12360 return;
12361 }
12362
12363 if self.mode.is_single_line() {
12364 cx.propagate();
12365 return;
12366 }
12367
12368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12369
12370 let text_layout_details = &self.text_layout_details(window);
12371 let selection_count = self.selections.count();
12372 let first_selection = self.selections.first_anchor();
12373
12374 self.change_selections(Default::default(), window, cx, |s| {
12375 s.move_with(|map, selection| {
12376 if !selection.is_empty() {
12377 selection.goal = SelectionGoal::None;
12378 }
12379 let (cursor, goal) = movement::up(
12380 map,
12381 selection.start,
12382 selection.goal,
12383 false,
12384 text_layout_details,
12385 );
12386 selection.collapse_to(cursor, goal);
12387 });
12388 });
12389
12390 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12391 {
12392 cx.propagate();
12393 }
12394 }
12395
12396 pub fn move_up_by_lines(
12397 &mut self,
12398 action: &MoveUpByLines,
12399 window: &mut Window,
12400 cx: &mut Context<Self>,
12401 ) {
12402 if self.take_rename(true, window, cx).is_some() {
12403 return;
12404 }
12405
12406 if self.mode.is_single_line() {
12407 cx.propagate();
12408 return;
12409 }
12410
12411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12412
12413 let text_layout_details = &self.text_layout_details(window);
12414
12415 self.change_selections(Default::default(), window, cx, |s| {
12416 s.move_with(|map, selection| {
12417 if !selection.is_empty() {
12418 selection.goal = SelectionGoal::None;
12419 }
12420 let (cursor, goal) = movement::up_by_rows(
12421 map,
12422 selection.start,
12423 action.lines,
12424 selection.goal,
12425 false,
12426 text_layout_details,
12427 );
12428 selection.collapse_to(cursor, goal);
12429 });
12430 })
12431 }
12432
12433 pub fn move_down_by_lines(
12434 &mut self,
12435 action: &MoveDownByLines,
12436 window: &mut Window,
12437 cx: &mut Context<Self>,
12438 ) {
12439 if self.take_rename(true, window, cx).is_some() {
12440 return;
12441 }
12442
12443 if self.mode.is_single_line() {
12444 cx.propagate();
12445 return;
12446 }
12447
12448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12449
12450 let text_layout_details = &self.text_layout_details(window);
12451
12452 self.change_selections(Default::default(), window, cx, |s| {
12453 s.move_with(|map, selection| {
12454 if !selection.is_empty() {
12455 selection.goal = SelectionGoal::None;
12456 }
12457 let (cursor, goal) = movement::down_by_rows(
12458 map,
12459 selection.start,
12460 action.lines,
12461 selection.goal,
12462 false,
12463 text_layout_details,
12464 );
12465 selection.collapse_to(cursor, goal);
12466 });
12467 })
12468 }
12469
12470 pub fn select_down_by_lines(
12471 &mut self,
12472 action: &SelectDownByLines,
12473 window: &mut Window,
12474 cx: &mut Context<Self>,
12475 ) {
12476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12477 let text_layout_details = &self.text_layout_details(window);
12478 self.change_selections(Default::default(), window, cx, |s| {
12479 s.move_heads_with(|map, head, goal| {
12480 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12481 })
12482 })
12483 }
12484
12485 pub fn select_up_by_lines(
12486 &mut self,
12487 action: &SelectUpByLines,
12488 window: &mut Window,
12489 cx: &mut Context<Self>,
12490 ) {
12491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12492 let text_layout_details = &self.text_layout_details(window);
12493 self.change_selections(Default::default(), window, cx, |s| {
12494 s.move_heads_with(|map, head, goal| {
12495 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12496 })
12497 })
12498 }
12499
12500 pub fn select_page_up(
12501 &mut self,
12502 _: &SelectPageUp,
12503 window: &mut Window,
12504 cx: &mut Context<Self>,
12505 ) {
12506 let Some(row_count) = self.visible_row_count() else {
12507 return;
12508 };
12509
12510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12511
12512 let text_layout_details = &self.text_layout_details(window);
12513
12514 self.change_selections(Default::default(), window, cx, |s| {
12515 s.move_heads_with(|map, head, goal| {
12516 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12517 })
12518 })
12519 }
12520
12521 pub fn move_page_up(
12522 &mut self,
12523 action: &MovePageUp,
12524 window: &mut Window,
12525 cx: &mut Context<Self>,
12526 ) {
12527 if self.take_rename(true, window, cx).is_some() {
12528 return;
12529 }
12530
12531 if self
12532 .context_menu
12533 .borrow_mut()
12534 .as_mut()
12535 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12536 .unwrap_or(false)
12537 {
12538 return;
12539 }
12540
12541 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12542 cx.propagate();
12543 return;
12544 }
12545
12546 let Some(row_count) = self.visible_row_count() else {
12547 return;
12548 };
12549
12550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12551
12552 let effects = if action.center_cursor {
12553 SelectionEffects::scroll(Autoscroll::center())
12554 } else {
12555 SelectionEffects::default()
12556 };
12557
12558 let text_layout_details = &self.text_layout_details(window);
12559
12560 self.change_selections(effects, window, cx, |s| {
12561 s.move_with(|map, selection| {
12562 if !selection.is_empty() {
12563 selection.goal = SelectionGoal::None;
12564 }
12565 let (cursor, goal) = movement::up_by_rows(
12566 map,
12567 selection.end,
12568 row_count,
12569 selection.goal,
12570 false,
12571 text_layout_details,
12572 );
12573 selection.collapse_to(cursor, goal);
12574 });
12575 });
12576 }
12577
12578 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12579 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12580 let text_layout_details = &self.text_layout_details(window);
12581 self.change_selections(Default::default(), window, cx, |s| {
12582 s.move_heads_with(|map, head, goal| {
12583 movement::up(map, head, goal, false, text_layout_details)
12584 })
12585 })
12586 }
12587
12588 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12589 self.take_rename(true, window, cx);
12590
12591 if self.mode.is_single_line() {
12592 cx.propagate();
12593 return;
12594 }
12595
12596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12597
12598 let text_layout_details = &self.text_layout_details(window);
12599 let selection_count = self.selections.count();
12600 let first_selection = self.selections.first_anchor();
12601
12602 self.change_selections(Default::default(), window, cx, |s| {
12603 s.move_with(|map, selection| {
12604 if !selection.is_empty() {
12605 selection.goal = SelectionGoal::None;
12606 }
12607 let (cursor, goal) = movement::down(
12608 map,
12609 selection.end,
12610 selection.goal,
12611 false,
12612 text_layout_details,
12613 );
12614 selection.collapse_to(cursor, goal);
12615 });
12616 });
12617
12618 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12619 {
12620 cx.propagate();
12621 }
12622 }
12623
12624 pub fn select_page_down(
12625 &mut self,
12626 _: &SelectPageDown,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 ) {
12630 let Some(row_count) = self.visible_row_count() else {
12631 return;
12632 };
12633
12634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12635
12636 let text_layout_details = &self.text_layout_details(window);
12637
12638 self.change_selections(Default::default(), window, cx, |s| {
12639 s.move_heads_with(|map, head, goal| {
12640 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12641 })
12642 })
12643 }
12644
12645 pub fn move_page_down(
12646 &mut self,
12647 action: &MovePageDown,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) {
12651 if self.take_rename(true, window, cx).is_some() {
12652 return;
12653 }
12654
12655 if self
12656 .context_menu
12657 .borrow_mut()
12658 .as_mut()
12659 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12660 .unwrap_or(false)
12661 {
12662 return;
12663 }
12664
12665 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12666 cx.propagate();
12667 return;
12668 }
12669
12670 let Some(row_count) = self.visible_row_count() else {
12671 return;
12672 };
12673
12674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12675
12676 let effects = if action.center_cursor {
12677 SelectionEffects::scroll(Autoscroll::center())
12678 } else {
12679 SelectionEffects::default()
12680 };
12681
12682 let text_layout_details = &self.text_layout_details(window);
12683 self.change_selections(effects, window, cx, |s| {
12684 s.move_with(|map, selection| {
12685 if !selection.is_empty() {
12686 selection.goal = SelectionGoal::None;
12687 }
12688 let (cursor, goal) = movement::down_by_rows(
12689 map,
12690 selection.end,
12691 row_count,
12692 selection.goal,
12693 false,
12694 text_layout_details,
12695 );
12696 selection.collapse_to(cursor, goal);
12697 });
12698 });
12699 }
12700
12701 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12703 let text_layout_details = &self.text_layout_details(window);
12704 self.change_selections(Default::default(), window, cx, |s| {
12705 s.move_heads_with(|map, head, goal| {
12706 movement::down(map, head, goal, false, text_layout_details)
12707 })
12708 });
12709 }
12710
12711 pub fn context_menu_first(
12712 &mut self,
12713 _: &ContextMenuFirst,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12718 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12719 }
12720 }
12721
12722 pub fn context_menu_prev(
12723 &mut self,
12724 _: &ContextMenuPrevious,
12725 window: &mut Window,
12726 cx: &mut Context<Self>,
12727 ) {
12728 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12729 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12730 }
12731 }
12732
12733 pub fn context_menu_next(
12734 &mut self,
12735 _: &ContextMenuNext,
12736 window: &mut Window,
12737 cx: &mut Context<Self>,
12738 ) {
12739 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12740 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12741 }
12742 }
12743
12744 pub fn context_menu_last(
12745 &mut self,
12746 _: &ContextMenuLast,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12751 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12752 }
12753 }
12754
12755 pub fn signature_help_prev(
12756 &mut self,
12757 _: &SignatureHelpPrevious,
12758 _: &mut Window,
12759 cx: &mut Context<Self>,
12760 ) {
12761 if let Some(popover) = self.signature_help_state.popover_mut() {
12762 if popover.current_signature == 0 {
12763 popover.current_signature = popover.signatures.len() - 1;
12764 } else {
12765 popover.current_signature -= 1;
12766 }
12767 cx.notify();
12768 }
12769 }
12770
12771 pub fn signature_help_next(
12772 &mut self,
12773 _: &SignatureHelpNext,
12774 _: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 if let Some(popover) = self.signature_help_state.popover_mut() {
12778 if popover.current_signature + 1 == popover.signatures.len() {
12779 popover.current_signature = 0;
12780 } else {
12781 popover.current_signature += 1;
12782 }
12783 cx.notify();
12784 }
12785 }
12786
12787 pub fn move_to_previous_word_start(
12788 &mut self,
12789 _: &MoveToPreviousWordStart,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) {
12793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12794 self.change_selections(Default::default(), window, cx, |s| {
12795 s.move_cursors_with(|map, head, _| {
12796 (
12797 movement::previous_word_start(map, head),
12798 SelectionGoal::None,
12799 )
12800 });
12801 })
12802 }
12803
12804 pub fn move_to_previous_subword_start(
12805 &mut self,
12806 _: &MoveToPreviousSubwordStart,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12811 self.change_selections(Default::default(), window, cx, |s| {
12812 s.move_cursors_with(|map, head, _| {
12813 (
12814 movement::previous_subword_start(map, head),
12815 SelectionGoal::None,
12816 )
12817 });
12818 })
12819 }
12820
12821 pub fn select_to_previous_word_start(
12822 &mut self,
12823 _: &SelectToPreviousWordStart,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12828 self.change_selections(Default::default(), window, cx, |s| {
12829 s.move_heads_with(|map, head, _| {
12830 (
12831 movement::previous_word_start(map, head),
12832 SelectionGoal::None,
12833 )
12834 });
12835 })
12836 }
12837
12838 pub fn select_to_previous_subword_start(
12839 &mut self,
12840 _: &SelectToPreviousSubwordStart,
12841 window: &mut Window,
12842 cx: &mut Context<Self>,
12843 ) {
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 self.change_selections(Default::default(), window, cx, |s| {
12846 s.move_heads_with(|map, head, _| {
12847 (
12848 movement::previous_subword_start(map, head),
12849 SelectionGoal::None,
12850 )
12851 });
12852 })
12853 }
12854
12855 pub fn delete_to_previous_word_start(
12856 &mut self,
12857 action: &DeleteToPreviousWordStart,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12862 self.transact(window, cx, |this, window, cx| {
12863 this.select_autoclose_pair(window, cx);
12864 this.change_selections(Default::default(), window, cx, |s| {
12865 s.move_with(|map, selection| {
12866 if selection.is_empty() {
12867 let cursor = if action.ignore_newlines {
12868 movement::previous_word_start(map, selection.head())
12869 } else {
12870 movement::previous_word_start_or_newline(map, selection.head())
12871 };
12872 selection.set_head(cursor, SelectionGoal::None);
12873 }
12874 });
12875 });
12876 this.insert("", window, cx);
12877 });
12878 }
12879
12880 pub fn delete_to_previous_subword_start(
12881 &mut self,
12882 _: &DeleteToPreviousSubwordStart,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12887 self.transact(window, cx, |this, window, cx| {
12888 this.select_autoclose_pair(window, cx);
12889 this.change_selections(Default::default(), window, cx, |s| {
12890 s.move_with(|map, selection| {
12891 if selection.is_empty() {
12892 let cursor = movement::previous_subword_start(map, selection.head());
12893 selection.set_head(cursor, SelectionGoal::None);
12894 }
12895 });
12896 });
12897 this.insert("", window, cx);
12898 });
12899 }
12900
12901 pub fn move_to_next_word_end(
12902 &mut self,
12903 _: &MoveToNextWordEnd,
12904 window: &mut Window,
12905 cx: &mut Context<Self>,
12906 ) {
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12908 self.change_selections(Default::default(), window, cx, |s| {
12909 s.move_cursors_with(|map, head, _| {
12910 (movement::next_word_end(map, head), SelectionGoal::None)
12911 });
12912 })
12913 }
12914
12915 pub fn move_to_next_subword_end(
12916 &mut self,
12917 _: &MoveToNextSubwordEnd,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12922 self.change_selections(Default::default(), window, cx, |s| {
12923 s.move_cursors_with(|map, head, _| {
12924 (movement::next_subword_end(map, head), SelectionGoal::None)
12925 });
12926 })
12927 }
12928
12929 pub fn select_to_next_word_end(
12930 &mut self,
12931 _: &SelectToNextWordEnd,
12932 window: &mut Window,
12933 cx: &mut Context<Self>,
12934 ) {
12935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12936 self.change_selections(Default::default(), window, cx, |s| {
12937 s.move_heads_with(|map, head, _| {
12938 (movement::next_word_end(map, head), SelectionGoal::None)
12939 });
12940 })
12941 }
12942
12943 pub fn select_to_next_subword_end(
12944 &mut self,
12945 _: &SelectToNextSubwordEnd,
12946 window: &mut Window,
12947 cx: &mut Context<Self>,
12948 ) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_heads_with(|map, head, _| {
12952 (movement::next_subword_end(map, head), SelectionGoal::None)
12953 });
12954 })
12955 }
12956
12957 pub fn delete_to_next_word_end(
12958 &mut self,
12959 action: &DeleteToNextWordEnd,
12960 window: &mut Window,
12961 cx: &mut Context<Self>,
12962 ) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12964 self.transact(window, cx, |this, window, cx| {
12965 this.change_selections(Default::default(), window, cx, |s| {
12966 s.move_with(|map, selection| {
12967 if selection.is_empty() {
12968 let cursor = if action.ignore_newlines {
12969 movement::next_word_end(map, selection.head())
12970 } else {
12971 movement::next_word_end_or_newline(map, selection.head())
12972 };
12973 selection.set_head(cursor, SelectionGoal::None);
12974 }
12975 });
12976 });
12977 this.insert("", window, cx);
12978 });
12979 }
12980
12981 pub fn delete_to_next_subword_end(
12982 &mut self,
12983 _: &DeleteToNextSubwordEnd,
12984 window: &mut Window,
12985 cx: &mut Context<Self>,
12986 ) {
12987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12988 self.transact(window, cx, |this, window, cx| {
12989 this.change_selections(Default::default(), window, cx, |s| {
12990 s.move_with(|map, selection| {
12991 if selection.is_empty() {
12992 let cursor = movement::next_subword_end(map, selection.head());
12993 selection.set_head(cursor, SelectionGoal::None);
12994 }
12995 });
12996 });
12997 this.insert("", window, cx);
12998 });
12999 }
13000
13001 pub fn move_to_beginning_of_line(
13002 &mut self,
13003 action: &MoveToBeginningOfLine,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13008 self.change_selections(Default::default(), window, cx, |s| {
13009 s.move_cursors_with(|map, head, _| {
13010 (
13011 movement::indented_line_beginning(
13012 map,
13013 head,
13014 action.stop_at_soft_wraps,
13015 action.stop_at_indent,
13016 ),
13017 SelectionGoal::None,
13018 )
13019 });
13020 })
13021 }
13022
13023 pub fn select_to_beginning_of_line(
13024 &mut self,
13025 action: &SelectToBeginningOfLine,
13026 window: &mut Window,
13027 cx: &mut Context<Self>,
13028 ) {
13029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13030 self.change_selections(Default::default(), window, cx, |s| {
13031 s.move_heads_with(|map, head, _| {
13032 (
13033 movement::indented_line_beginning(
13034 map,
13035 head,
13036 action.stop_at_soft_wraps,
13037 action.stop_at_indent,
13038 ),
13039 SelectionGoal::None,
13040 )
13041 });
13042 });
13043 }
13044
13045 pub fn delete_to_beginning_of_line(
13046 &mut self,
13047 action: &DeleteToBeginningOfLine,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13052 self.transact(window, cx, |this, window, cx| {
13053 this.change_selections(Default::default(), window, cx, |s| {
13054 s.move_with(|_, selection| {
13055 selection.reversed = true;
13056 });
13057 });
13058
13059 this.select_to_beginning_of_line(
13060 &SelectToBeginningOfLine {
13061 stop_at_soft_wraps: false,
13062 stop_at_indent: action.stop_at_indent,
13063 },
13064 window,
13065 cx,
13066 );
13067 this.backspace(&Backspace, window, cx);
13068 });
13069 }
13070
13071 pub fn move_to_end_of_line(
13072 &mut self,
13073 action: &MoveToEndOfLine,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_cursors_with(|map, head, _| {
13080 (
13081 movement::line_end(map, head, action.stop_at_soft_wraps),
13082 SelectionGoal::None,
13083 )
13084 });
13085 })
13086 }
13087
13088 pub fn select_to_end_of_line(
13089 &mut self,
13090 action: &SelectToEndOfLine,
13091 window: &mut Window,
13092 cx: &mut Context<Self>,
13093 ) {
13094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13095 self.change_selections(Default::default(), window, cx, |s| {
13096 s.move_heads_with(|map, head, _| {
13097 (
13098 movement::line_end(map, head, action.stop_at_soft_wraps),
13099 SelectionGoal::None,
13100 )
13101 });
13102 })
13103 }
13104
13105 pub fn delete_to_end_of_line(
13106 &mut self,
13107 _: &DeleteToEndOfLine,
13108 window: &mut Window,
13109 cx: &mut Context<Self>,
13110 ) {
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13112 self.transact(window, cx, |this, window, cx| {
13113 this.select_to_end_of_line(
13114 &SelectToEndOfLine {
13115 stop_at_soft_wraps: false,
13116 },
13117 window,
13118 cx,
13119 );
13120 this.delete(&Delete, window, cx);
13121 });
13122 }
13123
13124 pub fn cut_to_end_of_line(
13125 &mut self,
13126 _: &CutToEndOfLine,
13127 window: &mut Window,
13128 cx: &mut Context<Self>,
13129 ) {
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13131 self.transact(window, cx, |this, window, cx| {
13132 this.select_to_end_of_line(
13133 &SelectToEndOfLine {
13134 stop_at_soft_wraps: false,
13135 },
13136 window,
13137 cx,
13138 );
13139 this.cut(&Cut, window, cx);
13140 });
13141 }
13142
13143 pub fn move_to_start_of_paragraph(
13144 &mut self,
13145 _: &MoveToStartOfParagraph,
13146 window: &mut Window,
13147 cx: &mut Context<Self>,
13148 ) {
13149 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13150 cx.propagate();
13151 return;
13152 }
13153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13154 self.change_selections(Default::default(), window, cx, |s| {
13155 s.move_with(|map, selection| {
13156 selection.collapse_to(
13157 movement::start_of_paragraph(map, selection.head(), 1),
13158 SelectionGoal::None,
13159 )
13160 });
13161 })
13162 }
13163
13164 pub fn move_to_end_of_paragraph(
13165 &mut self,
13166 _: &MoveToEndOfParagraph,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13171 cx.propagate();
13172 return;
13173 }
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13175 self.change_selections(Default::default(), window, cx, |s| {
13176 s.move_with(|map, selection| {
13177 selection.collapse_to(
13178 movement::end_of_paragraph(map, selection.head(), 1),
13179 SelectionGoal::None,
13180 )
13181 });
13182 })
13183 }
13184
13185 pub fn select_to_start_of_paragraph(
13186 &mut self,
13187 _: &SelectToStartOfParagraph,
13188 window: &mut Window,
13189 cx: &mut Context<Self>,
13190 ) {
13191 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13192 cx.propagate();
13193 return;
13194 }
13195 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13196 self.change_selections(Default::default(), window, cx, |s| {
13197 s.move_heads_with(|map, head, _| {
13198 (
13199 movement::start_of_paragraph(map, head, 1),
13200 SelectionGoal::None,
13201 )
13202 });
13203 })
13204 }
13205
13206 pub fn select_to_end_of_paragraph(
13207 &mut self,
13208 _: &SelectToEndOfParagraph,
13209 window: &mut Window,
13210 cx: &mut Context<Self>,
13211 ) {
13212 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13213 cx.propagate();
13214 return;
13215 }
13216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13217 self.change_selections(Default::default(), window, cx, |s| {
13218 s.move_heads_with(|map, head, _| {
13219 (
13220 movement::end_of_paragraph(map, head, 1),
13221 SelectionGoal::None,
13222 )
13223 });
13224 })
13225 }
13226
13227 pub fn move_to_start_of_excerpt(
13228 &mut self,
13229 _: &MoveToStartOfExcerpt,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13234 cx.propagate();
13235 return;
13236 }
13237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13238 self.change_selections(Default::default(), window, cx, |s| {
13239 s.move_with(|map, selection| {
13240 selection.collapse_to(
13241 movement::start_of_excerpt(
13242 map,
13243 selection.head(),
13244 workspace::searchable::Direction::Prev,
13245 ),
13246 SelectionGoal::None,
13247 )
13248 });
13249 })
13250 }
13251
13252 pub fn move_to_start_of_next_excerpt(
13253 &mut self,
13254 _: &MoveToStartOfNextExcerpt,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13259 cx.propagate();
13260 return;
13261 }
13262
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_with(|map, selection| {
13265 selection.collapse_to(
13266 movement::start_of_excerpt(
13267 map,
13268 selection.head(),
13269 workspace::searchable::Direction::Next,
13270 ),
13271 SelectionGoal::None,
13272 )
13273 });
13274 })
13275 }
13276
13277 pub fn move_to_end_of_excerpt(
13278 &mut self,
13279 _: &MoveToEndOfExcerpt,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) {
13283 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13284 cx.propagate();
13285 return;
13286 }
13287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13288 self.change_selections(Default::default(), window, cx, |s| {
13289 s.move_with(|map, selection| {
13290 selection.collapse_to(
13291 movement::end_of_excerpt(
13292 map,
13293 selection.head(),
13294 workspace::searchable::Direction::Next,
13295 ),
13296 SelectionGoal::None,
13297 )
13298 });
13299 })
13300 }
13301
13302 pub fn move_to_end_of_previous_excerpt(
13303 &mut self,
13304 _: &MoveToEndOfPreviousExcerpt,
13305 window: &mut Window,
13306 cx: &mut Context<Self>,
13307 ) {
13308 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13309 cx.propagate();
13310 return;
13311 }
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13313 self.change_selections(Default::default(), window, cx, |s| {
13314 s.move_with(|map, selection| {
13315 selection.collapse_to(
13316 movement::end_of_excerpt(
13317 map,
13318 selection.head(),
13319 workspace::searchable::Direction::Prev,
13320 ),
13321 SelectionGoal::None,
13322 )
13323 });
13324 })
13325 }
13326
13327 pub fn select_to_start_of_excerpt(
13328 &mut self,
13329 _: &SelectToStartOfExcerpt,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13334 cx.propagate();
13335 return;
13336 }
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338 self.change_selections(Default::default(), window, cx, |s| {
13339 s.move_heads_with(|map, head, _| {
13340 (
13341 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13342 SelectionGoal::None,
13343 )
13344 });
13345 })
13346 }
13347
13348 pub fn select_to_start_of_next_excerpt(
13349 &mut self,
13350 _: &SelectToStartOfNextExcerpt,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13355 cx.propagate();
13356 return;
13357 }
13358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13359 self.change_selections(Default::default(), window, cx, |s| {
13360 s.move_heads_with(|map, head, _| {
13361 (
13362 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13363 SelectionGoal::None,
13364 )
13365 });
13366 })
13367 }
13368
13369 pub fn select_to_end_of_excerpt(
13370 &mut self,
13371 _: &SelectToEndOfExcerpt,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13376 cx.propagate();
13377 return;
13378 }
13379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13380 self.change_selections(Default::default(), window, cx, |s| {
13381 s.move_heads_with(|map, head, _| {
13382 (
13383 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13384 SelectionGoal::None,
13385 )
13386 });
13387 })
13388 }
13389
13390 pub fn select_to_end_of_previous_excerpt(
13391 &mut self,
13392 _: &SelectToEndOfPreviousExcerpt,
13393 window: &mut Window,
13394 cx: &mut Context<Self>,
13395 ) {
13396 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13397 cx.propagate();
13398 return;
13399 }
13400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13401 self.change_selections(Default::default(), window, cx, |s| {
13402 s.move_heads_with(|map, head, _| {
13403 (
13404 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13405 SelectionGoal::None,
13406 )
13407 });
13408 })
13409 }
13410
13411 pub fn move_to_beginning(
13412 &mut self,
13413 _: &MoveToBeginning,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13418 cx.propagate();
13419 return;
13420 }
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.select_ranges(vec![0..0]);
13424 });
13425 }
13426
13427 pub fn select_to_beginning(
13428 &mut self,
13429 _: &SelectToBeginning,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 let mut selection = self.selections.last::<Point>(cx);
13434 selection.set_head(Point::zero(), SelectionGoal::None);
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.select(vec![selection]);
13438 });
13439 }
13440
13441 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13442 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13443 cx.propagate();
13444 return;
13445 }
13446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13447 let cursor = self.buffer.read(cx).read(cx).len();
13448 self.change_selections(Default::default(), window, cx, |s| {
13449 s.select_ranges(vec![cursor..cursor])
13450 });
13451 }
13452
13453 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13454 self.nav_history = nav_history;
13455 }
13456
13457 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13458 self.nav_history.as_ref()
13459 }
13460
13461 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13462 self.push_to_nav_history(
13463 self.selections.newest_anchor().head(),
13464 None,
13465 false,
13466 true,
13467 cx,
13468 );
13469 }
13470
13471 fn push_to_nav_history(
13472 &mut self,
13473 cursor_anchor: Anchor,
13474 new_position: Option<Point>,
13475 is_deactivate: bool,
13476 always: bool,
13477 cx: &mut Context<Self>,
13478 ) {
13479 if let Some(nav_history) = self.nav_history.as_mut() {
13480 let buffer = self.buffer.read(cx).read(cx);
13481 let cursor_position = cursor_anchor.to_point(&buffer);
13482 let scroll_state = self.scroll_manager.anchor();
13483 let scroll_top_row = scroll_state.top_row(&buffer);
13484 drop(buffer);
13485
13486 if let Some(new_position) = new_position {
13487 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13488 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13489 return;
13490 }
13491 }
13492
13493 nav_history.push(
13494 Some(NavigationData {
13495 cursor_anchor,
13496 cursor_position,
13497 scroll_anchor: scroll_state,
13498 scroll_top_row,
13499 }),
13500 cx,
13501 );
13502 cx.emit(EditorEvent::PushedToNavHistory {
13503 anchor: cursor_anchor,
13504 is_deactivate,
13505 })
13506 }
13507 }
13508
13509 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13511 let buffer = self.buffer.read(cx).snapshot(cx);
13512 let mut selection = self.selections.first::<usize>(cx);
13513 selection.set_head(buffer.len(), SelectionGoal::None);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.select(vec![selection]);
13516 });
13517 }
13518
13519 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13521 let end = self.buffer.read(cx).read(cx).len();
13522 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13523 s.select_ranges(vec![0..end]);
13524 });
13525 }
13526
13527 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13530 let mut selections = self.selections.all::<Point>(cx);
13531 let max_point = display_map.buffer_snapshot.max_point();
13532 for selection in &mut selections {
13533 let rows = selection.spanned_rows(true, &display_map);
13534 selection.start = Point::new(rows.start.0, 0);
13535 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13536 selection.reversed = false;
13537 }
13538 self.change_selections(Default::default(), window, cx, |s| {
13539 s.select(selections);
13540 });
13541 }
13542
13543 pub fn split_selection_into_lines(
13544 &mut self,
13545 _: &SplitSelectionIntoLines,
13546 window: &mut Window,
13547 cx: &mut Context<Self>,
13548 ) {
13549 let selections = self
13550 .selections
13551 .all::<Point>(cx)
13552 .into_iter()
13553 .map(|selection| selection.start..selection.end)
13554 .collect::<Vec<_>>();
13555 self.unfold_ranges(&selections, true, true, cx);
13556
13557 let mut new_selection_ranges = Vec::new();
13558 {
13559 let buffer = self.buffer.read(cx).read(cx);
13560 for selection in selections {
13561 for row in selection.start.row..selection.end.row {
13562 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13563 new_selection_ranges.push(cursor..cursor);
13564 }
13565
13566 let is_multiline_selection = selection.start.row != selection.end.row;
13567 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13568 // so this action feels more ergonomic when paired with other selection operations
13569 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13570 if !should_skip_last {
13571 new_selection_ranges.push(selection.end..selection.end);
13572 }
13573 }
13574 }
13575 self.change_selections(Default::default(), window, cx, |s| {
13576 s.select_ranges(new_selection_ranges);
13577 });
13578 }
13579
13580 pub fn add_selection_above(
13581 &mut self,
13582 _: &AddSelectionAbove,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 self.add_selection(true, window, cx);
13587 }
13588
13589 pub fn add_selection_below(
13590 &mut self,
13591 _: &AddSelectionBelow,
13592 window: &mut Window,
13593 cx: &mut Context<Self>,
13594 ) {
13595 self.add_selection(false, window, cx);
13596 }
13597
13598 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13600
13601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13602 let all_selections = self.selections.all::<Point>(cx);
13603 let text_layout_details = self.text_layout_details(window);
13604
13605 let (mut columnar_selections, new_selections_to_columnarize) = {
13606 if let Some(state) = self.add_selections_state.as_ref() {
13607 let columnar_selection_ids: HashSet<_> = state
13608 .groups
13609 .iter()
13610 .flat_map(|group| group.stack.iter())
13611 .copied()
13612 .collect();
13613
13614 all_selections
13615 .into_iter()
13616 .partition(|s| columnar_selection_ids.contains(&s.id))
13617 } else {
13618 (Vec::new(), all_selections)
13619 }
13620 };
13621
13622 let mut state = self
13623 .add_selections_state
13624 .take()
13625 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13626
13627 for selection in new_selections_to_columnarize {
13628 let range = selection.display_range(&display_map).sorted();
13629 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13630 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13631 let positions = start_x.min(end_x)..start_x.max(end_x);
13632 let mut stack = Vec::new();
13633 for row in range.start.row().0..=range.end.row().0 {
13634 if let Some(selection) = self.selections.build_columnar_selection(
13635 &display_map,
13636 DisplayRow(row),
13637 &positions,
13638 selection.reversed,
13639 &text_layout_details,
13640 ) {
13641 stack.push(selection.id);
13642 columnar_selections.push(selection);
13643 }
13644 }
13645 if !stack.is_empty() {
13646 if above {
13647 stack.reverse();
13648 }
13649 state.groups.push(AddSelectionsGroup { above, stack });
13650 }
13651 }
13652
13653 let mut final_selections = Vec::new();
13654 let end_row = if above {
13655 DisplayRow(0)
13656 } else {
13657 display_map.max_point().row()
13658 };
13659
13660 let mut last_added_item_per_group = HashMap::default();
13661 for group in state.groups.iter_mut() {
13662 if let Some(last_id) = group.stack.last() {
13663 last_added_item_per_group.insert(*last_id, group);
13664 }
13665 }
13666
13667 for selection in columnar_selections {
13668 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13669 if above == group.above {
13670 let range = selection.display_range(&display_map).sorted();
13671 debug_assert_eq!(range.start.row(), range.end.row());
13672 let mut row = range.start.row();
13673 let positions =
13674 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13675 px(start)..px(end)
13676 } else {
13677 let start_x =
13678 display_map.x_for_display_point(range.start, &text_layout_details);
13679 let end_x =
13680 display_map.x_for_display_point(range.end, &text_layout_details);
13681 start_x.min(end_x)..start_x.max(end_x)
13682 };
13683
13684 let mut maybe_new_selection = None;
13685 while row != end_row {
13686 if above {
13687 row.0 -= 1;
13688 } else {
13689 row.0 += 1;
13690 }
13691 if let Some(new_selection) = self.selections.build_columnar_selection(
13692 &display_map,
13693 row,
13694 &positions,
13695 selection.reversed,
13696 &text_layout_details,
13697 ) {
13698 maybe_new_selection = Some(new_selection);
13699 break;
13700 }
13701 }
13702
13703 if let Some(new_selection) = maybe_new_selection {
13704 group.stack.push(new_selection.id);
13705 if above {
13706 final_selections.push(new_selection);
13707 final_selections.push(selection);
13708 } else {
13709 final_selections.push(selection);
13710 final_selections.push(new_selection);
13711 }
13712 } else {
13713 final_selections.push(selection);
13714 }
13715 } else {
13716 group.stack.pop();
13717 }
13718 } else {
13719 final_selections.push(selection);
13720 }
13721 }
13722
13723 self.change_selections(Default::default(), window, cx, |s| {
13724 s.select(final_selections);
13725 });
13726
13727 let final_selection_ids: HashSet<_> = self
13728 .selections
13729 .all::<Point>(cx)
13730 .iter()
13731 .map(|s| s.id)
13732 .collect();
13733 state.groups.retain_mut(|group| {
13734 // selections might get merged above so we remove invalid items from stacks
13735 group.stack.retain(|id| final_selection_ids.contains(id));
13736
13737 // single selection in stack can be treated as initial state
13738 group.stack.len() > 1
13739 });
13740
13741 if !state.groups.is_empty() {
13742 self.add_selections_state = Some(state);
13743 }
13744 }
13745
13746 fn select_match_ranges(
13747 &mut self,
13748 range: Range<usize>,
13749 reversed: bool,
13750 replace_newest: bool,
13751 auto_scroll: Option<Autoscroll>,
13752 window: &mut Window,
13753 cx: &mut Context<Editor>,
13754 ) {
13755 self.unfold_ranges(
13756 std::slice::from_ref(&range),
13757 false,
13758 auto_scroll.is_some(),
13759 cx,
13760 );
13761 let effects = if let Some(scroll) = auto_scroll {
13762 SelectionEffects::scroll(scroll)
13763 } else {
13764 SelectionEffects::no_scroll()
13765 };
13766 self.change_selections(effects, window, cx, |s| {
13767 if replace_newest {
13768 s.delete(s.newest_anchor().id);
13769 }
13770 if reversed {
13771 s.insert_range(range.end..range.start);
13772 } else {
13773 s.insert_range(range);
13774 }
13775 });
13776 }
13777
13778 pub fn select_next_match_internal(
13779 &mut self,
13780 display_map: &DisplaySnapshot,
13781 replace_newest: bool,
13782 autoscroll: Option<Autoscroll>,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) -> Result<()> {
13786 let buffer = &display_map.buffer_snapshot;
13787 let mut selections = self.selections.all::<usize>(cx);
13788 if let Some(mut select_next_state) = self.select_next_state.take() {
13789 let query = &select_next_state.query;
13790 if !select_next_state.done {
13791 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13792 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13793 let mut next_selected_range = None;
13794
13795 let bytes_after_last_selection =
13796 buffer.bytes_in_range(last_selection.end..buffer.len());
13797 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13798 let query_matches = query
13799 .stream_find_iter(bytes_after_last_selection)
13800 .map(|result| (last_selection.end, result))
13801 .chain(
13802 query
13803 .stream_find_iter(bytes_before_first_selection)
13804 .map(|result| (0, result)),
13805 );
13806
13807 for (start_offset, query_match) in query_matches {
13808 let query_match = query_match.unwrap(); // can only fail due to I/O
13809 let offset_range =
13810 start_offset + query_match.start()..start_offset + query_match.end();
13811
13812 if !select_next_state.wordwise
13813 || (!buffer.is_inside_word(offset_range.start, false)
13814 && !buffer.is_inside_word(offset_range.end, false))
13815 {
13816 // TODO: This is n^2, because we might check all the selections
13817 if !selections
13818 .iter()
13819 .any(|selection| selection.range().overlaps(&offset_range))
13820 {
13821 next_selected_range = Some(offset_range);
13822 break;
13823 }
13824 }
13825 }
13826
13827 if let Some(next_selected_range) = next_selected_range {
13828 self.select_match_ranges(
13829 next_selected_range,
13830 last_selection.reversed,
13831 replace_newest,
13832 autoscroll,
13833 window,
13834 cx,
13835 );
13836 } else {
13837 select_next_state.done = true;
13838 }
13839 }
13840
13841 self.select_next_state = Some(select_next_state);
13842 } else {
13843 let mut only_carets = true;
13844 let mut same_text_selected = true;
13845 let mut selected_text = None;
13846
13847 let mut selections_iter = selections.iter().peekable();
13848 while let Some(selection) = selections_iter.next() {
13849 if selection.start != selection.end {
13850 only_carets = false;
13851 }
13852
13853 if same_text_selected {
13854 if selected_text.is_none() {
13855 selected_text =
13856 Some(buffer.text_for_range(selection.range()).collect::<String>());
13857 }
13858
13859 if let Some(next_selection) = selections_iter.peek() {
13860 if next_selection.range().len() == selection.range().len() {
13861 let next_selected_text = buffer
13862 .text_for_range(next_selection.range())
13863 .collect::<String>();
13864 if Some(next_selected_text) != selected_text {
13865 same_text_selected = false;
13866 selected_text = None;
13867 }
13868 } else {
13869 same_text_selected = false;
13870 selected_text = None;
13871 }
13872 }
13873 }
13874 }
13875
13876 if only_carets {
13877 for selection in &mut selections {
13878 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13879 selection.start = word_range.start;
13880 selection.end = word_range.end;
13881 selection.goal = SelectionGoal::None;
13882 selection.reversed = false;
13883 self.select_match_ranges(
13884 selection.start..selection.end,
13885 selection.reversed,
13886 replace_newest,
13887 autoscroll,
13888 window,
13889 cx,
13890 );
13891 }
13892
13893 if selections.len() == 1 {
13894 let selection = selections
13895 .last()
13896 .expect("ensured that there's only one selection");
13897 let query = buffer
13898 .text_for_range(selection.start..selection.end)
13899 .collect::<String>();
13900 let is_empty = query.is_empty();
13901 let select_state = SelectNextState {
13902 query: AhoCorasick::new(&[query])?,
13903 wordwise: true,
13904 done: is_empty,
13905 };
13906 self.select_next_state = Some(select_state);
13907 } else {
13908 self.select_next_state = None;
13909 }
13910 } else if let Some(selected_text) = selected_text {
13911 self.select_next_state = Some(SelectNextState {
13912 query: AhoCorasick::new(&[selected_text])?,
13913 wordwise: false,
13914 done: false,
13915 });
13916 self.select_next_match_internal(
13917 display_map,
13918 replace_newest,
13919 autoscroll,
13920 window,
13921 cx,
13922 )?;
13923 }
13924 }
13925 Ok(())
13926 }
13927
13928 pub fn select_all_matches(
13929 &mut self,
13930 _action: &SelectAllMatches,
13931 window: &mut Window,
13932 cx: &mut Context<Self>,
13933 ) -> Result<()> {
13934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13935
13936 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13937
13938 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13939 let Some(select_next_state) = self.select_next_state.as_mut() else {
13940 return Ok(());
13941 };
13942 if select_next_state.done {
13943 return Ok(());
13944 }
13945
13946 let mut new_selections = Vec::new();
13947
13948 let reversed = self.selections.oldest::<usize>(cx).reversed;
13949 let buffer = &display_map.buffer_snapshot;
13950 let query_matches = select_next_state
13951 .query
13952 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13953
13954 for query_match in query_matches.into_iter() {
13955 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13956 let offset_range = if reversed {
13957 query_match.end()..query_match.start()
13958 } else {
13959 query_match.start()..query_match.end()
13960 };
13961
13962 if !select_next_state.wordwise
13963 || (!buffer.is_inside_word(offset_range.start, false)
13964 && !buffer.is_inside_word(offset_range.end, false))
13965 {
13966 new_selections.push(offset_range.start..offset_range.end);
13967 }
13968 }
13969
13970 select_next_state.done = true;
13971
13972 if new_selections.is_empty() {
13973 log::error!("bug: new_selections is empty in select_all_matches");
13974 return Ok(());
13975 }
13976
13977 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13978 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13979 selections.select_ranges(new_selections)
13980 });
13981
13982 Ok(())
13983 }
13984
13985 pub fn select_next(
13986 &mut self,
13987 action: &SelectNext,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) -> Result<()> {
13991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13993 self.select_next_match_internal(
13994 &display_map,
13995 action.replace_newest,
13996 Some(Autoscroll::newest()),
13997 window,
13998 cx,
13999 )?;
14000 Ok(())
14001 }
14002
14003 pub fn select_previous(
14004 &mut self,
14005 action: &SelectPrevious,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) -> Result<()> {
14009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14010 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14011 let buffer = &display_map.buffer_snapshot;
14012 let mut selections = self.selections.all::<usize>(cx);
14013 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14014 let query = &select_prev_state.query;
14015 if !select_prev_state.done {
14016 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14017 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14018 let mut next_selected_range = None;
14019 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14020 let bytes_before_last_selection =
14021 buffer.reversed_bytes_in_range(0..last_selection.start);
14022 let bytes_after_first_selection =
14023 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14024 let query_matches = query
14025 .stream_find_iter(bytes_before_last_selection)
14026 .map(|result| (last_selection.start, result))
14027 .chain(
14028 query
14029 .stream_find_iter(bytes_after_first_selection)
14030 .map(|result| (buffer.len(), result)),
14031 );
14032 for (end_offset, query_match) in query_matches {
14033 let query_match = query_match.unwrap(); // can only fail due to I/O
14034 let offset_range =
14035 end_offset - query_match.end()..end_offset - query_match.start();
14036
14037 if !select_prev_state.wordwise
14038 || (!buffer.is_inside_word(offset_range.start, false)
14039 && !buffer.is_inside_word(offset_range.end, false))
14040 {
14041 next_selected_range = Some(offset_range);
14042 break;
14043 }
14044 }
14045
14046 if let Some(next_selected_range) = next_selected_range {
14047 self.select_match_ranges(
14048 next_selected_range,
14049 last_selection.reversed,
14050 action.replace_newest,
14051 Some(Autoscroll::newest()),
14052 window,
14053 cx,
14054 );
14055 } else {
14056 select_prev_state.done = true;
14057 }
14058 }
14059
14060 self.select_prev_state = Some(select_prev_state);
14061 } else {
14062 let mut only_carets = true;
14063 let mut same_text_selected = true;
14064 let mut selected_text = None;
14065
14066 let mut selections_iter = selections.iter().peekable();
14067 while let Some(selection) = selections_iter.next() {
14068 if selection.start != selection.end {
14069 only_carets = false;
14070 }
14071
14072 if same_text_selected {
14073 if selected_text.is_none() {
14074 selected_text =
14075 Some(buffer.text_for_range(selection.range()).collect::<String>());
14076 }
14077
14078 if let Some(next_selection) = selections_iter.peek() {
14079 if next_selection.range().len() == selection.range().len() {
14080 let next_selected_text = buffer
14081 .text_for_range(next_selection.range())
14082 .collect::<String>();
14083 if Some(next_selected_text) != selected_text {
14084 same_text_selected = false;
14085 selected_text = None;
14086 }
14087 } else {
14088 same_text_selected = false;
14089 selected_text = None;
14090 }
14091 }
14092 }
14093 }
14094
14095 if only_carets {
14096 for selection in &mut selections {
14097 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14098 selection.start = word_range.start;
14099 selection.end = word_range.end;
14100 selection.goal = SelectionGoal::None;
14101 selection.reversed = false;
14102 self.select_match_ranges(
14103 selection.start..selection.end,
14104 selection.reversed,
14105 action.replace_newest,
14106 Some(Autoscroll::newest()),
14107 window,
14108 cx,
14109 );
14110 }
14111 if selections.len() == 1 {
14112 let selection = selections
14113 .last()
14114 .expect("ensured that there's only one selection");
14115 let query = buffer
14116 .text_for_range(selection.start..selection.end)
14117 .collect::<String>();
14118 let is_empty = query.is_empty();
14119 let select_state = SelectNextState {
14120 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14121 wordwise: true,
14122 done: is_empty,
14123 };
14124 self.select_prev_state = Some(select_state);
14125 } else {
14126 self.select_prev_state = None;
14127 }
14128 } else if let Some(selected_text) = selected_text {
14129 self.select_prev_state = Some(SelectNextState {
14130 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14131 wordwise: false,
14132 done: false,
14133 });
14134 self.select_previous(action, window, cx)?;
14135 }
14136 }
14137 Ok(())
14138 }
14139
14140 pub fn find_next_match(
14141 &mut self,
14142 _: &FindNextMatch,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) -> Result<()> {
14146 let selections = self.selections.disjoint_anchors();
14147 match selections.first() {
14148 Some(first) if selections.len() >= 2 => {
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.select_ranges([first.range()]);
14151 });
14152 }
14153 _ => self.select_next(
14154 &SelectNext {
14155 replace_newest: true,
14156 },
14157 window,
14158 cx,
14159 )?,
14160 }
14161 Ok(())
14162 }
14163
14164 pub fn find_previous_match(
14165 &mut self,
14166 _: &FindPreviousMatch,
14167 window: &mut Window,
14168 cx: &mut Context<Self>,
14169 ) -> Result<()> {
14170 let selections = self.selections.disjoint_anchors();
14171 match selections.last() {
14172 Some(last) if selections.len() >= 2 => {
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select_ranges([last.range()]);
14175 });
14176 }
14177 _ => self.select_previous(
14178 &SelectPrevious {
14179 replace_newest: true,
14180 },
14181 window,
14182 cx,
14183 )?,
14184 }
14185 Ok(())
14186 }
14187
14188 pub fn toggle_comments(
14189 &mut self,
14190 action: &ToggleComments,
14191 window: &mut Window,
14192 cx: &mut Context<Self>,
14193 ) {
14194 if self.read_only(cx) {
14195 return;
14196 }
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14198 let text_layout_details = &self.text_layout_details(window);
14199 self.transact(window, cx, |this, window, cx| {
14200 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14201 let mut edits = Vec::new();
14202 let mut selection_edit_ranges = Vec::new();
14203 let mut last_toggled_row = None;
14204 let snapshot = this.buffer.read(cx).read(cx);
14205 let empty_str: Arc<str> = Arc::default();
14206 let mut suffixes_inserted = Vec::new();
14207 let ignore_indent = action.ignore_indent;
14208
14209 fn comment_prefix_range(
14210 snapshot: &MultiBufferSnapshot,
14211 row: MultiBufferRow,
14212 comment_prefix: &str,
14213 comment_prefix_whitespace: &str,
14214 ignore_indent: bool,
14215 ) -> Range<Point> {
14216 let indent_size = if ignore_indent {
14217 0
14218 } else {
14219 snapshot.indent_size_for_line(row).len
14220 };
14221
14222 let start = Point::new(row.0, indent_size);
14223
14224 let mut line_bytes = snapshot
14225 .bytes_in_range(start..snapshot.max_point())
14226 .flatten()
14227 .copied();
14228
14229 // If this line currently begins with the line comment prefix, then record
14230 // the range containing the prefix.
14231 if line_bytes
14232 .by_ref()
14233 .take(comment_prefix.len())
14234 .eq(comment_prefix.bytes())
14235 {
14236 // Include any whitespace that matches the comment prefix.
14237 let matching_whitespace_len = line_bytes
14238 .zip(comment_prefix_whitespace.bytes())
14239 .take_while(|(a, b)| a == b)
14240 .count() as u32;
14241 let end = Point::new(
14242 start.row,
14243 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14244 );
14245 start..end
14246 } else {
14247 start..start
14248 }
14249 }
14250
14251 fn comment_suffix_range(
14252 snapshot: &MultiBufferSnapshot,
14253 row: MultiBufferRow,
14254 comment_suffix: &str,
14255 comment_suffix_has_leading_space: bool,
14256 ) -> Range<Point> {
14257 let end = Point::new(row.0, snapshot.line_len(row));
14258 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14259
14260 let mut line_end_bytes = snapshot
14261 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14262 .flatten()
14263 .copied();
14264
14265 let leading_space_len = if suffix_start_column > 0
14266 && line_end_bytes.next() == Some(b' ')
14267 && comment_suffix_has_leading_space
14268 {
14269 1
14270 } else {
14271 0
14272 };
14273
14274 // If this line currently begins with the line comment prefix, then record
14275 // the range containing the prefix.
14276 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14277 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14278 start..end
14279 } else {
14280 end..end
14281 }
14282 }
14283
14284 // TODO: Handle selections that cross excerpts
14285 for selection in &mut selections {
14286 let start_column = snapshot
14287 .indent_size_for_line(MultiBufferRow(selection.start.row))
14288 .len;
14289 let language = if let Some(language) =
14290 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14291 {
14292 language
14293 } else {
14294 continue;
14295 };
14296
14297 selection_edit_ranges.clear();
14298
14299 // If multiple selections contain a given row, avoid processing that
14300 // row more than once.
14301 let mut start_row = MultiBufferRow(selection.start.row);
14302 if last_toggled_row == Some(start_row) {
14303 start_row = start_row.next_row();
14304 }
14305 let end_row =
14306 if selection.end.row > selection.start.row && selection.end.column == 0 {
14307 MultiBufferRow(selection.end.row - 1)
14308 } else {
14309 MultiBufferRow(selection.end.row)
14310 };
14311 last_toggled_row = Some(end_row);
14312
14313 if start_row > end_row {
14314 continue;
14315 }
14316
14317 // If the language has line comments, toggle those.
14318 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14319
14320 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14321 if ignore_indent {
14322 full_comment_prefixes = full_comment_prefixes
14323 .into_iter()
14324 .map(|s| Arc::from(s.trim_end()))
14325 .collect();
14326 }
14327
14328 if !full_comment_prefixes.is_empty() {
14329 let first_prefix = full_comment_prefixes
14330 .first()
14331 .expect("prefixes is non-empty");
14332 let prefix_trimmed_lengths = full_comment_prefixes
14333 .iter()
14334 .map(|p| p.trim_end_matches(' ').len())
14335 .collect::<SmallVec<[usize; 4]>>();
14336
14337 let mut all_selection_lines_are_comments = true;
14338
14339 for row in start_row.0..=end_row.0 {
14340 let row = MultiBufferRow(row);
14341 if start_row < end_row && snapshot.is_line_blank(row) {
14342 continue;
14343 }
14344
14345 let prefix_range = full_comment_prefixes
14346 .iter()
14347 .zip(prefix_trimmed_lengths.iter().copied())
14348 .map(|(prefix, trimmed_prefix_len)| {
14349 comment_prefix_range(
14350 snapshot.deref(),
14351 row,
14352 &prefix[..trimmed_prefix_len],
14353 &prefix[trimmed_prefix_len..],
14354 ignore_indent,
14355 )
14356 })
14357 .max_by_key(|range| range.end.column - range.start.column)
14358 .expect("prefixes is non-empty");
14359
14360 if prefix_range.is_empty() {
14361 all_selection_lines_are_comments = false;
14362 }
14363
14364 selection_edit_ranges.push(prefix_range);
14365 }
14366
14367 if all_selection_lines_are_comments {
14368 edits.extend(
14369 selection_edit_ranges
14370 .iter()
14371 .cloned()
14372 .map(|range| (range, empty_str.clone())),
14373 );
14374 } else {
14375 let min_column = selection_edit_ranges
14376 .iter()
14377 .map(|range| range.start.column)
14378 .min()
14379 .unwrap_or(0);
14380 edits.extend(selection_edit_ranges.iter().map(|range| {
14381 let position = Point::new(range.start.row, min_column);
14382 (position..position, first_prefix.clone())
14383 }));
14384 }
14385 } else if let Some(BlockCommentConfig {
14386 start: full_comment_prefix,
14387 end: comment_suffix,
14388 ..
14389 }) = language.block_comment()
14390 {
14391 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14392 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14393 let prefix_range = comment_prefix_range(
14394 snapshot.deref(),
14395 start_row,
14396 comment_prefix,
14397 comment_prefix_whitespace,
14398 ignore_indent,
14399 );
14400 let suffix_range = comment_suffix_range(
14401 snapshot.deref(),
14402 end_row,
14403 comment_suffix.trim_start_matches(' '),
14404 comment_suffix.starts_with(' '),
14405 );
14406
14407 if prefix_range.is_empty() || suffix_range.is_empty() {
14408 edits.push((
14409 prefix_range.start..prefix_range.start,
14410 full_comment_prefix.clone(),
14411 ));
14412 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14413 suffixes_inserted.push((end_row, comment_suffix.len()));
14414 } else {
14415 edits.push((prefix_range, empty_str.clone()));
14416 edits.push((suffix_range, empty_str.clone()));
14417 }
14418 } else {
14419 continue;
14420 }
14421 }
14422
14423 drop(snapshot);
14424 this.buffer.update(cx, |buffer, cx| {
14425 buffer.edit(edits, None, cx);
14426 });
14427
14428 // Adjust selections so that they end before any comment suffixes that
14429 // were inserted.
14430 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14431 let mut selections = this.selections.all::<Point>(cx);
14432 let snapshot = this.buffer.read(cx).read(cx);
14433 for selection in &mut selections {
14434 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14435 match row.cmp(&MultiBufferRow(selection.end.row)) {
14436 Ordering::Less => {
14437 suffixes_inserted.next();
14438 continue;
14439 }
14440 Ordering::Greater => break,
14441 Ordering::Equal => {
14442 if selection.end.column == snapshot.line_len(row) {
14443 if selection.is_empty() {
14444 selection.start.column -= suffix_len as u32;
14445 }
14446 selection.end.column -= suffix_len as u32;
14447 }
14448 break;
14449 }
14450 }
14451 }
14452 }
14453
14454 drop(snapshot);
14455 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14456
14457 let selections = this.selections.all::<Point>(cx);
14458 let selections_on_single_row = selections.windows(2).all(|selections| {
14459 selections[0].start.row == selections[1].start.row
14460 && selections[0].end.row == selections[1].end.row
14461 && selections[0].start.row == selections[0].end.row
14462 });
14463 let selections_selecting = selections
14464 .iter()
14465 .any(|selection| selection.start != selection.end);
14466 let advance_downwards = action.advance_downwards
14467 && selections_on_single_row
14468 && !selections_selecting
14469 && !matches!(this.mode, EditorMode::SingleLine { .. });
14470
14471 if advance_downwards {
14472 let snapshot = this.buffer.read(cx).snapshot(cx);
14473
14474 this.change_selections(Default::default(), window, cx, |s| {
14475 s.move_cursors_with(|display_snapshot, display_point, _| {
14476 let mut point = display_point.to_point(display_snapshot);
14477 point.row += 1;
14478 point = snapshot.clip_point(point, Bias::Left);
14479 let display_point = point.to_display_point(display_snapshot);
14480 let goal = SelectionGoal::HorizontalPosition(
14481 display_snapshot
14482 .x_for_display_point(display_point, text_layout_details)
14483 .into(),
14484 );
14485 (display_point, goal)
14486 })
14487 });
14488 }
14489 });
14490 }
14491
14492 pub fn select_enclosing_symbol(
14493 &mut self,
14494 _: &SelectEnclosingSymbol,
14495 window: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14499
14500 let buffer = self.buffer.read(cx).snapshot(cx);
14501 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14502
14503 fn update_selection(
14504 selection: &Selection<usize>,
14505 buffer_snap: &MultiBufferSnapshot,
14506 ) -> Option<Selection<usize>> {
14507 let cursor = selection.head();
14508 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14509 for symbol in symbols.iter().rev() {
14510 let start = symbol.range.start.to_offset(buffer_snap);
14511 let end = symbol.range.end.to_offset(buffer_snap);
14512 let new_range = start..end;
14513 if start < selection.start || end > selection.end {
14514 return Some(Selection {
14515 id: selection.id,
14516 start: new_range.start,
14517 end: new_range.end,
14518 goal: SelectionGoal::None,
14519 reversed: selection.reversed,
14520 });
14521 }
14522 }
14523 None
14524 }
14525
14526 let mut selected_larger_symbol = false;
14527 let new_selections = old_selections
14528 .iter()
14529 .map(|selection| match update_selection(selection, &buffer) {
14530 Some(new_selection) => {
14531 if new_selection.range() != selection.range() {
14532 selected_larger_symbol = true;
14533 }
14534 new_selection
14535 }
14536 None => selection.clone(),
14537 })
14538 .collect::<Vec<_>>();
14539
14540 if selected_larger_symbol {
14541 self.change_selections(Default::default(), window, cx, |s| {
14542 s.select(new_selections);
14543 });
14544 }
14545 }
14546
14547 pub fn select_larger_syntax_node(
14548 &mut self,
14549 _: &SelectLargerSyntaxNode,
14550 window: &mut Window,
14551 cx: &mut Context<Self>,
14552 ) {
14553 let Some(visible_row_count) = self.visible_row_count() else {
14554 return;
14555 };
14556 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14557 if old_selections.is_empty() {
14558 return;
14559 }
14560
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562
14563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14564 let buffer = self.buffer.read(cx).snapshot(cx);
14565
14566 let mut selected_larger_node = false;
14567 let mut new_selections = old_selections
14568 .iter()
14569 .map(|selection| {
14570 let old_range = selection.start..selection.end;
14571
14572 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14573 // manually select word at selection
14574 if ["string_content", "inline"].contains(&node.kind()) {
14575 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14576 // ignore if word is already selected
14577 if !word_range.is_empty() && old_range != word_range {
14578 let (last_word_range, _) =
14579 buffer.surrounding_word(old_range.end, false);
14580 // only select word if start and end point belongs to same word
14581 if word_range == last_word_range {
14582 selected_larger_node = true;
14583 return Selection {
14584 id: selection.id,
14585 start: word_range.start,
14586 end: word_range.end,
14587 goal: SelectionGoal::None,
14588 reversed: selection.reversed,
14589 };
14590 }
14591 }
14592 }
14593 }
14594
14595 let mut new_range = old_range.clone();
14596 while let Some((_node, containing_range)) =
14597 buffer.syntax_ancestor(new_range.clone())
14598 {
14599 new_range = match containing_range {
14600 MultiOrSingleBufferOffsetRange::Single(_) => break,
14601 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14602 };
14603 if !display_map.intersects_fold(new_range.start)
14604 && !display_map.intersects_fold(new_range.end)
14605 {
14606 break;
14607 }
14608 }
14609
14610 selected_larger_node |= new_range != old_range;
14611 Selection {
14612 id: selection.id,
14613 start: new_range.start,
14614 end: new_range.end,
14615 goal: SelectionGoal::None,
14616 reversed: selection.reversed,
14617 }
14618 })
14619 .collect::<Vec<_>>();
14620
14621 if !selected_larger_node {
14622 return; // don't put this call in the history
14623 }
14624
14625 // scroll based on transformation done to the last selection created by the user
14626 let (last_old, last_new) = old_selections
14627 .last()
14628 .zip(new_selections.last().cloned())
14629 .expect("old_selections isn't empty");
14630
14631 // revert selection
14632 let is_selection_reversed = {
14633 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14634 new_selections.last_mut().expect("checked above").reversed =
14635 should_newest_selection_be_reversed;
14636 should_newest_selection_be_reversed
14637 };
14638
14639 if selected_larger_node {
14640 self.select_syntax_node_history.disable_clearing = true;
14641 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14642 s.select(new_selections.clone());
14643 });
14644 self.select_syntax_node_history.disable_clearing = false;
14645 }
14646
14647 let start_row = last_new.start.to_display_point(&display_map).row().0;
14648 let end_row = last_new.end.to_display_point(&display_map).row().0;
14649 let selection_height = end_row - start_row + 1;
14650 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14651
14652 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14653 let scroll_behavior = if fits_on_the_screen {
14654 self.request_autoscroll(Autoscroll::fit(), cx);
14655 SelectSyntaxNodeScrollBehavior::FitSelection
14656 } else if is_selection_reversed {
14657 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14658 SelectSyntaxNodeScrollBehavior::CursorTop
14659 } else {
14660 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14661 SelectSyntaxNodeScrollBehavior::CursorBottom
14662 };
14663
14664 self.select_syntax_node_history.push((
14665 old_selections,
14666 scroll_behavior,
14667 is_selection_reversed,
14668 ));
14669 }
14670
14671 pub fn select_smaller_syntax_node(
14672 &mut self,
14673 _: &SelectSmallerSyntaxNode,
14674 window: &mut Window,
14675 cx: &mut Context<Self>,
14676 ) {
14677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14678
14679 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14680 self.select_syntax_node_history.pop()
14681 {
14682 if let Some(selection) = selections.last_mut() {
14683 selection.reversed = is_selection_reversed;
14684 }
14685
14686 self.select_syntax_node_history.disable_clearing = true;
14687 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14688 s.select(selections.to_vec());
14689 });
14690 self.select_syntax_node_history.disable_clearing = false;
14691
14692 match scroll_behavior {
14693 SelectSyntaxNodeScrollBehavior::CursorTop => {
14694 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14695 }
14696 SelectSyntaxNodeScrollBehavior::FitSelection => {
14697 self.request_autoscroll(Autoscroll::fit(), cx);
14698 }
14699 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14700 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14701 }
14702 }
14703 }
14704 }
14705
14706 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14707 if !EditorSettings::get_global(cx).gutter.runnables {
14708 self.clear_tasks();
14709 return Task::ready(());
14710 }
14711 let project = self.project.as_ref().map(Entity::downgrade);
14712 let task_sources = self.lsp_task_sources(cx);
14713 let multi_buffer = self.buffer.downgrade();
14714 cx.spawn_in(window, async move |editor, cx| {
14715 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14716 let Some(project) = project.and_then(|p| p.upgrade()) else {
14717 return;
14718 };
14719 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14720 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14721 }) else {
14722 return;
14723 };
14724
14725 let hide_runnables = project
14726 .update(cx, |project, cx| {
14727 // Do not display any test indicators in non-dev server remote projects.
14728 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14729 })
14730 .unwrap_or(true);
14731 if hide_runnables {
14732 return;
14733 }
14734 let new_rows =
14735 cx.background_spawn({
14736 let snapshot = display_snapshot.clone();
14737 async move {
14738 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14739 }
14740 })
14741 .await;
14742 let Ok(lsp_tasks) =
14743 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14744 else {
14745 return;
14746 };
14747 let lsp_tasks = lsp_tasks.await;
14748
14749 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14750 lsp_tasks
14751 .into_iter()
14752 .flat_map(|(kind, tasks)| {
14753 tasks.into_iter().filter_map(move |(location, task)| {
14754 Some((kind.clone(), location?, task))
14755 })
14756 })
14757 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14758 let buffer = location.target.buffer;
14759 let buffer_snapshot = buffer.read(cx).snapshot();
14760 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14761 |(excerpt_id, snapshot, _)| {
14762 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14763 display_snapshot
14764 .buffer_snapshot
14765 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14766 } else {
14767 None
14768 }
14769 },
14770 );
14771 if let Some(offset) = offset {
14772 let task_buffer_range =
14773 location.target.range.to_point(&buffer_snapshot);
14774 let context_buffer_range =
14775 task_buffer_range.to_offset(&buffer_snapshot);
14776 let context_range = BufferOffset(context_buffer_range.start)
14777 ..BufferOffset(context_buffer_range.end);
14778
14779 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14780 .or_insert_with(|| RunnableTasks {
14781 templates: Vec::new(),
14782 offset,
14783 column: task_buffer_range.start.column,
14784 extra_variables: HashMap::default(),
14785 context_range,
14786 })
14787 .templates
14788 .push((kind, task.original_task().clone()));
14789 }
14790
14791 acc
14792 })
14793 }) else {
14794 return;
14795 };
14796
14797 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14798 buffer.language_settings(cx).tasks.prefer_lsp
14799 }) else {
14800 return;
14801 };
14802
14803 let rows = Self::runnable_rows(
14804 project,
14805 display_snapshot,
14806 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14807 new_rows,
14808 cx.clone(),
14809 )
14810 .await;
14811 editor
14812 .update(cx, |editor, _| {
14813 editor.clear_tasks();
14814 for (key, mut value) in rows {
14815 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14816 value.templates.extend(lsp_tasks.templates);
14817 }
14818
14819 editor.insert_tasks(key, value);
14820 }
14821 for (key, value) in lsp_tasks_by_rows {
14822 editor.insert_tasks(key, value);
14823 }
14824 })
14825 .ok();
14826 })
14827 }
14828 fn fetch_runnable_ranges(
14829 snapshot: &DisplaySnapshot,
14830 range: Range<Anchor>,
14831 ) -> Vec<language::RunnableRange> {
14832 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14833 }
14834
14835 fn runnable_rows(
14836 project: Entity<Project>,
14837 snapshot: DisplaySnapshot,
14838 prefer_lsp: bool,
14839 runnable_ranges: Vec<RunnableRange>,
14840 cx: AsyncWindowContext,
14841 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14842 cx.spawn(async move |cx| {
14843 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14844 for mut runnable in runnable_ranges {
14845 let Some(tasks) = cx
14846 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14847 .ok()
14848 else {
14849 continue;
14850 };
14851 let mut tasks = tasks.await;
14852
14853 if prefer_lsp {
14854 tasks.retain(|(task_kind, _)| {
14855 !matches!(task_kind, TaskSourceKind::Language { .. })
14856 });
14857 }
14858 if tasks.is_empty() {
14859 continue;
14860 }
14861
14862 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14863 let Some(row) = snapshot
14864 .buffer_snapshot
14865 .buffer_line_for_row(MultiBufferRow(point.row))
14866 .map(|(_, range)| range.start.row)
14867 else {
14868 continue;
14869 };
14870
14871 let context_range =
14872 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14873 runnable_rows.push((
14874 (runnable.buffer_id, row),
14875 RunnableTasks {
14876 templates: tasks,
14877 offset: snapshot
14878 .buffer_snapshot
14879 .anchor_before(runnable.run_range.start),
14880 context_range,
14881 column: point.column,
14882 extra_variables: runnable.extra_captures,
14883 },
14884 ));
14885 }
14886 runnable_rows
14887 })
14888 }
14889
14890 fn templates_with_tags(
14891 project: &Entity<Project>,
14892 runnable: &mut Runnable,
14893 cx: &mut App,
14894 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14895 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14896 let (worktree_id, file) = project
14897 .buffer_for_id(runnable.buffer, cx)
14898 .and_then(|buffer| buffer.read(cx).file())
14899 .map(|file| (file.worktree_id(cx), file.clone()))
14900 .unzip();
14901
14902 (
14903 project.task_store().read(cx).task_inventory().cloned(),
14904 worktree_id,
14905 file,
14906 )
14907 });
14908
14909 let tags = mem::take(&mut runnable.tags);
14910 let language = runnable.language.clone();
14911 cx.spawn(async move |cx| {
14912 let mut templates_with_tags = Vec::new();
14913 if let Some(inventory) = inventory {
14914 for RunnableTag(tag) in tags {
14915 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14916 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14917 }) else {
14918 return templates_with_tags;
14919 };
14920 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14921 move |(_, template)| {
14922 template.tags.iter().any(|source_tag| source_tag == &tag)
14923 },
14924 ));
14925 }
14926 }
14927 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14928
14929 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14930 // Strongest source wins; if we have worktree tag binding, prefer that to
14931 // global and language bindings;
14932 // if we have a global binding, prefer that to language binding.
14933 let first_mismatch = templates_with_tags
14934 .iter()
14935 .position(|(tag_source, _)| tag_source != leading_tag_source);
14936 if let Some(index) = first_mismatch {
14937 templates_with_tags.truncate(index);
14938 }
14939 }
14940
14941 templates_with_tags
14942 })
14943 }
14944
14945 pub fn move_to_enclosing_bracket(
14946 &mut self,
14947 _: &MoveToEnclosingBracket,
14948 window: &mut Window,
14949 cx: &mut Context<Self>,
14950 ) {
14951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14952 self.change_selections(Default::default(), window, cx, |s| {
14953 s.move_offsets_with(|snapshot, selection| {
14954 let Some(enclosing_bracket_ranges) =
14955 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14956 else {
14957 return;
14958 };
14959
14960 let mut best_length = usize::MAX;
14961 let mut best_inside = false;
14962 let mut best_in_bracket_range = false;
14963 let mut best_destination = None;
14964 for (open, close) in enclosing_bracket_ranges {
14965 let close = close.to_inclusive();
14966 let length = close.end() - open.start;
14967 let inside = selection.start >= open.end && selection.end <= *close.start();
14968 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14969 || close.contains(&selection.head());
14970
14971 // If best is next to a bracket and current isn't, skip
14972 if !in_bracket_range && best_in_bracket_range {
14973 continue;
14974 }
14975
14976 // Prefer smaller lengths unless best is inside and current isn't
14977 if length > best_length && (best_inside || !inside) {
14978 continue;
14979 }
14980
14981 best_length = length;
14982 best_inside = inside;
14983 best_in_bracket_range = in_bracket_range;
14984 best_destination = Some(
14985 if close.contains(&selection.start) && close.contains(&selection.end) {
14986 if inside { open.end } else { open.start }
14987 } else if inside {
14988 *close.start()
14989 } else {
14990 *close.end()
14991 },
14992 );
14993 }
14994
14995 if let Some(destination) = best_destination {
14996 selection.collapse_to(destination, SelectionGoal::None);
14997 }
14998 })
14999 });
15000 }
15001
15002 pub fn undo_selection(
15003 &mut self,
15004 _: &UndoSelection,
15005 window: &mut Window,
15006 cx: &mut Context<Self>,
15007 ) {
15008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15009 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15010 self.selection_history.mode = SelectionHistoryMode::Undoing;
15011 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15012 this.end_selection(window, cx);
15013 this.change_selections(
15014 SelectionEffects::scroll(Autoscroll::newest()),
15015 window,
15016 cx,
15017 |s| s.select_anchors(entry.selections.to_vec()),
15018 );
15019 });
15020 self.selection_history.mode = SelectionHistoryMode::Normal;
15021
15022 self.select_next_state = entry.select_next_state;
15023 self.select_prev_state = entry.select_prev_state;
15024 self.add_selections_state = entry.add_selections_state;
15025 }
15026 }
15027
15028 pub fn redo_selection(
15029 &mut self,
15030 _: &RedoSelection,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15035 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15036 self.selection_history.mode = SelectionHistoryMode::Redoing;
15037 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15038 this.end_selection(window, cx);
15039 this.change_selections(
15040 SelectionEffects::scroll(Autoscroll::newest()),
15041 window,
15042 cx,
15043 |s| s.select_anchors(entry.selections.to_vec()),
15044 );
15045 });
15046 self.selection_history.mode = SelectionHistoryMode::Normal;
15047
15048 self.select_next_state = entry.select_next_state;
15049 self.select_prev_state = entry.select_prev_state;
15050 self.add_selections_state = entry.add_selections_state;
15051 }
15052 }
15053
15054 pub fn expand_excerpts(
15055 &mut self,
15056 action: &ExpandExcerpts,
15057 _: &mut Window,
15058 cx: &mut Context<Self>,
15059 ) {
15060 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15061 }
15062
15063 pub fn expand_excerpts_down(
15064 &mut self,
15065 action: &ExpandExcerptsDown,
15066 _: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) {
15069 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15070 }
15071
15072 pub fn expand_excerpts_up(
15073 &mut self,
15074 action: &ExpandExcerptsUp,
15075 _: &mut Window,
15076 cx: &mut Context<Self>,
15077 ) {
15078 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15079 }
15080
15081 pub fn expand_excerpts_for_direction(
15082 &mut self,
15083 lines: u32,
15084 direction: ExpandExcerptDirection,
15085
15086 cx: &mut Context<Self>,
15087 ) {
15088 let selections = self.selections.disjoint_anchors();
15089
15090 let lines = if lines == 0 {
15091 EditorSettings::get_global(cx).expand_excerpt_lines
15092 } else {
15093 lines
15094 };
15095
15096 self.buffer.update(cx, |buffer, cx| {
15097 let snapshot = buffer.snapshot(cx);
15098 let mut excerpt_ids = selections
15099 .iter()
15100 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15101 .collect::<Vec<_>>();
15102 excerpt_ids.sort();
15103 excerpt_ids.dedup();
15104 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15105 })
15106 }
15107
15108 pub fn expand_excerpt(
15109 &mut self,
15110 excerpt: ExcerptId,
15111 direction: ExpandExcerptDirection,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) {
15115 let current_scroll_position = self.scroll_position(cx);
15116 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15117 let mut should_scroll_up = false;
15118
15119 if direction == ExpandExcerptDirection::Down {
15120 let multi_buffer = self.buffer.read(cx);
15121 let snapshot = multi_buffer.snapshot(cx);
15122 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15123 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15124 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15125 let buffer_snapshot = buffer.read(cx).snapshot();
15126 let excerpt_end_row =
15127 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15128 let last_row = buffer_snapshot.max_point().row;
15129 let lines_below = last_row.saturating_sub(excerpt_end_row);
15130 should_scroll_up = lines_below >= lines_to_expand;
15131 }
15132 }
15133 }
15134 }
15135
15136 self.buffer.update(cx, |buffer, cx| {
15137 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15138 });
15139
15140 if should_scroll_up {
15141 let new_scroll_position =
15142 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15143 self.set_scroll_position(new_scroll_position, window, cx);
15144 }
15145 }
15146
15147 pub fn go_to_singleton_buffer_point(
15148 &mut self,
15149 point: Point,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) {
15153 self.go_to_singleton_buffer_range(point..point, window, cx);
15154 }
15155
15156 pub fn go_to_singleton_buffer_range(
15157 &mut self,
15158 range: Range<Point>,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) {
15162 let multibuffer = self.buffer().read(cx);
15163 let Some(buffer) = multibuffer.as_singleton() else {
15164 return;
15165 };
15166 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15167 return;
15168 };
15169 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15170 return;
15171 };
15172 self.change_selections(
15173 SelectionEffects::default().nav_history(true),
15174 window,
15175 cx,
15176 |s| s.select_anchor_ranges([start..end]),
15177 );
15178 }
15179
15180 pub fn go_to_diagnostic(
15181 &mut self,
15182 action: &GoToDiagnostic,
15183 window: &mut Window,
15184 cx: &mut Context<Self>,
15185 ) {
15186 if !self.diagnostics_enabled() {
15187 return;
15188 }
15189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15190 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15191 }
15192
15193 pub fn go_to_prev_diagnostic(
15194 &mut self,
15195 action: &GoToPreviousDiagnostic,
15196 window: &mut Window,
15197 cx: &mut Context<Self>,
15198 ) {
15199 if !self.diagnostics_enabled() {
15200 return;
15201 }
15202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15203 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15204 }
15205
15206 pub fn go_to_diagnostic_impl(
15207 &mut self,
15208 direction: Direction,
15209 severity: GoToDiagnosticSeverityFilter,
15210 window: &mut Window,
15211 cx: &mut Context<Self>,
15212 ) {
15213 let buffer = self.buffer.read(cx).snapshot(cx);
15214 let selection = self.selections.newest::<usize>(cx);
15215
15216 let mut active_group_id = None;
15217 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15218 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15219 active_group_id = Some(active_group.group_id);
15220 }
15221 }
15222
15223 fn filtered(
15224 snapshot: EditorSnapshot,
15225 severity: GoToDiagnosticSeverityFilter,
15226 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15227 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15228 diagnostics
15229 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15230 .filter(|entry| entry.range.start != entry.range.end)
15231 .filter(|entry| !entry.diagnostic.is_unnecessary)
15232 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15233 }
15234
15235 let snapshot = self.snapshot(window, cx);
15236 let before = filtered(
15237 snapshot.clone(),
15238 severity,
15239 buffer
15240 .diagnostics_in_range(0..selection.start)
15241 .filter(|entry| entry.range.start <= selection.start),
15242 );
15243 let after = filtered(
15244 snapshot,
15245 severity,
15246 buffer
15247 .diagnostics_in_range(selection.start..buffer.len())
15248 .filter(|entry| entry.range.start >= selection.start),
15249 );
15250
15251 let mut found: Option<DiagnosticEntry<usize>> = None;
15252 if direction == Direction::Prev {
15253 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15254 {
15255 for diagnostic in prev_diagnostics.into_iter().rev() {
15256 if diagnostic.range.start != selection.start
15257 || active_group_id
15258 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15259 {
15260 found = Some(diagnostic);
15261 break 'outer;
15262 }
15263 }
15264 }
15265 } else {
15266 for diagnostic in after.chain(before) {
15267 if diagnostic.range.start != selection.start
15268 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15269 {
15270 found = Some(diagnostic);
15271 break;
15272 }
15273 }
15274 }
15275 let Some(next_diagnostic) = found else {
15276 return;
15277 };
15278
15279 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15280 return;
15281 };
15282 self.change_selections(Default::default(), window, cx, |s| {
15283 s.select_ranges(vec![
15284 next_diagnostic.range.start..next_diagnostic.range.start,
15285 ])
15286 });
15287 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15288 self.refresh_inline_completion(false, true, window, cx);
15289 }
15290
15291 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15293 let snapshot = self.snapshot(window, cx);
15294 let selection = self.selections.newest::<Point>(cx);
15295 self.go_to_hunk_before_or_after_position(
15296 &snapshot,
15297 selection.head(),
15298 Direction::Next,
15299 window,
15300 cx,
15301 );
15302 }
15303
15304 pub fn go_to_hunk_before_or_after_position(
15305 &mut self,
15306 snapshot: &EditorSnapshot,
15307 position: Point,
15308 direction: Direction,
15309 window: &mut Window,
15310 cx: &mut Context<Editor>,
15311 ) {
15312 let row = if direction == Direction::Next {
15313 self.hunk_after_position(snapshot, position)
15314 .map(|hunk| hunk.row_range.start)
15315 } else {
15316 self.hunk_before_position(snapshot, position)
15317 };
15318
15319 if let Some(row) = row {
15320 let destination = Point::new(row.0, 0);
15321 let autoscroll = Autoscroll::center();
15322
15323 self.unfold_ranges(&[destination..destination], false, false, cx);
15324 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15325 s.select_ranges([destination..destination]);
15326 });
15327 }
15328 }
15329
15330 fn hunk_after_position(
15331 &mut self,
15332 snapshot: &EditorSnapshot,
15333 position: Point,
15334 ) -> Option<MultiBufferDiffHunk> {
15335 snapshot
15336 .buffer_snapshot
15337 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15338 .find(|hunk| hunk.row_range.start.0 > position.row)
15339 .or_else(|| {
15340 snapshot
15341 .buffer_snapshot
15342 .diff_hunks_in_range(Point::zero()..position)
15343 .find(|hunk| hunk.row_range.end.0 < position.row)
15344 })
15345 }
15346
15347 fn go_to_prev_hunk(
15348 &mut self,
15349 _: &GoToPreviousHunk,
15350 window: &mut Window,
15351 cx: &mut Context<Self>,
15352 ) {
15353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15354 let snapshot = self.snapshot(window, cx);
15355 let selection = self.selections.newest::<Point>(cx);
15356 self.go_to_hunk_before_or_after_position(
15357 &snapshot,
15358 selection.head(),
15359 Direction::Prev,
15360 window,
15361 cx,
15362 );
15363 }
15364
15365 fn hunk_before_position(
15366 &mut self,
15367 snapshot: &EditorSnapshot,
15368 position: Point,
15369 ) -> Option<MultiBufferRow> {
15370 snapshot
15371 .buffer_snapshot
15372 .diff_hunk_before(position)
15373 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15374 }
15375
15376 fn go_to_next_change(
15377 &mut self,
15378 _: &GoToNextChange,
15379 window: &mut Window,
15380 cx: &mut Context<Self>,
15381 ) {
15382 if let Some(selections) = self
15383 .change_list
15384 .next_change(1, Direction::Next)
15385 .map(|s| s.to_vec())
15386 {
15387 self.change_selections(Default::default(), window, cx, |s| {
15388 let map = s.display_map();
15389 s.select_display_ranges(selections.iter().map(|a| {
15390 let point = a.to_display_point(&map);
15391 point..point
15392 }))
15393 })
15394 }
15395 }
15396
15397 fn go_to_previous_change(
15398 &mut self,
15399 _: &GoToPreviousChange,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 if let Some(selections) = self
15404 .change_list
15405 .next_change(1, Direction::Prev)
15406 .map(|s| s.to_vec())
15407 {
15408 self.change_selections(Default::default(), window, cx, |s| {
15409 let map = s.display_map();
15410 s.select_display_ranges(selections.iter().map(|a| {
15411 let point = a.to_display_point(&map);
15412 point..point
15413 }))
15414 })
15415 }
15416 }
15417
15418 fn go_to_line<T: 'static>(
15419 &mut self,
15420 position: Anchor,
15421 highlight_color: Option<Hsla>,
15422 window: &mut Window,
15423 cx: &mut Context<Self>,
15424 ) {
15425 let snapshot = self.snapshot(window, cx).display_snapshot;
15426 let position = position.to_point(&snapshot.buffer_snapshot);
15427 let start = snapshot
15428 .buffer_snapshot
15429 .clip_point(Point::new(position.row, 0), Bias::Left);
15430 let end = start + Point::new(1, 0);
15431 let start = snapshot.buffer_snapshot.anchor_before(start);
15432 let end = snapshot.buffer_snapshot.anchor_before(end);
15433
15434 self.highlight_rows::<T>(
15435 start..end,
15436 highlight_color
15437 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15438 Default::default(),
15439 cx,
15440 );
15441
15442 if self.buffer.read(cx).is_singleton() {
15443 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15444 }
15445 }
15446
15447 pub fn go_to_definition(
15448 &mut self,
15449 _: &GoToDefinition,
15450 window: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) -> Task<Result<Navigated>> {
15453 let definition =
15454 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15455 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15456 cx.spawn_in(window, async move |editor, cx| {
15457 if definition.await? == Navigated::Yes {
15458 return Ok(Navigated::Yes);
15459 }
15460 match fallback_strategy {
15461 GoToDefinitionFallback::None => Ok(Navigated::No),
15462 GoToDefinitionFallback::FindAllReferences => {
15463 match editor.update_in(cx, |editor, window, cx| {
15464 editor.find_all_references(&FindAllReferences, window, cx)
15465 })? {
15466 Some(references) => references.await,
15467 None => Ok(Navigated::No),
15468 }
15469 }
15470 }
15471 })
15472 }
15473
15474 pub fn go_to_declaration(
15475 &mut self,
15476 _: &GoToDeclaration,
15477 window: &mut Window,
15478 cx: &mut Context<Self>,
15479 ) -> Task<Result<Navigated>> {
15480 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15481 }
15482
15483 pub fn go_to_declaration_split(
15484 &mut self,
15485 _: &GoToDeclaration,
15486 window: &mut Window,
15487 cx: &mut Context<Self>,
15488 ) -> Task<Result<Navigated>> {
15489 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15490 }
15491
15492 pub fn go_to_implementation(
15493 &mut self,
15494 _: &GoToImplementation,
15495 window: &mut Window,
15496 cx: &mut Context<Self>,
15497 ) -> Task<Result<Navigated>> {
15498 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15499 }
15500
15501 pub fn go_to_implementation_split(
15502 &mut self,
15503 _: &GoToImplementationSplit,
15504 window: &mut Window,
15505 cx: &mut Context<Self>,
15506 ) -> Task<Result<Navigated>> {
15507 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15508 }
15509
15510 pub fn go_to_type_definition(
15511 &mut self,
15512 _: &GoToTypeDefinition,
15513 window: &mut Window,
15514 cx: &mut Context<Self>,
15515 ) -> Task<Result<Navigated>> {
15516 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15517 }
15518
15519 pub fn go_to_definition_split(
15520 &mut self,
15521 _: &GoToDefinitionSplit,
15522 window: &mut Window,
15523 cx: &mut Context<Self>,
15524 ) -> Task<Result<Navigated>> {
15525 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15526 }
15527
15528 pub fn go_to_type_definition_split(
15529 &mut self,
15530 _: &GoToTypeDefinitionSplit,
15531 window: &mut Window,
15532 cx: &mut Context<Self>,
15533 ) -> Task<Result<Navigated>> {
15534 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15535 }
15536
15537 fn go_to_definition_of_kind(
15538 &mut self,
15539 kind: GotoDefinitionKind,
15540 split: bool,
15541 window: &mut Window,
15542 cx: &mut Context<Self>,
15543 ) -> Task<Result<Navigated>> {
15544 let Some(provider) = self.semantics_provider.clone() else {
15545 return Task::ready(Ok(Navigated::No));
15546 };
15547 let head = self.selections.newest::<usize>(cx).head();
15548 let buffer = self.buffer.read(cx);
15549 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15550 text_anchor
15551 } else {
15552 return Task::ready(Ok(Navigated::No));
15553 };
15554
15555 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15556 return Task::ready(Ok(Navigated::No));
15557 };
15558
15559 cx.spawn_in(window, async move |editor, cx| {
15560 let definitions = definitions.await?;
15561 let navigated = editor
15562 .update_in(cx, |editor, window, cx| {
15563 editor.navigate_to_hover_links(
15564 Some(kind),
15565 definitions
15566 .into_iter()
15567 .filter(|location| {
15568 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15569 })
15570 .map(HoverLink::Text)
15571 .collect::<Vec<_>>(),
15572 split,
15573 window,
15574 cx,
15575 )
15576 })?
15577 .await?;
15578 anyhow::Ok(navigated)
15579 })
15580 }
15581
15582 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15583 let selection = self.selections.newest_anchor();
15584 let head = selection.head();
15585 let tail = selection.tail();
15586
15587 let Some((buffer, start_position)) =
15588 self.buffer.read(cx).text_anchor_for_position(head, cx)
15589 else {
15590 return;
15591 };
15592
15593 let end_position = if head != tail {
15594 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15595 return;
15596 };
15597 Some(pos)
15598 } else {
15599 None
15600 };
15601
15602 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15603 let url = if let Some(end_pos) = end_position {
15604 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15605 } else {
15606 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15607 };
15608
15609 if let Some(url) = url {
15610 editor.update(cx, |_, cx| {
15611 cx.open_url(&url);
15612 })
15613 } else {
15614 Ok(())
15615 }
15616 });
15617
15618 url_finder.detach();
15619 }
15620
15621 pub fn open_selected_filename(
15622 &mut self,
15623 _: &OpenSelectedFilename,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) {
15627 let Some(workspace) = self.workspace() else {
15628 return;
15629 };
15630
15631 let position = self.selections.newest_anchor().head();
15632
15633 let Some((buffer, buffer_position)) =
15634 self.buffer.read(cx).text_anchor_for_position(position, cx)
15635 else {
15636 return;
15637 };
15638
15639 let project = self.project.clone();
15640
15641 cx.spawn_in(window, async move |_, cx| {
15642 let result = find_file(&buffer, project, buffer_position, cx).await;
15643
15644 if let Some((_, path)) = result {
15645 workspace
15646 .update_in(cx, |workspace, window, cx| {
15647 workspace.open_resolved_path(path, window, cx)
15648 })?
15649 .await?;
15650 }
15651 anyhow::Ok(())
15652 })
15653 .detach();
15654 }
15655
15656 pub(crate) fn navigate_to_hover_links(
15657 &mut self,
15658 kind: Option<GotoDefinitionKind>,
15659 mut definitions: Vec<HoverLink>,
15660 split: bool,
15661 window: &mut Window,
15662 cx: &mut Context<Editor>,
15663 ) -> Task<Result<Navigated>> {
15664 // If there is one definition, just open it directly
15665 if definitions.len() == 1 {
15666 let definition = definitions.pop().unwrap();
15667
15668 enum TargetTaskResult {
15669 Location(Option<Location>),
15670 AlreadyNavigated,
15671 }
15672
15673 let target_task = match definition {
15674 HoverLink::Text(link) => {
15675 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15676 }
15677 HoverLink::InlayHint(lsp_location, server_id) => {
15678 let computation =
15679 self.compute_target_location(lsp_location, server_id, window, cx);
15680 cx.background_spawn(async move {
15681 let location = computation.await?;
15682 Ok(TargetTaskResult::Location(location))
15683 })
15684 }
15685 HoverLink::Url(url) => {
15686 cx.open_url(&url);
15687 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15688 }
15689 HoverLink::File(path) => {
15690 if let Some(workspace) = self.workspace() {
15691 cx.spawn_in(window, async move |_, cx| {
15692 workspace
15693 .update_in(cx, |workspace, window, cx| {
15694 workspace.open_resolved_path(path, window, cx)
15695 })?
15696 .await
15697 .map(|_| TargetTaskResult::AlreadyNavigated)
15698 })
15699 } else {
15700 Task::ready(Ok(TargetTaskResult::Location(None)))
15701 }
15702 }
15703 };
15704 cx.spawn_in(window, async move |editor, cx| {
15705 let target = match target_task.await.context("target resolution task")? {
15706 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15707 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15708 TargetTaskResult::Location(Some(target)) => target,
15709 };
15710
15711 editor.update_in(cx, |editor, window, cx| {
15712 let Some(workspace) = editor.workspace() else {
15713 return Navigated::No;
15714 };
15715 let pane = workspace.read(cx).active_pane().clone();
15716
15717 let range = target.range.to_point(target.buffer.read(cx));
15718 let range = editor.range_for_match(&range);
15719 let range = collapse_multiline_range(range);
15720
15721 if !split
15722 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15723 {
15724 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15725 } else {
15726 window.defer(cx, move |window, cx| {
15727 let target_editor: Entity<Self> =
15728 workspace.update(cx, |workspace, cx| {
15729 let pane = if split {
15730 workspace.adjacent_pane(window, cx)
15731 } else {
15732 workspace.active_pane().clone()
15733 };
15734
15735 workspace.open_project_item(
15736 pane,
15737 target.buffer.clone(),
15738 true,
15739 true,
15740 window,
15741 cx,
15742 )
15743 });
15744 target_editor.update(cx, |target_editor, cx| {
15745 // When selecting a definition in a different buffer, disable the nav history
15746 // to avoid creating a history entry at the previous cursor location.
15747 pane.update(cx, |pane, _| pane.disable_history());
15748 target_editor.go_to_singleton_buffer_range(range, window, cx);
15749 pane.update(cx, |pane, _| pane.enable_history());
15750 });
15751 });
15752 }
15753 Navigated::Yes
15754 })
15755 })
15756 } else if !definitions.is_empty() {
15757 cx.spawn_in(window, async move |editor, cx| {
15758 let (title, location_tasks, workspace) = editor
15759 .update_in(cx, |editor, window, cx| {
15760 let tab_kind = match kind {
15761 Some(GotoDefinitionKind::Implementation) => "Implementations",
15762 _ => "Definitions",
15763 };
15764 let title = definitions
15765 .iter()
15766 .find_map(|definition| match definition {
15767 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15768 let buffer = origin.buffer.read(cx);
15769 format!(
15770 "{} for {}",
15771 tab_kind,
15772 buffer
15773 .text_for_range(origin.range.clone())
15774 .collect::<String>()
15775 )
15776 }),
15777 HoverLink::InlayHint(_, _) => None,
15778 HoverLink::Url(_) => None,
15779 HoverLink::File(_) => None,
15780 })
15781 .unwrap_or(tab_kind.to_string());
15782 let location_tasks = definitions
15783 .into_iter()
15784 .map(|definition| match definition {
15785 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15786 HoverLink::InlayHint(lsp_location, server_id) => editor
15787 .compute_target_location(lsp_location, server_id, window, cx),
15788 HoverLink::Url(_) => Task::ready(Ok(None)),
15789 HoverLink::File(_) => Task::ready(Ok(None)),
15790 })
15791 .collect::<Vec<_>>();
15792 (title, location_tasks, editor.workspace().clone())
15793 })
15794 .context("location tasks preparation")?;
15795
15796 let locations: Vec<Location> = future::join_all(location_tasks)
15797 .await
15798 .into_iter()
15799 .filter_map(|location| location.transpose())
15800 .collect::<Result<_>>()
15801 .context("location tasks")?;
15802
15803 if locations.is_empty() {
15804 return Ok(Navigated::No);
15805 }
15806
15807 let Some(workspace) = workspace else {
15808 return Ok(Navigated::No);
15809 };
15810
15811 let opened = workspace
15812 .update_in(cx, |workspace, window, cx| {
15813 Self::open_locations_in_multibuffer(
15814 workspace,
15815 locations,
15816 title,
15817 split,
15818 MultibufferSelectionMode::First,
15819 window,
15820 cx,
15821 )
15822 })
15823 .ok();
15824
15825 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15826 })
15827 } else {
15828 Task::ready(Ok(Navigated::No))
15829 }
15830 }
15831
15832 fn compute_target_location(
15833 &self,
15834 lsp_location: lsp::Location,
15835 server_id: LanguageServerId,
15836 window: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) -> Task<anyhow::Result<Option<Location>>> {
15839 let Some(project) = self.project.clone() else {
15840 return Task::ready(Ok(None));
15841 };
15842
15843 cx.spawn_in(window, async move |editor, cx| {
15844 let location_task = editor.update(cx, |_, cx| {
15845 project.update(cx, |project, cx| {
15846 let language_server_name = project
15847 .language_server_statuses(cx)
15848 .find(|(id, _)| server_id == *id)
15849 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15850 language_server_name.map(|language_server_name| {
15851 project.open_local_buffer_via_lsp(
15852 lsp_location.uri.clone(),
15853 server_id,
15854 language_server_name,
15855 cx,
15856 )
15857 })
15858 })
15859 })?;
15860 let location = match location_task {
15861 Some(task) => Some({
15862 let target_buffer_handle = task.await.context("open local buffer")?;
15863 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15864 let target_start = target_buffer
15865 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15866 let target_end = target_buffer
15867 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15868 target_buffer.anchor_after(target_start)
15869 ..target_buffer.anchor_before(target_end)
15870 })?;
15871 Location {
15872 buffer: target_buffer_handle,
15873 range,
15874 }
15875 }),
15876 None => None,
15877 };
15878 Ok(location)
15879 })
15880 }
15881
15882 pub fn find_all_references(
15883 &mut self,
15884 _: &FindAllReferences,
15885 window: &mut Window,
15886 cx: &mut Context<Self>,
15887 ) -> Option<Task<Result<Navigated>>> {
15888 let selection = self.selections.newest::<usize>(cx);
15889 let multi_buffer = self.buffer.read(cx);
15890 let head = selection.head();
15891
15892 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15893 let head_anchor = multi_buffer_snapshot.anchor_at(
15894 head,
15895 if head < selection.tail() {
15896 Bias::Right
15897 } else {
15898 Bias::Left
15899 },
15900 );
15901
15902 match self
15903 .find_all_references_task_sources
15904 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15905 {
15906 Ok(_) => {
15907 log::info!(
15908 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15909 );
15910 return None;
15911 }
15912 Err(i) => {
15913 self.find_all_references_task_sources.insert(i, head_anchor);
15914 }
15915 }
15916
15917 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15918 let workspace = self.workspace()?;
15919 let project = workspace.read(cx).project().clone();
15920 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15921 Some(cx.spawn_in(window, async move |editor, cx| {
15922 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15923 if let Ok(i) = editor
15924 .find_all_references_task_sources
15925 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15926 {
15927 editor.find_all_references_task_sources.remove(i);
15928 }
15929 });
15930
15931 let locations = references.await?;
15932 if locations.is_empty() {
15933 return anyhow::Ok(Navigated::No);
15934 }
15935
15936 workspace.update_in(cx, |workspace, window, cx| {
15937 let title = locations
15938 .first()
15939 .as_ref()
15940 .map(|location| {
15941 let buffer = location.buffer.read(cx);
15942 format!(
15943 "References to `{}`",
15944 buffer
15945 .text_for_range(location.range.clone())
15946 .collect::<String>()
15947 )
15948 })
15949 .unwrap();
15950 Self::open_locations_in_multibuffer(
15951 workspace,
15952 locations,
15953 title,
15954 false,
15955 MultibufferSelectionMode::First,
15956 window,
15957 cx,
15958 );
15959 Navigated::Yes
15960 })
15961 }))
15962 }
15963
15964 /// Opens a multibuffer with the given project locations in it
15965 pub fn open_locations_in_multibuffer(
15966 workspace: &mut Workspace,
15967 mut locations: Vec<Location>,
15968 title: String,
15969 split: bool,
15970 multibuffer_selection_mode: MultibufferSelectionMode,
15971 window: &mut Window,
15972 cx: &mut Context<Workspace>,
15973 ) {
15974 if locations.is_empty() {
15975 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15976 return;
15977 }
15978
15979 // If there are multiple definitions, open them in a multibuffer
15980 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15981 let mut locations = locations.into_iter().peekable();
15982 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15983 let capability = workspace.project().read(cx).capability();
15984
15985 let excerpt_buffer = cx.new(|cx| {
15986 let mut multibuffer = MultiBuffer::new(capability);
15987 while let Some(location) = locations.next() {
15988 let buffer = location.buffer.read(cx);
15989 let mut ranges_for_buffer = Vec::new();
15990 let range = location.range.to_point(buffer);
15991 ranges_for_buffer.push(range.clone());
15992
15993 while let Some(next_location) = locations.peek() {
15994 if next_location.buffer == location.buffer {
15995 ranges_for_buffer.push(next_location.range.to_point(buffer));
15996 locations.next();
15997 } else {
15998 break;
15999 }
16000 }
16001
16002 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16003 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16004 PathKey::for_buffer(&location.buffer, cx),
16005 location.buffer.clone(),
16006 ranges_for_buffer,
16007 DEFAULT_MULTIBUFFER_CONTEXT,
16008 cx,
16009 );
16010 ranges.extend(new_ranges)
16011 }
16012
16013 multibuffer.with_title(title)
16014 });
16015
16016 let editor = cx.new(|cx| {
16017 Editor::for_multibuffer(
16018 excerpt_buffer,
16019 Some(workspace.project().clone()),
16020 window,
16021 cx,
16022 )
16023 });
16024 editor.update(cx, |editor, cx| {
16025 match multibuffer_selection_mode {
16026 MultibufferSelectionMode::First => {
16027 if let Some(first_range) = ranges.first() {
16028 editor.change_selections(
16029 SelectionEffects::no_scroll(),
16030 window,
16031 cx,
16032 |selections| {
16033 selections.clear_disjoint();
16034 selections
16035 .select_anchor_ranges(std::iter::once(first_range.clone()));
16036 },
16037 );
16038 }
16039 editor.highlight_background::<Self>(
16040 &ranges,
16041 |theme| theme.colors().editor_highlighted_line_background,
16042 cx,
16043 );
16044 }
16045 MultibufferSelectionMode::All => {
16046 editor.change_selections(
16047 SelectionEffects::no_scroll(),
16048 window,
16049 cx,
16050 |selections| {
16051 selections.clear_disjoint();
16052 selections.select_anchor_ranges(ranges);
16053 },
16054 );
16055 }
16056 }
16057 editor.register_buffers_with_language_servers(cx);
16058 });
16059
16060 let item = Box::new(editor);
16061 let item_id = item.item_id();
16062
16063 if split {
16064 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16065 } else {
16066 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16067 let (preview_item_id, preview_item_idx) =
16068 workspace.active_pane().read_with(cx, |pane, _| {
16069 (pane.preview_item_id(), pane.preview_item_idx())
16070 });
16071
16072 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16073
16074 if let Some(preview_item_id) = preview_item_id {
16075 workspace.active_pane().update(cx, |pane, cx| {
16076 pane.remove_item(preview_item_id, false, false, window, cx);
16077 });
16078 }
16079 } else {
16080 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16081 }
16082 }
16083 workspace.active_pane().update(cx, |pane, cx| {
16084 pane.set_preview_item_id(Some(item_id), cx);
16085 });
16086 }
16087
16088 pub fn rename(
16089 &mut self,
16090 _: &Rename,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) -> Option<Task<Result<()>>> {
16094 use language::ToOffset as _;
16095
16096 let provider = self.semantics_provider.clone()?;
16097 let selection = self.selections.newest_anchor().clone();
16098 let (cursor_buffer, cursor_buffer_position) = self
16099 .buffer
16100 .read(cx)
16101 .text_anchor_for_position(selection.head(), cx)?;
16102 let (tail_buffer, cursor_buffer_position_end) = self
16103 .buffer
16104 .read(cx)
16105 .text_anchor_for_position(selection.tail(), cx)?;
16106 if tail_buffer != cursor_buffer {
16107 return None;
16108 }
16109
16110 let snapshot = cursor_buffer.read(cx).snapshot();
16111 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16112 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16113 let prepare_rename = provider
16114 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16115 .unwrap_or_else(|| Task::ready(Ok(None)));
16116 drop(snapshot);
16117
16118 Some(cx.spawn_in(window, async move |this, cx| {
16119 let rename_range = if let Some(range) = prepare_rename.await? {
16120 Some(range)
16121 } else {
16122 this.update(cx, |this, cx| {
16123 let buffer = this.buffer.read(cx).snapshot(cx);
16124 let mut buffer_highlights = this
16125 .document_highlights_for_position(selection.head(), &buffer)
16126 .filter(|highlight| {
16127 highlight.start.excerpt_id == selection.head().excerpt_id
16128 && highlight.end.excerpt_id == selection.head().excerpt_id
16129 });
16130 buffer_highlights
16131 .next()
16132 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16133 })?
16134 };
16135 if let Some(rename_range) = rename_range {
16136 this.update_in(cx, |this, window, cx| {
16137 let snapshot = cursor_buffer.read(cx).snapshot();
16138 let rename_buffer_range = rename_range.to_offset(&snapshot);
16139 let cursor_offset_in_rename_range =
16140 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16141 let cursor_offset_in_rename_range_end =
16142 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16143
16144 this.take_rename(false, window, cx);
16145 let buffer = this.buffer.read(cx).read(cx);
16146 let cursor_offset = selection.head().to_offset(&buffer);
16147 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16148 let rename_end = rename_start + rename_buffer_range.len();
16149 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16150 let mut old_highlight_id = None;
16151 let old_name: Arc<str> = buffer
16152 .chunks(rename_start..rename_end, true)
16153 .map(|chunk| {
16154 if old_highlight_id.is_none() {
16155 old_highlight_id = chunk.syntax_highlight_id;
16156 }
16157 chunk.text
16158 })
16159 .collect::<String>()
16160 .into();
16161
16162 drop(buffer);
16163
16164 // Position the selection in the rename editor so that it matches the current selection.
16165 this.show_local_selections = false;
16166 let rename_editor = cx.new(|cx| {
16167 let mut editor = Editor::single_line(window, cx);
16168 editor.buffer.update(cx, |buffer, cx| {
16169 buffer.edit([(0..0, old_name.clone())], None, cx)
16170 });
16171 let rename_selection_range = match cursor_offset_in_rename_range
16172 .cmp(&cursor_offset_in_rename_range_end)
16173 {
16174 Ordering::Equal => {
16175 editor.select_all(&SelectAll, window, cx);
16176 return editor;
16177 }
16178 Ordering::Less => {
16179 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16180 }
16181 Ordering::Greater => {
16182 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16183 }
16184 };
16185 if rename_selection_range.end > old_name.len() {
16186 editor.select_all(&SelectAll, window, cx);
16187 } else {
16188 editor.change_selections(Default::default(), window, cx, |s| {
16189 s.select_ranges([rename_selection_range]);
16190 });
16191 }
16192 editor
16193 });
16194 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16195 if e == &EditorEvent::Focused {
16196 cx.emit(EditorEvent::FocusedIn)
16197 }
16198 })
16199 .detach();
16200
16201 let write_highlights =
16202 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16203 let read_highlights =
16204 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16205 let ranges = write_highlights
16206 .iter()
16207 .flat_map(|(_, ranges)| ranges.iter())
16208 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16209 .cloned()
16210 .collect();
16211
16212 this.highlight_text::<Rename>(
16213 ranges,
16214 HighlightStyle {
16215 fade_out: Some(0.6),
16216 ..Default::default()
16217 },
16218 cx,
16219 );
16220 let rename_focus_handle = rename_editor.focus_handle(cx);
16221 window.focus(&rename_focus_handle);
16222 let block_id = this.insert_blocks(
16223 [BlockProperties {
16224 style: BlockStyle::Flex,
16225 placement: BlockPlacement::Below(range.start),
16226 height: Some(1),
16227 render: Arc::new({
16228 let rename_editor = rename_editor.clone();
16229 move |cx: &mut BlockContext| {
16230 let mut text_style = cx.editor_style.text.clone();
16231 if let Some(highlight_style) = old_highlight_id
16232 .and_then(|h| h.style(&cx.editor_style.syntax))
16233 {
16234 text_style = text_style.highlight(highlight_style);
16235 }
16236 div()
16237 .block_mouse_except_scroll()
16238 .pl(cx.anchor_x)
16239 .child(EditorElement::new(
16240 &rename_editor,
16241 EditorStyle {
16242 background: cx.theme().system().transparent,
16243 local_player: cx.editor_style.local_player,
16244 text: text_style,
16245 scrollbar_width: cx.editor_style.scrollbar_width,
16246 syntax: cx.editor_style.syntax.clone(),
16247 status: cx.editor_style.status.clone(),
16248 inlay_hints_style: HighlightStyle {
16249 font_weight: Some(FontWeight::BOLD),
16250 ..make_inlay_hints_style(cx.app)
16251 },
16252 inline_completion_styles: make_suggestion_styles(
16253 cx.app,
16254 ),
16255 ..EditorStyle::default()
16256 },
16257 ))
16258 .into_any_element()
16259 }
16260 }),
16261 priority: 0,
16262 }],
16263 Some(Autoscroll::fit()),
16264 cx,
16265 )[0];
16266 this.pending_rename = Some(RenameState {
16267 range,
16268 old_name,
16269 editor: rename_editor,
16270 block_id,
16271 });
16272 })?;
16273 }
16274
16275 Ok(())
16276 }))
16277 }
16278
16279 pub fn confirm_rename(
16280 &mut self,
16281 _: &ConfirmRename,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) -> Option<Task<Result<()>>> {
16285 let rename = self.take_rename(false, window, cx)?;
16286 let workspace = self.workspace()?.downgrade();
16287 let (buffer, start) = self
16288 .buffer
16289 .read(cx)
16290 .text_anchor_for_position(rename.range.start, cx)?;
16291 let (end_buffer, _) = self
16292 .buffer
16293 .read(cx)
16294 .text_anchor_for_position(rename.range.end, cx)?;
16295 if buffer != end_buffer {
16296 return None;
16297 }
16298
16299 let old_name = rename.old_name;
16300 let new_name = rename.editor.read(cx).text(cx);
16301
16302 let rename = self.semantics_provider.as_ref()?.perform_rename(
16303 &buffer,
16304 start,
16305 new_name.clone(),
16306 cx,
16307 )?;
16308
16309 Some(cx.spawn_in(window, async move |editor, cx| {
16310 let project_transaction = rename.await?;
16311 Self::open_project_transaction(
16312 &editor,
16313 workspace,
16314 project_transaction,
16315 format!("Rename: {} → {}", old_name, new_name),
16316 cx,
16317 )
16318 .await?;
16319
16320 editor.update(cx, |editor, cx| {
16321 editor.refresh_document_highlights(cx);
16322 })?;
16323 Ok(())
16324 }))
16325 }
16326
16327 fn take_rename(
16328 &mut self,
16329 moving_cursor: bool,
16330 window: &mut Window,
16331 cx: &mut Context<Self>,
16332 ) -> Option<RenameState> {
16333 let rename = self.pending_rename.take()?;
16334 if rename.editor.focus_handle(cx).is_focused(window) {
16335 window.focus(&self.focus_handle);
16336 }
16337
16338 self.remove_blocks(
16339 [rename.block_id].into_iter().collect(),
16340 Some(Autoscroll::fit()),
16341 cx,
16342 );
16343 self.clear_highlights::<Rename>(cx);
16344 self.show_local_selections = true;
16345
16346 if moving_cursor {
16347 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16348 editor.selections.newest::<usize>(cx).head()
16349 });
16350
16351 // Update the selection to match the position of the selection inside
16352 // the rename editor.
16353 let snapshot = self.buffer.read(cx).read(cx);
16354 let rename_range = rename.range.to_offset(&snapshot);
16355 let cursor_in_editor = snapshot
16356 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16357 .min(rename_range.end);
16358 drop(snapshot);
16359
16360 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16361 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16362 });
16363 } else {
16364 self.refresh_document_highlights(cx);
16365 }
16366
16367 Some(rename)
16368 }
16369
16370 pub fn pending_rename(&self) -> Option<&RenameState> {
16371 self.pending_rename.as_ref()
16372 }
16373
16374 fn format(
16375 &mut self,
16376 _: &Format,
16377 window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) -> Option<Task<Result<()>>> {
16380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16381
16382 let project = match &self.project {
16383 Some(project) => project.clone(),
16384 None => return None,
16385 };
16386
16387 Some(self.perform_format(
16388 project,
16389 FormatTrigger::Manual,
16390 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16391 window,
16392 cx,
16393 ))
16394 }
16395
16396 fn format_selections(
16397 &mut self,
16398 _: &FormatSelections,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) -> Option<Task<Result<()>>> {
16402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16403
16404 let project = match &self.project {
16405 Some(project) => project.clone(),
16406 None => return None,
16407 };
16408
16409 let ranges = self
16410 .selections
16411 .all_adjusted(cx)
16412 .into_iter()
16413 .map(|selection| selection.range())
16414 .collect_vec();
16415
16416 Some(self.perform_format(
16417 project,
16418 FormatTrigger::Manual,
16419 FormatTarget::Ranges(ranges),
16420 window,
16421 cx,
16422 ))
16423 }
16424
16425 fn perform_format(
16426 &mut self,
16427 project: Entity<Project>,
16428 trigger: FormatTrigger,
16429 target: FormatTarget,
16430 window: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) -> Task<Result<()>> {
16433 let buffer = self.buffer.clone();
16434 let (buffers, target) = match target {
16435 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16436 FormatTarget::Ranges(selection_ranges) => {
16437 let multi_buffer = buffer.read(cx);
16438 let snapshot = multi_buffer.read(cx);
16439 let mut buffers = HashSet::default();
16440 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16441 BTreeMap::new();
16442 for selection_range in selection_ranges {
16443 for (buffer, buffer_range, _) in
16444 snapshot.range_to_buffer_ranges(selection_range)
16445 {
16446 let buffer_id = buffer.remote_id();
16447 let start = buffer.anchor_before(buffer_range.start);
16448 let end = buffer.anchor_after(buffer_range.end);
16449 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16450 buffer_id_to_ranges
16451 .entry(buffer_id)
16452 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16453 .or_insert_with(|| vec![start..end]);
16454 }
16455 }
16456 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16457 }
16458 };
16459
16460 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16461 let selections_prev = transaction_id_prev
16462 .and_then(|transaction_id_prev| {
16463 // default to selections as they were after the last edit, if we have them,
16464 // instead of how they are now.
16465 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16466 // will take you back to where you made the last edit, instead of staying where you scrolled
16467 self.selection_history
16468 .transaction(transaction_id_prev)
16469 .map(|t| t.0.clone())
16470 })
16471 .unwrap_or_else(|| {
16472 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16473 self.selections.disjoint_anchors()
16474 });
16475
16476 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16477 let format = project.update(cx, |project, cx| {
16478 project.format(buffers, target, true, trigger, cx)
16479 });
16480
16481 cx.spawn_in(window, async move |editor, cx| {
16482 let transaction = futures::select_biased! {
16483 transaction = format.log_err().fuse() => transaction,
16484 () = timeout => {
16485 log::warn!("timed out waiting for formatting");
16486 None
16487 }
16488 };
16489
16490 buffer
16491 .update(cx, |buffer, cx| {
16492 if let Some(transaction) = transaction {
16493 if !buffer.is_singleton() {
16494 buffer.push_transaction(&transaction.0, cx);
16495 }
16496 }
16497 cx.notify();
16498 })
16499 .ok();
16500
16501 if let Some(transaction_id_now) =
16502 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16503 {
16504 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16505 if has_new_transaction {
16506 _ = editor.update(cx, |editor, _| {
16507 editor
16508 .selection_history
16509 .insert_transaction(transaction_id_now, selections_prev);
16510 });
16511 }
16512 }
16513
16514 Ok(())
16515 })
16516 }
16517
16518 fn organize_imports(
16519 &mut self,
16520 _: &OrganizeImports,
16521 window: &mut Window,
16522 cx: &mut Context<Self>,
16523 ) -> Option<Task<Result<()>>> {
16524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16525 let project = match &self.project {
16526 Some(project) => project.clone(),
16527 None => return None,
16528 };
16529 Some(self.perform_code_action_kind(
16530 project,
16531 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16532 window,
16533 cx,
16534 ))
16535 }
16536
16537 fn perform_code_action_kind(
16538 &mut self,
16539 project: Entity<Project>,
16540 kind: CodeActionKind,
16541 window: &mut Window,
16542 cx: &mut Context<Self>,
16543 ) -> Task<Result<()>> {
16544 let buffer = self.buffer.clone();
16545 let buffers = buffer.read(cx).all_buffers();
16546 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16547 let apply_action = project.update(cx, |project, cx| {
16548 project.apply_code_action_kind(buffers, kind, true, cx)
16549 });
16550 cx.spawn_in(window, async move |_, cx| {
16551 let transaction = futures::select_biased! {
16552 () = timeout => {
16553 log::warn!("timed out waiting for executing code action");
16554 None
16555 }
16556 transaction = apply_action.log_err().fuse() => transaction,
16557 };
16558 buffer
16559 .update(cx, |buffer, cx| {
16560 // check if we need this
16561 if let Some(transaction) = transaction {
16562 if !buffer.is_singleton() {
16563 buffer.push_transaction(&transaction.0, cx);
16564 }
16565 }
16566 cx.notify();
16567 })
16568 .ok();
16569 Ok(())
16570 })
16571 }
16572
16573 pub fn restart_language_server(
16574 &mut self,
16575 _: &RestartLanguageServer,
16576 _: &mut Window,
16577 cx: &mut Context<Self>,
16578 ) {
16579 if let Some(project) = self.project.clone() {
16580 self.buffer.update(cx, |multi_buffer, cx| {
16581 project.update(cx, |project, cx| {
16582 project.restart_language_servers_for_buffers(
16583 multi_buffer.all_buffers().into_iter().collect(),
16584 HashSet::default(),
16585 cx,
16586 );
16587 });
16588 })
16589 }
16590 }
16591
16592 pub fn stop_language_server(
16593 &mut self,
16594 _: &StopLanguageServer,
16595 _: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) {
16598 if let Some(project) = self.project.clone() {
16599 self.buffer.update(cx, |multi_buffer, cx| {
16600 project.update(cx, |project, cx| {
16601 project.stop_language_servers_for_buffers(
16602 multi_buffer.all_buffers().into_iter().collect(),
16603 HashSet::default(),
16604 cx,
16605 );
16606 cx.emit(project::Event::RefreshInlayHints);
16607 });
16608 });
16609 }
16610 }
16611
16612 fn cancel_language_server_work(
16613 workspace: &mut Workspace,
16614 _: &actions::CancelLanguageServerWork,
16615 _: &mut Window,
16616 cx: &mut Context<Workspace>,
16617 ) {
16618 let project = workspace.project();
16619 let buffers = workspace
16620 .active_item(cx)
16621 .and_then(|item| item.act_as::<Editor>(cx))
16622 .map_or(HashSet::default(), |editor| {
16623 editor.read(cx).buffer.read(cx).all_buffers()
16624 });
16625 project.update(cx, |project, cx| {
16626 project.cancel_language_server_work_for_buffers(buffers, cx);
16627 });
16628 }
16629
16630 fn show_character_palette(
16631 &mut self,
16632 _: &ShowCharacterPalette,
16633 window: &mut Window,
16634 _: &mut Context<Self>,
16635 ) {
16636 window.show_character_palette();
16637 }
16638
16639 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16640 if !self.diagnostics_enabled() {
16641 return;
16642 }
16643
16644 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16645 let buffer = self.buffer.read(cx).snapshot(cx);
16646 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16647 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16648 let is_valid = buffer
16649 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16650 .any(|entry| {
16651 entry.diagnostic.is_primary
16652 && !entry.range.is_empty()
16653 && entry.range.start == primary_range_start
16654 && entry.diagnostic.message == active_diagnostics.active_message
16655 });
16656
16657 if !is_valid {
16658 self.dismiss_diagnostics(cx);
16659 }
16660 }
16661 }
16662
16663 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16664 match &self.active_diagnostics {
16665 ActiveDiagnostic::Group(group) => Some(group),
16666 _ => None,
16667 }
16668 }
16669
16670 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16671 if !self.diagnostics_enabled() {
16672 return;
16673 }
16674 self.dismiss_diagnostics(cx);
16675 self.active_diagnostics = ActiveDiagnostic::All;
16676 }
16677
16678 fn activate_diagnostics(
16679 &mut self,
16680 buffer_id: BufferId,
16681 diagnostic: DiagnosticEntry<usize>,
16682 window: &mut Window,
16683 cx: &mut Context<Self>,
16684 ) {
16685 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16686 return;
16687 }
16688 self.dismiss_diagnostics(cx);
16689 let snapshot = self.snapshot(window, cx);
16690 let buffer = self.buffer.read(cx).snapshot(cx);
16691 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16692 return;
16693 };
16694
16695 let diagnostic_group = buffer
16696 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16697 .collect::<Vec<_>>();
16698
16699 let blocks =
16700 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16701
16702 let blocks = self.display_map.update(cx, |display_map, cx| {
16703 display_map.insert_blocks(blocks, cx).into_iter().collect()
16704 });
16705 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16706 active_range: buffer.anchor_before(diagnostic.range.start)
16707 ..buffer.anchor_after(diagnostic.range.end),
16708 active_message: diagnostic.diagnostic.message.clone(),
16709 group_id: diagnostic.diagnostic.group_id,
16710 blocks,
16711 });
16712 cx.notify();
16713 }
16714
16715 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16716 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16717 return;
16718 };
16719
16720 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16721 if let ActiveDiagnostic::Group(group) = prev {
16722 self.display_map.update(cx, |display_map, cx| {
16723 display_map.remove_blocks(group.blocks, cx);
16724 });
16725 cx.notify();
16726 }
16727 }
16728
16729 /// Disable inline diagnostics rendering for this editor.
16730 pub fn disable_inline_diagnostics(&mut self) {
16731 self.inline_diagnostics_enabled = false;
16732 self.inline_diagnostics_update = Task::ready(());
16733 self.inline_diagnostics.clear();
16734 }
16735
16736 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16737 self.diagnostics_enabled = false;
16738 self.dismiss_diagnostics(cx);
16739 self.inline_diagnostics_update = Task::ready(());
16740 self.inline_diagnostics.clear();
16741 }
16742
16743 pub fn diagnostics_enabled(&self) -> bool {
16744 self.diagnostics_enabled && self.mode.is_full()
16745 }
16746
16747 pub fn inline_diagnostics_enabled(&self) -> bool {
16748 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16749 }
16750
16751 pub fn show_inline_diagnostics(&self) -> bool {
16752 self.show_inline_diagnostics
16753 }
16754
16755 pub fn toggle_inline_diagnostics(
16756 &mut self,
16757 _: &ToggleInlineDiagnostics,
16758 window: &mut Window,
16759 cx: &mut Context<Editor>,
16760 ) {
16761 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16762 self.refresh_inline_diagnostics(false, window, cx);
16763 }
16764
16765 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16766 self.diagnostics_max_severity = severity;
16767 self.display_map.update(cx, |display_map, _| {
16768 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16769 });
16770 }
16771
16772 pub fn toggle_diagnostics(
16773 &mut self,
16774 _: &ToggleDiagnostics,
16775 window: &mut Window,
16776 cx: &mut Context<Editor>,
16777 ) {
16778 if !self.diagnostics_enabled() {
16779 return;
16780 }
16781
16782 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16783 EditorSettings::get_global(cx)
16784 .diagnostics_max_severity
16785 .filter(|severity| severity != &DiagnosticSeverity::Off)
16786 .unwrap_or(DiagnosticSeverity::Hint)
16787 } else {
16788 DiagnosticSeverity::Off
16789 };
16790 self.set_max_diagnostics_severity(new_severity, cx);
16791 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16792 self.active_diagnostics = ActiveDiagnostic::None;
16793 self.inline_diagnostics_update = Task::ready(());
16794 self.inline_diagnostics.clear();
16795 } else {
16796 self.refresh_inline_diagnostics(false, window, cx);
16797 }
16798
16799 cx.notify();
16800 }
16801
16802 pub fn toggle_minimap(
16803 &mut self,
16804 _: &ToggleMinimap,
16805 window: &mut Window,
16806 cx: &mut Context<Editor>,
16807 ) {
16808 if self.supports_minimap(cx) {
16809 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16810 }
16811 }
16812
16813 fn refresh_inline_diagnostics(
16814 &mut self,
16815 debounce: bool,
16816 window: &mut Window,
16817 cx: &mut Context<Self>,
16818 ) {
16819 let max_severity = ProjectSettings::get_global(cx)
16820 .diagnostics
16821 .inline
16822 .max_severity
16823 .unwrap_or(self.diagnostics_max_severity);
16824
16825 if !self.inline_diagnostics_enabled()
16826 || !self.show_inline_diagnostics
16827 || max_severity == DiagnosticSeverity::Off
16828 {
16829 self.inline_diagnostics_update = Task::ready(());
16830 self.inline_diagnostics.clear();
16831 return;
16832 }
16833
16834 let debounce_ms = ProjectSettings::get_global(cx)
16835 .diagnostics
16836 .inline
16837 .update_debounce_ms;
16838 let debounce = if debounce && debounce_ms > 0 {
16839 Some(Duration::from_millis(debounce_ms))
16840 } else {
16841 None
16842 };
16843 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16844 if let Some(debounce) = debounce {
16845 cx.background_executor().timer(debounce).await;
16846 }
16847 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16848 editor
16849 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16850 .ok()
16851 }) else {
16852 return;
16853 };
16854
16855 let new_inline_diagnostics = cx
16856 .background_spawn(async move {
16857 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16858 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16859 let message = diagnostic_entry
16860 .diagnostic
16861 .message
16862 .split_once('\n')
16863 .map(|(line, _)| line)
16864 .map(SharedString::new)
16865 .unwrap_or_else(|| {
16866 SharedString::from(diagnostic_entry.diagnostic.message)
16867 });
16868 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16869 let (Ok(i) | Err(i)) = inline_diagnostics
16870 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16871 inline_diagnostics.insert(
16872 i,
16873 (
16874 start_anchor,
16875 InlineDiagnostic {
16876 message,
16877 group_id: diagnostic_entry.diagnostic.group_id,
16878 start: diagnostic_entry.range.start.to_point(&snapshot),
16879 is_primary: diagnostic_entry.diagnostic.is_primary,
16880 severity: diagnostic_entry.diagnostic.severity,
16881 },
16882 ),
16883 );
16884 }
16885 inline_diagnostics
16886 })
16887 .await;
16888
16889 editor
16890 .update(cx, |editor, cx| {
16891 editor.inline_diagnostics = new_inline_diagnostics;
16892 cx.notify();
16893 })
16894 .ok();
16895 });
16896 }
16897
16898 fn pull_diagnostics(
16899 &mut self,
16900 buffer_id: Option<BufferId>,
16901 window: &Window,
16902 cx: &mut Context<Self>,
16903 ) -> Option<()> {
16904 if !self.mode().is_full() {
16905 return None;
16906 }
16907 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16908 .diagnostics
16909 .lsp_pull_diagnostics;
16910 if !pull_diagnostics_settings.enabled {
16911 return None;
16912 }
16913 let project = self.project.as_ref()?.downgrade();
16914 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16915 let mut buffers = self.buffer.read(cx).all_buffers();
16916 if let Some(buffer_id) = buffer_id {
16917 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16918 }
16919
16920 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16921 cx.background_executor().timer(debounce).await;
16922
16923 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16924 buffers
16925 .into_iter()
16926 .filter_map(|buffer| {
16927 project
16928 .update(cx, |project, cx| {
16929 project.lsp_store().update(cx, |lsp_store, cx| {
16930 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16931 })
16932 })
16933 .ok()
16934 })
16935 .collect::<FuturesUnordered<_>>()
16936 }) else {
16937 return;
16938 };
16939
16940 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16941 match pull_task {
16942 Ok(()) => {
16943 if editor
16944 .update_in(cx, |editor, window, cx| {
16945 editor.update_diagnostics_state(window, cx);
16946 })
16947 .is_err()
16948 {
16949 return;
16950 }
16951 }
16952 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16953 }
16954 }
16955 });
16956
16957 Some(())
16958 }
16959
16960 pub fn set_selections_from_remote(
16961 &mut self,
16962 selections: Vec<Selection<Anchor>>,
16963 pending_selection: Option<Selection<Anchor>>,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 let old_cursor_position = self.selections.newest_anchor().head();
16968 self.selections.change_with(cx, |s| {
16969 s.select_anchors(selections);
16970 if let Some(pending_selection) = pending_selection {
16971 s.set_pending(pending_selection, SelectMode::Character);
16972 } else {
16973 s.clear_pending();
16974 }
16975 });
16976 self.selections_did_change(
16977 false,
16978 &old_cursor_position,
16979 SelectionEffects::default(),
16980 window,
16981 cx,
16982 );
16983 }
16984
16985 pub fn transact(
16986 &mut self,
16987 window: &mut Window,
16988 cx: &mut Context<Self>,
16989 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16990 ) -> Option<TransactionId> {
16991 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16992 this.start_transaction_at(Instant::now(), window, cx);
16993 update(this, window, cx);
16994 this.end_transaction_at(Instant::now(), cx)
16995 })
16996 }
16997
16998 pub fn start_transaction_at(
16999 &mut self,
17000 now: Instant,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) -> Option<TransactionId> {
17004 self.end_selection(window, cx);
17005 if let Some(tx_id) = self
17006 .buffer
17007 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17008 {
17009 self.selection_history
17010 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17011 cx.emit(EditorEvent::TransactionBegun {
17012 transaction_id: tx_id,
17013 });
17014 Some(tx_id)
17015 } else {
17016 None
17017 }
17018 }
17019
17020 pub fn end_transaction_at(
17021 &mut self,
17022 now: Instant,
17023 cx: &mut Context<Self>,
17024 ) -> Option<TransactionId> {
17025 if let Some(transaction_id) = self
17026 .buffer
17027 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17028 {
17029 if let Some((_, end_selections)) =
17030 self.selection_history.transaction_mut(transaction_id)
17031 {
17032 *end_selections = Some(self.selections.disjoint_anchors());
17033 } else {
17034 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17035 }
17036
17037 cx.emit(EditorEvent::Edited { transaction_id });
17038 Some(transaction_id)
17039 } else {
17040 None
17041 }
17042 }
17043
17044 pub fn modify_transaction_selection_history(
17045 &mut self,
17046 transaction_id: TransactionId,
17047 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17048 ) -> bool {
17049 self.selection_history
17050 .transaction_mut(transaction_id)
17051 .map(modify)
17052 .is_some()
17053 }
17054
17055 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17056 if self.selection_mark_mode {
17057 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17058 s.move_with(|_, sel| {
17059 sel.collapse_to(sel.head(), SelectionGoal::None);
17060 });
17061 })
17062 }
17063 self.selection_mark_mode = true;
17064 cx.notify();
17065 }
17066
17067 pub fn swap_selection_ends(
17068 &mut self,
17069 _: &actions::SwapSelectionEnds,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) {
17073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17074 s.move_with(|_, sel| {
17075 if sel.start != sel.end {
17076 sel.reversed = !sel.reversed
17077 }
17078 });
17079 });
17080 self.request_autoscroll(Autoscroll::newest(), cx);
17081 cx.notify();
17082 }
17083
17084 pub fn toggle_focus(
17085 workspace: &mut Workspace,
17086 _: &actions::ToggleFocus,
17087 window: &mut Window,
17088 cx: &mut Context<Workspace>,
17089 ) {
17090 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17091 return;
17092 };
17093 workspace.activate_item(&item, true, true, window, cx);
17094 }
17095
17096 pub fn toggle_fold(
17097 &mut self,
17098 _: &actions::ToggleFold,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 if self.is_singleton(cx) {
17103 let selection = self.selections.newest::<Point>(cx);
17104
17105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17106 let range = if selection.is_empty() {
17107 let point = selection.head().to_display_point(&display_map);
17108 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17109 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17110 .to_point(&display_map);
17111 start..end
17112 } else {
17113 selection.range()
17114 };
17115 if display_map.folds_in_range(range).next().is_some() {
17116 self.unfold_lines(&Default::default(), window, cx)
17117 } else {
17118 self.fold(&Default::default(), window, cx)
17119 }
17120 } else {
17121 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17122 let buffer_ids: HashSet<_> = self
17123 .selections
17124 .disjoint_anchor_ranges()
17125 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17126 .collect();
17127
17128 let should_unfold = buffer_ids
17129 .iter()
17130 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17131
17132 for buffer_id in buffer_ids {
17133 if should_unfold {
17134 self.unfold_buffer(buffer_id, cx);
17135 } else {
17136 self.fold_buffer(buffer_id, cx);
17137 }
17138 }
17139 }
17140 }
17141
17142 pub fn toggle_fold_recursive(
17143 &mut self,
17144 _: &actions::ToggleFoldRecursive,
17145 window: &mut Window,
17146 cx: &mut Context<Self>,
17147 ) {
17148 let selection = self.selections.newest::<Point>(cx);
17149
17150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17151 let range = if selection.is_empty() {
17152 let point = selection.head().to_display_point(&display_map);
17153 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17154 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17155 .to_point(&display_map);
17156 start..end
17157 } else {
17158 selection.range()
17159 };
17160 if display_map.folds_in_range(range).next().is_some() {
17161 self.unfold_recursive(&Default::default(), window, cx)
17162 } else {
17163 self.fold_recursive(&Default::default(), window, cx)
17164 }
17165 }
17166
17167 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17168 if self.is_singleton(cx) {
17169 let mut to_fold = Vec::new();
17170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17171 let selections = self.selections.all_adjusted(cx);
17172
17173 for selection in selections {
17174 let range = selection.range().sorted();
17175 let buffer_start_row = range.start.row;
17176
17177 if range.start.row != range.end.row {
17178 let mut found = false;
17179 let mut row = range.start.row;
17180 while row <= range.end.row {
17181 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17182 {
17183 found = true;
17184 row = crease.range().end.row + 1;
17185 to_fold.push(crease);
17186 } else {
17187 row += 1
17188 }
17189 }
17190 if found {
17191 continue;
17192 }
17193 }
17194
17195 for row in (0..=range.start.row).rev() {
17196 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17197 if crease.range().end.row >= buffer_start_row {
17198 to_fold.push(crease);
17199 if row <= range.start.row {
17200 break;
17201 }
17202 }
17203 }
17204 }
17205 }
17206
17207 self.fold_creases(to_fold, true, window, cx);
17208 } else {
17209 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17210 let buffer_ids = self
17211 .selections
17212 .disjoint_anchor_ranges()
17213 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17214 .collect::<HashSet<_>>();
17215 for buffer_id in buffer_ids {
17216 self.fold_buffer(buffer_id, cx);
17217 }
17218 }
17219 }
17220
17221 pub fn toggle_fold_all(
17222 &mut self,
17223 _: &actions::ToggleFoldAll,
17224 window: &mut Window,
17225 cx: &mut Context<Self>,
17226 ) {
17227 if self.buffer.read(cx).is_singleton() {
17228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17229 let has_folds = display_map
17230 .folds_in_range(0..display_map.buffer_snapshot.len())
17231 .next()
17232 .is_some();
17233
17234 if has_folds {
17235 self.unfold_all(&actions::UnfoldAll, window, cx);
17236 } else {
17237 self.fold_all(&actions::FoldAll, window, cx);
17238 }
17239 } else {
17240 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17241 let should_unfold = buffer_ids
17242 .iter()
17243 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17244
17245 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17246 editor
17247 .update_in(cx, |editor, _, cx| {
17248 for buffer_id in buffer_ids {
17249 if should_unfold {
17250 editor.unfold_buffer(buffer_id, cx);
17251 } else {
17252 editor.fold_buffer(buffer_id, cx);
17253 }
17254 }
17255 })
17256 .ok();
17257 });
17258 }
17259 }
17260
17261 fn fold_at_level(
17262 &mut self,
17263 fold_at: &FoldAtLevel,
17264 window: &mut Window,
17265 cx: &mut Context<Self>,
17266 ) {
17267 if !self.buffer.read(cx).is_singleton() {
17268 return;
17269 }
17270
17271 let fold_at_level = fold_at.0;
17272 let snapshot = self.buffer.read(cx).snapshot(cx);
17273 let mut to_fold = Vec::new();
17274 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17275
17276 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17277 while start_row < end_row {
17278 match self
17279 .snapshot(window, cx)
17280 .crease_for_buffer_row(MultiBufferRow(start_row))
17281 {
17282 Some(crease) => {
17283 let nested_start_row = crease.range().start.row + 1;
17284 let nested_end_row = crease.range().end.row;
17285
17286 if current_level < fold_at_level {
17287 stack.push((nested_start_row, nested_end_row, current_level + 1));
17288 } else if current_level == fold_at_level {
17289 to_fold.push(crease);
17290 }
17291
17292 start_row = nested_end_row + 1;
17293 }
17294 None => start_row += 1,
17295 }
17296 }
17297 }
17298
17299 self.fold_creases(to_fold, true, window, cx);
17300 }
17301
17302 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17303 if self.buffer.read(cx).is_singleton() {
17304 let mut fold_ranges = Vec::new();
17305 let snapshot = self.buffer.read(cx).snapshot(cx);
17306
17307 for row in 0..snapshot.max_row().0 {
17308 if let Some(foldable_range) = self
17309 .snapshot(window, cx)
17310 .crease_for_buffer_row(MultiBufferRow(row))
17311 {
17312 fold_ranges.push(foldable_range);
17313 }
17314 }
17315
17316 self.fold_creases(fold_ranges, true, window, cx);
17317 } else {
17318 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17319 editor
17320 .update_in(cx, |editor, _, cx| {
17321 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17322 editor.fold_buffer(buffer_id, cx);
17323 }
17324 })
17325 .ok();
17326 });
17327 }
17328 }
17329
17330 pub fn fold_function_bodies(
17331 &mut self,
17332 _: &actions::FoldFunctionBodies,
17333 window: &mut Window,
17334 cx: &mut Context<Self>,
17335 ) {
17336 let snapshot = self.buffer.read(cx).snapshot(cx);
17337
17338 let ranges = snapshot
17339 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17340 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17341 .collect::<Vec<_>>();
17342
17343 let creases = ranges
17344 .into_iter()
17345 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17346 .collect();
17347
17348 self.fold_creases(creases, true, window, cx);
17349 }
17350
17351 pub fn fold_recursive(
17352 &mut self,
17353 _: &actions::FoldRecursive,
17354 window: &mut Window,
17355 cx: &mut Context<Self>,
17356 ) {
17357 let mut to_fold = Vec::new();
17358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17359 let selections = self.selections.all_adjusted(cx);
17360
17361 for selection in selections {
17362 let range = selection.range().sorted();
17363 let buffer_start_row = range.start.row;
17364
17365 if range.start.row != range.end.row {
17366 let mut found = false;
17367 for row in range.start.row..=range.end.row {
17368 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17369 found = true;
17370 to_fold.push(crease);
17371 }
17372 }
17373 if found {
17374 continue;
17375 }
17376 }
17377
17378 for row in (0..=range.start.row).rev() {
17379 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17380 if crease.range().end.row >= buffer_start_row {
17381 to_fold.push(crease);
17382 } else {
17383 break;
17384 }
17385 }
17386 }
17387 }
17388
17389 self.fold_creases(to_fold, true, window, cx);
17390 }
17391
17392 pub fn fold_at(
17393 &mut self,
17394 buffer_row: MultiBufferRow,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) {
17398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17399
17400 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17401 let autoscroll = self
17402 .selections
17403 .all::<Point>(cx)
17404 .iter()
17405 .any(|selection| crease.range().overlaps(&selection.range()));
17406
17407 self.fold_creases(vec![crease], autoscroll, window, cx);
17408 }
17409 }
17410
17411 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17412 if self.is_singleton(cx) {
17413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17414 let buffer = &display_map.buffer_snapshot;
17415 let selections = self.selections.all::<Point>(cx);
17416 let ranges = selections
17417 .iter()
17418 .map(|s| {
17419 let range = s.display_range(&display_map).sorted();
17420 let mut start = range.start.to_point(&display_map);
17421 let mut end = range.end.to_point(&display_map);
17422 start.column = 0;
17423 end.column = buffer.line_len(MultiBufferRow(end.row));
17424 start..end
17425 })
17426 .collect::<Vec<_>>();
17427
17428 self.unfold_ranges(&ranges, true, true, cx);
17429 } else {
17430 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17431 let buffer_ids = self
17432 .selections
17433 .disjoint_anchor_ranges()
17434 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17435 .collect::<HashSet<_>>();
17436 for buffer_id in buffer_ids {
17437 self.unfold_buffer(buffer_id, cx);
17438 }
17439 }
17440 }
17441
17442 pub fn unfold_recursive(
17443 &mut self,
17444 _: &UnfoldRecursive,
17445 _window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17449 let selections = self.selections.all::<Point>(cx);
17450 let ranges = selections
17451 .iter()
17452 .map(|s| {
17453 let mut range = s.display_range(&display_map).sorted();
17454 *range.start.column_mut() = 0;
17455 *range.end.column_mut() = display_map.line_len(range.end.row());
17456 let start = range.start.to_point(&display_map);
17457 let end = range.end.to_point(&display_map);
17458 start..end
17459 })
17460 .collect::<Vec<_>>();
17461
17462 self.unfold_ranges(&ranges, true, true, cx);
17463 }
17464
17465 pub fn unfold_at(
17466 &mut self,
17467 buffer_row: MultiBufferRow,
17468 _window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17472
17473 let intersection_range = Point::new(buffer_row.0, 0)
17474 ..Point::new(
17475 buffer_row.0,
17476 display_map.buffer_snapshot.line_len(buffer_row),
17477 );
17478
17479 let autoscroll = self
17480 .selections
17481 .all::<Point>(cx)
17482 .iter()
17483 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17484
17485 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17486 }
17487
17488 pub fn unfold_all(
17489 &mut self,
17490 _: &actions::UnfoldAll,
17491 _window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) {
17494 if self.buffer.read(cx).is_singleton() {
17495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17496 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17497 } else {
17498 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17499 editor
17500 .update(cx, |editor, cx| {
17501 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17502 editor.unfold_buffer(buffer_id, cx);
17503 }
17504 })
17505 .ok();
17506 });
17507 }
17508 }
17509
17510 pub fn fold_selected_ranges(
17511 &mut self,
17512 _: &FoldSelectedRanges,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 let selections = self.selections.all_adjusted(cx);
17517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17518 let ranges = selections
17519 .into_iter()
17520 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17521 .collect::<Vec<_>>();
17522 self.fold_creases(ranges, true, window, cx);
17523 }
17524
17525 pub fn fold_ranges<T: ToOffset + Clone>(
17526 &mut self,
17527 ranges: Vec<Range<T>>,
17528 auto_scroll: bool,
17529 window: &mut Window,
17530 cx: &mut Context<Self>,
17531 ) {
17532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17533 let ranges = ranges
17534 .into_iter()
17535 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17536 .collect::<Vec<_>>();
17537 self.fold_creases(ranges, auto_scroll, window, cx);
17538 }
17539
17540 pub fn fold_creases<T: ToOffset + Clone>(
17541 &mut self,
17542 creases: Vec<Crease<T>>,
17543 auto_scroll: bool,
17544 _window: &mut Window,
17545 cx: &mut Context<Self>,
17546 ) {
17547 if creases.is_empty() {
17548 return;
17549 }
17550
17551 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17552
17553 if auto_scroll {
17554 self.request_autoscroll(Autoscroll::fit(), cx);
17555 }
17556
17557 cx.notify();
17558
17559 self.scrollbar_marker_state.dirty = true;
17560 self.folds_did_change(cx);
17561 }
17562
17563 /// Removes any folds whose ranges intersect any of the given ranges.
17564 pub fn unfold_ranges<T: ToOffset + Clone>(
17565 &mut self,
17566 ranges: &[Range<T>],
17567 inclusive: bool,
17568 auto_scroll: bool,
17569 cx: &mut Context<Self>,
17570 ) {
17571 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17572 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17573 });
17574 self.folds_did_change(cx);
17575 }
17576
17577 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17578 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17579 return;
17580 }
17581 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17582 self.display_map.update(cx, |display_map, cx| {
17583 display_map.fold_buffers([buffer_id], cx)
17584 });
17585 cx.emit(EditorEvent::BufferFoldToggled {
17586 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17587 folded: true,
17588 });
17589 cx.notify();
17590 }
17591
17592 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17593 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17594 return;
17595 }
17596 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17597 self.display_map.update(cx, |display_map, cx| {
17598 display_map.unfold_buffers([buffer_id], cx);
17599 });
17600 cx.emit(EditorEvent::BufferFoldToggled {
17601 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17602 folded: false,
17603 });
17604 cx.notify();
17605 }
17606
17607 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17608 self.display_map.read(cx).is_buffer_folded(buffer)
17609 }
17610
17611 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17612 self.display_map.read(cx).folded_buffers()
17613 }
17614
17615 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17616 self.display_map.update(cx, |display_map, cx| {
17617 display_map.disable_header_for_buffer(buffer_id, cx);
17618 });
17619 cx.notify();
17620 }
17621
17622 /// Removes any folds with the given ranges.
17623 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17624 &mut self,
17625 ranges: &[Range<T>],
17626 type_id: TypeId,
17627 auto_scroll: bool,
17628 cx: &mut Context<Self>,
17629 ) {
17630 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17631 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17632 });
17633 self.folds_did_change(cx);
17634 }
17635
17636 fn remove_folds_with<T: ToOffset + Clone>(
17637 &mut self,
17638 ranges: &[Range<T>],
17639 auto_scroll: bool,
17640 cx: &mut Context<Self>,
17641 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17642 ) {
17643 if ranges.is_empty() {
17644 return;
17645 }
17646
17647 let mut buffers_affected = HashSet::default();
17648 let multi_buffer = self.buffer().read(cx);
17649 for range in ranges {
17650 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17651 buffers_affected.insert(buffer.read(cx).remote_id());
17652 };
17653 }
17654
17655 self.display_map.update(cx, update);
17656
17657 if auto_scroll {
17658 self.request_autoscroll(Autoscroll::fit(), cx);
17659 }
17660
17661 cx.notify();
17662 self.scrollbar_marker_state.dirty = true;
17663 self.active_indent_guides_state.dirty = true;
17664 }
17665
17666 pub fn update_renderer_widths(
17667 &mut self,
17668 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17669 cx: &mut Context<Self>,
17670 ) -> bool {
17671 self.display_map
17672 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17673 }
17674
17675 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17676 self.display_map.read(cx).fold_placeholder.clone()
17677 }
17678
17679 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17680 self.buffer.update(cx, |buffer, cx| {
17681 buffer.set_all_diff_hunks_expanded(cx);
17682 });
17683 }
17684
17685 pub fn expand_all_diff_hunks(
17686 &mut self,
17687 _: &ExpandAllDiffHunks,
17688 _window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 self.buffer.update(cx, |buffer, cx| {
17692 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17693 });
17694 }
17695
17696 pub fn toggle_selected_diff_hunks(
17697 &mut self,
17698 _: &ToggleSelectedDiffHunks,
17699 _window: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17703 self.toggle_diff_hunks_in_ranges(ranges, cx);
17704 }
17705
17706 pub fn diff_hunks_in_ranges<'a>(
17707 &'a self,
17708 ranges: &'a [Range<Anchor>],
17709 buffer: &'a MultiBufferSnapshot,
17710 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17711 ranges.iter().flat_map(move |range| {
17712 let end_excerpt_id = range.end.excerpt_id;
17713 let range = range.to_point(buffer);
17714 let mut peek_end = range.end;
17715 if range.end.row < buffer.max_row().0 {
17716 peek_end = Point::new(range.end.row + 1, 0);
17717 }
17718 buffer
17719 .diff_hunks_in_range(range.start..peek_end)
17720 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17721 })
17722 }
17723
17724 pub fn has_stageable_diff_hunks_in_ranges(
17725 &self,
17726 ranges: &[Range<Anchor>],
17727 snapshot: &MultiBufferSnapshot,
17728 ) -> bool {
17729 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17730 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17731 }
17732
17733 pub fn toggle_staged_selected_diff_hunks(
17734 &mut self,
17735 _: &::git::ToggleStaged,
17736 _: &mut Window,
17737 cx: &mut Context<Self>,
17738 ) {
17739 let snapshot = self.buffer.read(cx).snapshot(cx);
17740 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17741 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17742 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17743 }
17744
17745 pub fn set_render_diff_hunk_controls(
17746 &mut self,
17747 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17748 cx: &mut Context<Self>,
17749 ) {
17750 self.render_diff_hunk_controls = render_diff_hunk_controls;
17751 cx.notify();
17752 }
17753
17754 pub fn stage_and_next(
17755 &mut self,
17756 _: &::git::StageAndNext,
17757 window: &mut Window,
17758 cx: &mut Context<Self>,
17759 ) {
17760 self.do_stage_or_unstage_and_next(true, window, cx);
17761 }
17762
17763 pub fn unstage_and_next(
17764 &mut self,
17765 _: &::git::UnstageAndNext,
17766 window: &mut Window,
17767 cx: &mut Context<Self>,
17768 ) {
17769 self.do_stage_or_unstage_and_next(false, window, cx);
17770 }
17771
17772 pub fn stage_or_unstage_diff_hunks(
17773 &mut self,
17774 stage: bool,
17775 ranges: Vec<Range<Anchor>>,
17776 cx: &mut Context<Self>,
17777 ) {
17778 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17779 cx.spawn(async move |this, cx| {
17780 task.await?;
17781 this.update(cx, |this, cx| {
17782 let snapshot = this.buffer.read(cx).snapshot(cx);
17783 let chunk_by = this
17784 .diff_hunks_in_ranges(&ranges, &snapshot)
17785 .chunk_by(|hunk| hunk.buffer_id);
17786 for (buffer_id, hunks) in &chunk_by {
17787 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17788 }
17789 })
17790 })
17791 .detach_and_log_err(cx);
17792 }
17793
17794 fn save_buffers_for_ranges_if_needed(
17795 &mut self,
17796 ranges: &[Range<Anchor>],
17797 cx: &mut Context<Editor>,
17798 ) -> Task<Result<()>> {
17799 let multibuffer = self.buffer.read(cx);
17800 let snapshot = multibuffer.read(cx);
17801 let buffer_ids: HashSet<_> = ranges
17802 .iter()
17803 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17804 .collect();
17805 drop(snapshot);
17806
17807 let mut buffers = HashSet::default();
17808 for buffer_id in buffer_ids {
17809 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17810 let buffer = buffer_entity.read(cx);
17811 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17812 {
17813 buffers.insert(buffer_entity);
17814 }
17815 }
17816 }
17817
17818 if let Some(project) = &self.project {
17819 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17820 } else {
17821 Task::ready(Ok(()))
17822 }
17823 }
17824
17825 fn do_stage_or_unstage_and_next(
17826 &mut self,
17827 stage: bool,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17832
17833 if ranges.iter().any(|range| range.start != range.end) {
17834 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17835 return;
17836 }
17837
17838 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17839 let snapshot = self.snapshot(window, cx);
17840 let position = self.selections.newest::<Point>(cx).head();
17841 let mut row = snapshot
17842 .buffer_snapshot
17843 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17844 .find(|hunk| hunk.row_range.start.0 > position.row)
17845 .map(|hunk| hunk.row_range.start);
17846
17847 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17848 // Outside of the project diff editor, wrap around to the beginning.
17849 if !all_diff_hunks_expanded {
17850 row = row.or_else(|| {
17851 snapshot
17852 .buffer_snapshot
17853 .diff_hunks_in_range(Point::zero()..position)
17854 .find(|hunk| hunk.row_range.end.0 < position.row)
17855 .map(|hunk| hunk.row_range.start)
17856 });
17857 }
17858
17859 if let Some(row) = row {
17860 let destination = Point::new(row.0, 0);
17861 let autoscroll = Autoscroll::center();
17862
17863 self.unfold_ranges(&[destination..destination], false, false, cx);
17864 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17865 s.select_ranges([destination..destination]);
17866 });
17867 }
17868 }
17869
17870 fn do_stage_or_unstage(
17871 &self,
17872 stage: bool,
17873 buffer_id: BufferId,
17874 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17875 cx: &mut App,
17876 ) -> Option<()> {
17877 let project = self.project.as_ref()?;
17878 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17879 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17880 let buffer_snapshot = buffer.read(cx).snapshot();
17881 let file_exists = buffer_snapshot
17882 .file()
17883 .is_some_and(|file| file.disk_state().exists());
17884 diff.update(cx, |diff, cx| {
17885 diff.stage_or_unstage_hunks(
17886 stage,
17887 &hunks
17888 .map(|hunk| buffer_diff::DiffHunk {
17889 buffer_range: hunk.buffer_range,
17890 diff_base_byte_range: hunk.diff_base_byte_range,
17891 secondary_status: hunk.secondary_status,
17892 range: Point::zero()..Point::zero(), // unused
17893 })
17894 .collect::<Vec<_>>(),
17895 &buffer_snapshot,
17896 file_exists,
17897 cx,
17898 )
17899 });
17900 None
17901 }
17902
17903 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17904 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17905 self.buffer
17906 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17907 }
17908
17909 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17910 self.buffer.update(cx, |buffer, cx| {
17911 let ranges = vec![Anchor::min()..Anchor::max()];
17912 if !buffer.all_diff_hunks_expanded()
17913 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17914 {
17915 buffer.collapse_diff_hunks(ranges, cx);
17916 true
17917 } else {
17918 false
17919 }
17920 })
17921 }
17922
17923 fn toggle_diff_hunks_in_ranges(
17924 &mut self,
17925 ranges: Vec<Range<Anchor>>,
17926 cx: &mut Context<Editor>,
17927 ) {
17928 self.buffer.update(cx, |buffer, cx| {
17929 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17930 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17931 })
17932 }
17933
17934 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17935 self.buffer.update(cx, |buffer, cx| {
17936 let snapshot = buffer.snapshot(cx);
17937 let excerpt_id = range.end.excerpt_id;
17938 let point_range = range.to_point(&snapshot);
17939 let expand = !buffer.single_hunk_is_expanded(range, cx);
17940 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17941 })
17942 }
17943
17944 pub(crate) fn apply_all_diff_hunks(
17945 &mut self,
17946 _: &ApplyAllDiffHunks,
17947 window: &mut Window,
17948 cx: &mut Context<Self>,
17949 ) {
17950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17951
17952 let buffers = self.buffer.read(cx).all_buffers();
17953 for branch_buffer in buffers {
17954 branch_buffer.update(cx, |branch_buffer, cx| {
17955 branch_buffer.merge_into_base(Vec::new(), cx);
17956 });
17957 }
17958
17959 if let Some(project) = self.project.clone() {
17960 self.save(
17961 SaveOptions {
17962 format: true,
17963 autosave: false,
17964 },
17965 project,
17966 window,
17967 cx,
17968 )
17969 .detach_and_log_err(cx);
17970 }
17971 }
17972
17973 pub(crate) fn apply_selected_diff_hunks(
17974 &mut self,
17975 _: &ApplyDiffHunk,
17976 window: &mut Window,
17977 cx: &mut Context<Self>,
17978 ) {
17979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17980 let snapshot = self.snapshot(window, cx);
17981 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17982 let mut ranges_by_buffer = HashMap::default();
17983 self.transact(window, cx, |editor, _window, cx| {
17984 for hunk in hunks {
17985 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17986 ranges_by_buffer
17987 .entry(buffer.clone())
17988 .or_insert_with(Vec::new)
17989 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17990 }
17991 }
17992
17993 for (buffer, ranges) in ranges_by_buffer {
17994 buffer.update(cx, |buffer, cx| {
17995 buffer.merge_into_base(ranges, cx);
17996 });
17997 }
17998 });
17999
18000 if let Some(project) = self.project.clone() {
18001 self.save(
18002 SaveOptions {
18003 format: true,
18004 autosave: false,
18005 },
18006 project,
18007 window,
18008 cx,
18009 )
18010 .detach_and_log_err(cx);
18011 }
18012 }
18013
18014 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18015 if hovered != self.gutter_hovered {
18016 self.gutter_hovered = hovered;
18017 cx.notify();
18018 }
18019 }
18020
18021 pub fn insert_blocks(
18022 &mut self,
18023 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18024 autoscroll: Option<Autoscroll>,
18025 cx: &mut Context<Self>,
18026 ) -> Vec<CustomBlockId> {
18027 let blocks = self
18028 .display_map
18029 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18030 if let Some(autoscroll) = autoscroll {
18031 self.request_autoscroll(autoscroll, cx);
18032 }
18033 cx.notify();
18034 blocks
18035 }
18036
18037 pub fn resize_blocks(
18038 &mut self,
18039 heights: HashMap<CustomBlockId, u32>,
18040 autoscroll: Option<Autoscroll>,
18041 cx: &mut Context<Self>,
18042 ) {
18043 self.display_map
18044 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18045 if let Some(autoscroll) = autoscroll {
18046 self.request_autoscroll(autoscroll, cx);
18047 }
18048 cx.notify();
18049 }
18050
18051 pub fn replace_blocks(
18052 &mut self,
18053 renderers: HashMap<CustomBlockId, RenderBlock>,
18054 autoscroll: Option<Autoscroll>,
18055 cx: &mut Context<Self>,
18056 ) {
18057 self.display_map
18058 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18059 if let Some(autoscroll) = autoscroll {
18060 self.request_autoscroll(autoscroll, cx);
18061 }
18062 cx.notify();
18063 }
18064
18065 pub fn remove_blocks(
18066 &mut self,
18067 block_ids: HashSet<CustomBlockId>,
18068 autoscroll: Option<Autoscroll>,
18069 cx: &mut Context<Self>,
18070 ) {
18071 self.display_map.update(cx, |display_map, cx| {
18072 display_map.remove_blocks(block_ids, cx)
18073 });
18074 if let Some(autoscroll) = autoscroll {
18075 self.request_autoscroll(autoscroll, cx);
18076 }
18077 cx.notify();
18078 }
18079
18080 pub fn row_for_block(
18081 &self,
18082 block_id: CustomBlockId,
18083 cx: &mut Context<Self>,
18084 ) -> Option<DisplayRow> {
18085 self.display_map
18086 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18087 }
18088
18089 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18090 self.focused_block = Some(focused_block);
18091 }
18092
18093 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18094 self.focused_block.take()
18095 }
18096
18097 pub fn insert_creases(
18098 &mut self,
18099 creases: impl IntoIterator<Item = Crease<Anchor>>,
18100 cx: &mut Context<Self>,
18101 ) -> Vec<CreaseId> {
18102 self.display_map
18103 .update(cx, |map, cx| map.insert_creases(creases, cx))
18104 }
18105
18106 pub fn remove_creases(
18107 &mut self,
18108 ids: impl IntoIterator<Item = CreaseId>,
18109 cx: &mut Context<Self>,
18110 ) -> Vec<(CreaseId, Range<Anchor>)> {
18111 self.display_map
18112 .update(cx, |map, cx| map.remove_creases(ids, cx))
18113 }
18114
18115 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18116 self.display_map
18117 .update(cx, |map, cx| map.snapshot(cx))
18118 .longest_row()
18119 }
18120
18121 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18122 self.display_map
18123 .update(cx, |map, cx| map.snapshot(cx))
18124 .max_point()
18125 }
18126
18127 pub fn text(&self, cx: &App) -> String {
18128 self.buffer.read(cx).read(cx).text()
18129 }
18130
18131 pub fn is_empty(&self, cx: &App) -> bool {
18132 self.buffer.read(cx).read(cx).is_empty()
18133 }
18134
18135 pub fn text_option(&self, cx: &App) -> Option<String> {
18136 let text = self.text(cx);
18137 let text = text.trim();
18138
18139 if text.is_empty() {
18140 return None;
18141 }
18142
18143 Some(text.to_string())
18144 }
18145
18146 pub fn set_text(
18147 &mut self,
18148 text: impl Into<Arc<str>>,
18149 window: &mut Window,
18150 cx: &mut Context<Self>,
18151 ) {
18152 self.transact(window, cx, |this, _, cx| {
18153 this.buffer
18154 .read(cx)
18155 .as_singleton()
18156 .expect("you can only call set_text on editors for singleton buffers")
18157 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18158 });
18159 }
18160
18161 pub fn display_text(&self, cx: &mut App) -> String {
18162 self.display_map
18163 .update(cx, |map, cx| map.snapshot(cx))
18164 .text()
18165 }
18166
18167 fn create_minimap(
18168 &self,
18169 minimap_settings: MinimapSettings,
18170 window: &mut Window,
18171 cx: &mut Context<Self>,
18172 ) -> Option<Entity<Self>> {
18173 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18174 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18175 }
18176
18177 fn initialize_new_minimap(
18178 &self,
18179 minimap_settings: MinimapSettings,
18180 window: &mut Window,
18181 cx: &mut Context<Self>,
18182 ) -> Entity<Self> {
18183 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18184
18185 let mut minimap = Editor::new_internal(
18186 EditorMode::Minimap {
18187 parent: cx.weak_entity(),
18188 },
18189 self.buffer.clone(),
18190 None,
18191 Some(self.display_map.clone()),
18192 window,
18193 cx,
18194 );
18195 minimap.scroll_manager.clone_state(&self.scroll_manager);
18196 minimap.set_text_style_refinement(TextStyleRefinement {
18197 font_size: Some(MINIMAP_FONT_SIZE),
18198 font_weight: Some(MINIMAP_FONT_WEIGHT),
18199 ..Default::default()
18200 });
18201 minimap.update_minimap_configuration(minimap_settings, cx);
18202 cx.new(|_| minimap)
18203 }
18204
18205 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18206 let current_line_highlight = minimap_settings
18207 .current_line_highlight
18208 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18209 self.set_current_line_highlight(Some(current_line_highlight));
18210 }
18211
18212 pub fn minimap(&self) -> Option<&Entity<Self>> {
18213 self.minimap
18214 .as_ref()
18215 .filter(|_| self.minimap_visibility.visible())
18216 }
18217
18218 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18219 let mut wrap_guides = smallvec![];
18220
18221 if self.show_wrap_guides == Some(false) {
18222 return wrap_guides;
18223 }
18224
18225 let settings = self.buffer.read(cx).language_settings(cx);
18226 if settings.show_wrap_guides {
18227 match self.soft_wrap_mode(cx) {
18228 SoftWrap::Column(soft_wrap) => {
18229 wrap_guides.push((soft_wrap as usize, true));
18230 }
18231 SoftWrap::Bounded(soft_wrap) => {
18232 wrap_guides.push((soft_wrap as usize, true));
18233 }
18234 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18235 }
18236 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18237 }
18238
18239 wrap_guides
18240 }
18241
18242 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18243 let settings = self.buffer.read(cx).language_settings(cx);
18244 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18245 match mode {
18246 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18247 SoftWrap::None
18248 }
18249 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18250 language_settings::SoftWrap::PreferredLineLength => {
18251 SoftWrap::Column(settings.preferred_line_length)
18252 }
18253 language_settings::SoftWrap::Bounded => {
18254 SoftWrap::Bounded(settings.preferred_line_length)
18255 }
18256 }
18257 }
18258
18259 pub fn set_soft_wrap_mode(
18260 &mut self,
18261 mode: language_settings::SoftWrap,
18262
18263 cx: &mut Context<Self>,
18264 ) {
18265 self.soft_wrap_mode_override = Some(mode);
18266 cx.notify();
18267 }
18268
18269 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18270 self.hard_wrap = hard_wrap;
18271 cx.notify();
18272 }
18273
18274 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18275 self.text_style_refinement = Some(style);
18276 }
18277
18278 /// called by the Element so we know what style we were most recently rendered with.
18279 pub(crate) fn set_style(
18280 &mut self,
18281 style: EditorStyle,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) {
18285 // We intentionally do not inform the display map about the minimap style
18286 // so that wrapping is not recalculated and stays consistent for the editor
18287 // and its linked minimap.
18288 if !self.mode.is_minimap() {
18289 let rem_size = window.rem_size();
18290 self.display_map.update(cx, |map, cx| {
18291 map.set_font(
18292 style.text.font(),
18293 style.text.font_size.to_pixels(rem_size),
18294 cx,
18295 )
18296 });
18297 }
18298 self.style = Some(style);
18299 }
18300
18301 pub fn style(&self) -> Option<&EditorStyle> {
18302 self.style.as_ref()
18303 }
18304
18305 // Called by the element. This method is not designed to be called outside of the editor
18306 // element's layout code because it does not notify when rewrapping is computed synchronously.
18307 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18308 self.display_map
18309 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18310 }
18311
18312 pub fn set_soft_wrap(&mut self) {
18313 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18314 }
18315
18316 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18317 if self.soft_wrap_mode_override.is_some() {
18318 self.soft_wrap_mode_override.take();
18319 } else {
18320 let soft_wrap = match self.soft_wrap_mode(cx) {
18321 SoftWrap::GitDiff => return,
18322 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18323 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18324 language_settings::SoftWrap::None
18325 }
18326 };
18327 self.soft_wrap_mode_override = Some(soft_wrap);
18328 }
18329 cx.notify();
18330 }
18331
18332 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18333 let Some(workspace) = self.workspace() else {
18334 return;
18335 };
18336 let fs = workspace.read(cx).app_state().fs.clone();
18337 let current_show = TabBarSettings::get_global(cx).show;
18338 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18339 setting.show = Some(!current_show);
18340 });
18341 }
18342
18343 pub fn toggle_indent_guides(
18344 &mut self,
18345 _: &ToggleIndentGuides,
18346 _: &mut Window,
18347 cx: &mut Context<Self>,
18348 ) {
18349 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18350 self.buffer
18351 .read(cx)
18352 .language_settings(cx)
18353 .indent_guides
18354 .enabled
18355 });
18356 self.show_indent_guides = Some(!currently_enabled);
18357 cx.notify();
18358 }
18359
18360 fn should_show_indent_guides(&self) -> Option<bool> {
18361 self.show_indent_guides
18362 }
18363
18364 pub fn toggle_line_numbers(
18365 &mut self,
18366 _: &ToggleLineNumbers,
18367 _: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) {
18370 let mut editor_settings = EditorSettings::get_global(cx).clone();
18371 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18372 EditorSettings::override_global(editor_settings, cx);
18373 }
18374
18375 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18376 if let Some(show_line_numbers) = self.show_line_numbers {
18377 return show_line_numbers;
18378 }
18379 EditorSettings::get_global(cx).gutter.line_numbers
18380 }
18381
18382 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18383 self.use_relative_line_numbers
18384 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18385 }
18386
18387 pub fn toggle_relative_line_numbers(
18388 &mut self,
18389 _: &ToggleRelativeLineNumbers,
18390 _: &mut Window,
18391 cx: &mut Context<Self>,
18392 ) {
18393 let is_relative = self.should_use_relative_line_numbers(cx);
18394 self.set_relative_line_number(Some(!is_relative), cx)
18395 }
18396
18397 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18398 self.use_relative_line_numbers = is_relative;
18399 cx.notify();
18400 }
18401
18402 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18403 self.show_gutter = show_gutter;
18404 cx.notify();
18405 }
18406
18407 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18408 self.show_scrollbars = ScrollbarAxes {
18409 horizontal: show,
18410 vertical: show,
18411 };
18412 cx.notify();
18413 }
18414
18415 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18416 self.show_scrollbars.vertical = show;
18417 cx.notify();
18418 }
18419
18420 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18421 self.show_scrollbars.horizontal = show;
18422 cx.notify();
18423 }
18424
18425 pub fn set_minimap_visibility(
18426 &mut self,
18427 minimap_visibility: MinimapVisibility,
18428 window: &mut Window,
18429 cx: &mut Context<Self>,
18430 ) {
18431 if self.minimap_visibility != minimap_visibility {
18432 if minimap_visibility.visible() && self.minimap.is_none() {
18433 let minimap_settings = EditorSettings::get_global(cx).minimap;
18434 self.minimap =
18435 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18436 }
18437 self.minimap_visibility = minimap_visibility;
18438 cx.notify();
18439 }
18440 }
18441
18442 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18443 self.set_show_scrollbars(false, cx);
18444 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18445 }
18446
18447 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18448 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18449 }
18450
18451 /// Normally the text in full mode and auto height editors is padded on the
18452 /// left side by roughly half a character width for improved hit testing.
18453 ///
18454 /// Use this method to disable this for cases where this is not wanted (e.g.
18455 /// if you want to align the editor text with some other text above or below)
18456 /// or if you want to add this padding to single-line editors.
18457 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18458 self.offset_content = offset_content;
18459 cx.notify();
18460 }
18461
18462 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18463 self.show_line_numbers = Some(show_line_numbers);
18464 cx.notify();
18465 }
18466
18467 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18468 self.disable_expand_excerpt_buttons = true;
18469 cx.notify();
18470 }
18471
18472 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18473 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18474 cx.notify();
18475 }
18476
18477 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18478 self.show_code_actions = Some(show_code_actions);
18479 cx.notify();
18480 }
18481
18482 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18483 self.show_runnables = Some(show_runnables);
18484 cx.notify();
18485 }
18486
18487 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18488 self.show_breakpoints = Some(show_breakpoints);
18489 cx.notify();
18490 }
18491
18492 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18493 if self.display_map.read(cx).masked != masked {
18494 self.display_map.update(cx, |map, _| map.masked = masked);
18495 }
18496 cx.notify()
18497 }
18498
18499 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18500 self.show_wrap_guides = Some(show_wrap_guides);
18501 cx.notify();
18502 }
18503
18504 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18505 self.show_indent_guides = Some(show_indent_guides);
18506 cx.notify();
18507 }
18508
18509 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18510 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18511 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18512 if let Some(dir) = file.abs_path(cx).parent() {
18513 return Some(dir.to_owned());
18514 }
18515 }
18516
18517 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18518 return Some(project_path.path.to_path_buf());
18519 }
18520 }
18521
18522 None
18523 }
18524
18525 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18526 self.active_excerpt(cx)?
18527 .1
18528 .read(cx)
18529 .file()
18530 .and_then(|f| f.as_local())
18531 }
18532
18533 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18534 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18535 let buffer = buffer.read(cx);
18536 if let Some(project_path) = buffer.project_path(cx) {
18537 let project = self.project.as_ref()?.read(cx);
18538 project.absolute_path(&project_path, cx)
18539 } else {
18540 buffer
18541 .file()
18542 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18543 }
18544 })
18545 }
18546
18547 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18548 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18549 let project_path = buffer.read(cx).project_path(cx)?;
18550 let project = self.project.as_ref()?.read(cx);
18551 let entry = project.entry_for_path(&project_path, cx)?;
18552 let path = entry.path.to_path_buf();
18553 Some(path)
18554 })
18555 }
18556
18557 pub fn reveal_in_finder(
18558 &mut self,
18559 _: &RevealInFileManager,
18560 _window: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 if let Some(target) = self.target_file(cx) {
18564 cx.reveal_path(&target.abs_path(cx));
18565 }
18566 }
18567
18568 pub fn copy_path(
18569 &mut self,
18570 _: &zed_actions::workspace::CopyPath,
18571 _window: &mut Window,
18572 cx: &mut Context<Self>,
18573 ) {
18574 if let Some(path) = self.target_file_abs_path(cx) {
18575 if let Some(path) = path.to_str() {
18576 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18577 }
18578 }
18579 }
18580
18581 pub fn copy_relative_path(
18582 &mut self,
18583 _: &zed_actions::workspace::CopyRelativePath,
18584 _window: &mut Window,
18585 cx: &mut Context<Self>,
18586 ) {
18587 if let Some(path) = self.target_file_path(cx) {
18588 if let Some(path) = path.to_str() {
18589 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18590 }
18591 }
18592 }
18593
18594 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18595 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18596 buffer.read(cx).project_path(cx)
18597 } else {
18598 None
18599 }
18600 }
18601
18602 // Returns true if the editor handled a go-to-line request
18603 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18604 maybe!({
18605 let breakpoint_store = self.breakpoint_store.as_ref()?;
18606
18607 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18608 else {
18609 self.clear_row_highlights::<ActiveDebugLine>();
18610 return None;
18611 };
18612
18613 let position = active_stack_frame.position;
18614 let buffer_id = position.buffer_id?;
18615 let snapshot = self
18616 .project
18617 .as_ref()?
18618 .read(cx)
18619 .buffer_for_id(buffer_id, cx)?
18620 .read(cx)
18621 .snapshot();
18622
18623 let mut handled = false;
18624 for (id, ExcerptRange { context, .. }) in
18625 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18626 {
18627 if context.start.cmp(&position, &snapshot).is_ge()
18628 || context.end.cmp(&position, &snapshot).is_lt()
18629 {
18630 continue;
18631 }
18632 let snapshot = self.buffer.read(cx).snapshot(cx);
18633 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18634
18635 handled = true;
18636 self.clear_row_highlights::<ActiveDebugLine>();
18637
18638 self.go_to_line::<ActiveDebugLine>(
18639 multibuffer_anchor,
18640 Some(cx.theme().colors().editor_debugger_active_line_background),
18641 window,
18642 cx,
18643 );
18644
18645 cx.notify();
18646 }
18647
18648 handled.then_some(())
18649 })
18650 .is_some()
18651 }
18652
18653 pub fn copy_file_name_without_extension(
18654 &mut self,
18655 _: &CopyFileNameWithoutExtension,
18656 _: &mut Window,
18657 cx: &mut Context<Self>,
18658 ) {
18659 if let Some(file) = self.target_file(cx) {
18660 if let Some(file_stem) = file.path().file_stem() {
18661 if let Some(name) = file_stem.to_str() {
18662 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18663 }
18664 }
18665 }
18666 }
18667
18668 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18669 if let Some(file) = self.target_file(cx) {
18670 if let Some(file_name) = file.path().file_name() {
18671 if let Some(name) = file_name.to_str() {
18672 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18673 }
18674 }
18675 }
18676 }
18677
18678 pub fn toggle_git_blame(
18679 &mut self,
18680 _: &::git::Blame,
18681 window: &mut Window,
18682 cx: &mut Context<Self>,
18683 ) {
18684 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18685
18686 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18687 self.start_git_blame(true, window, cx);
18688 }
18689
18690 cx.notify();
18691 }
18692
18693 pub fn toggle_git_blame_inline(
18694 &mut self,
18695 _: &ToggleGitBlameInline,
18696 window: &mut Window,
18697 cx: &mut Context<Self>,
18698 ) {
18699 self.toggle_git_blame_inline_internal(true, window, cx);
18700 cx.notify();
18701 }
18702
18703 pub fn open_git_blame_commit(
18704 &mut self,
18705 _: &OpenGitBlameCommit,
18706 window: &mut Window,
18707 cx: &mut Context<Self>,
18708 ) {
18709 self.open_git_blame_commit_internal(window, cx);
18710 }
18711
18712 fn open_git_blame_commit_internal(
18713 &mut self,
18714 window: &mut Window,
18715 cx: &mut Context<Self>,
18716 ) -> Option<()> {
18717 let blame = self.blame.as_ref()?;
18718 let snapshot = self.snapshot(window, cx);
18719 let cursor = self.selections.newest::<Point>(cx).head();
18720 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18721 let blame_entry = blame
18722 .update(cx, |blame, cx| {
18723 blame
18724 .blame_for_rows(
18725 &[RowInfo {
18726 buffer_id: Some(buffer.remote_id()),
18727 buffer_row: Some(point.row),
18728 ..Default::default()
18729 }],
18730 cx,
18731 )
18732 .next()
18733 })
18734 .flatten()?;
18735 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18736 let repo = blame.read(cx).repository(cx)?;
18737 let workspace = self.workspace()?.downgrade();
18738 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18739 None
18740 }
18741
18742 pub fn git_blame_inline_enabled(&self) -> bool {
18743 self.git_blame_inline_enabled
18744 }
18745
18746 pub fn toggle_selection_menu(
18747 &mut self,
18748 _: &ToggleSelectionMenu,
18749 _: &mut Window,
18750 cx: &mut Context<Self>,
18751 ) {
18752 self.show_selection_menu = self
18753 .show_selection_menu
18754 .map(|show_selections_menu| !show_selections_menu)
18755 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18756
18757 cx.notify();
18758 }
18759
18760 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18761 self.show_selection_menu
18762 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18763 }
18764
18765 fn start_git_blame(
18766 &mut self,
18767 user_triggered: bool,
18768 window: &mut Window,
18769 cx: &mut Context<Self>,
18770 ) {
18771 if let Some(project) = self.project.as_ref() {
18772 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18773 return;
18774 };
18775
18776 if buffer.read(cx).file().is_none() {
18777 return;
18778 }
18779
18780 let focused = self.focus_handle(cx).contains_focused(window, cx);
18781
18782 let project = project.clone();
18783 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18784 self.blame_subscription =
18785 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18786 self.blame = Some(blame);
18787 }
18788 }
18789
18790 fn toggle_git_blame_inline_internal(
18791 &mut self,
18792 user_triggered: bool,
18793 window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) {
18796 if self.git_blame_inline_enabled {
18797 self.git_blame_inline_enabled = false;
18798 self.show_git_blame_inline = false;
18799 self.show_git_blame_inline_delay_task.take();
18800 } else {
18801 self.git_blame_inline_enabled = true;
18802 self.start_git_blame_inline(user_triggered, window, cx);
18803 }
18804
18805 cx.notify();
18806 }
18807
18808 fn start_git_blame_inline(
18809 &mut self,
18810 user_triggered: bool,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 self.start_git_blame(user_triggered, window, cx);
18815
18816 if ProjectSettings::get_global(cx)
18817 .git
18818 .inline_blame_delay()
18819 .is_some()
18820 {
18821 self.start_inline_blame_timer(window, cx);
18822 } else {
18823 self.show_git_blame_inline = true
18824 }
18825 }
18826
18827 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18828 self.blame.as_ref()
18829 }
18830
18831 pub fn show_git_blame_gutter(&self) -> bool {
18832 self.show_git_blame_gutter
18833 }
18834
18835 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18836 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18837 }
18838
18839 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18840 self.show_git_blame_inline
18841 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18842 && !self.newest_selection_head_on_empty_line(cx)
18843 && self.has_blame_entries(cx)
18844 }
18845
18846 fn has_blame_entries(&self, cx: &App) -> bool {
18847 self.blame()
18848 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18849 }
18850
18851 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18852 let cursor_anchor = self.selections.newest_anchor().head();
18853
18854 let snapshot = self.buffer.read(cx).snapshot(cx);
18855 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18856
18857 snapshot.line_len(buffer_row) == 0
18858 }
18859
18860 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18861 let buffer_and_selection = maybe!({
18862 let selection = self.selections.newest::<Point>(cx);
18863 let selection_range = selection.range();
18864
18865 let multi_buffer = self.buffer().read(cx);
18866 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18867 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18868
18869 let (buffer, range, _) = if selection.reversed {
18870 buffer_ranges.first()
18871 } else {
18872 buffer_ranges.last()
18873 }?;
18874
18875 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18876 ..text::ToPoint::to_point(&range.end, &buffer).row;
18877 Some((
18878 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18879 selection,
18880 ))
18881 });
18882
18883 let Some((buffer, selection)) = buffer_and_selection else {
18884 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18885 };
18886
18887 let Some(project) = self.project.as_ref() else {
18888 return Task::ready(Err(anyhow!("editor does not have project")));
18889 };
18890
18891 project.update(cx, |project, cx| {
18892 project.get_permalink_to_line(&buffer, selection, cx)
18893 })
18894 }
18895
18896 pub fn copy_permalink_to_line(
18897 &mut self,
18898 _: &CopyPermalinkToLine,
18899 window: &mut Window,
18900 cx: &mut Context<Self>,
18901 ) {
18902 let permalink_task = self.get_permalink_to_line(cx);
18903 let workspace = self.workspace();
18904
18905 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18906 Ok(permalink) => {
18907 cx.update(|_, cx| {
18908 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18909 })
18910 .ok();
18911 }
18912 Err(err) => {
18913 let message = format!("Failed to copy permalink: {err}");
18914
18915 anyhow::Result::<()>::Err(err).log_err();
18916
18917 if let Some(workspace) = workspace {
18918 workspace
18919 .update_in(cx, |workspace, _, cx| {
18920 struct CopyPermalinkToLine;
18921
18922 workspace.show_toast(
18923 Toast::new(
18924 NotificationId::unique::<CopyPermalinkToLine>(),
18925 message,
18926 ),
18927 cx,
18928 )
18929 })
18930 .ok();
18931 }
18932 }
18933 })
18934 .detach();
18935 }
18936
18937 pub fn copy_file_location(
18938 &mut self,
18939 _: &CopyFileLocation,
18940 _: &mut Window,
18941 cx: &mut Context<Self>,
18942 ) {
18943 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18944 if let Some(file) = self.target_file(cx) {
18945 if let Some(path) = file.path().to_str() {
18946 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18947 }
18948 }
18949 }
18950
18951 pub fn open_permalink_to_line(
18952 &mut self,
18953 _: &OpenPermalinkToLine,
18954 window: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) {
18957 let permalink_task = self.get_permalink_to_line(cx);
18958 let workspace = self.workspace();
18959
18960 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18961 Ok(permalink) => {
18962 cx.update(|_, cx| {
18963 cx.open_url(permalink.as_ref());
18964 })
18965 .ok();
18966 }
18967 Err(err) => {
18968 let message = format!("Failed to open permalink: {err}");
18969
18970 anyhow::Result::<()>::Err(err).log_err();
18971
18972 if let Some(workspace) = workspace {
18973 workspace
18974 .update(cx, |workspace, cx| {
18975 struct OpenPermalinkToLine;
18976
18977 workspace.show_toast(
18978 Toast::new(
18979 NotificationId::unique::<OpenPermalinkToLine>(),
18980 message,
18981 ),
18982 cx,
18983 )
18984 })
18985 .ok();
18986 }
18987 }
18988 })
18989 .detach();
18990 }
18991
18992 pub fn insert_uuid_v4(
18993 &mut self,
18994 _: &InsertUuidV4,
18995 window: &mut Window,
18996 cx: &mut Context<Self>,
18997 ) {
18998 self.insert_uuid(UuidVersion::V4, window, cx);
18999 }
19000
19001 pub fn insert_uuid_v7(
19002 &mut self,
19003 _: &InsertUuidV7,
19004 window: &mut Window,
19005 cx: &mut Context<Self>,
19006 ) {
19007 self.insert_uuid(UuidVersion::V7, window, cx);
19008 }
19009
19010 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19012 self.transact(window, cx, |this, window, cx| {
19013 let edits = this
19014 .selections
19015 .all::<Point>(cx)
19016 .into_iter()
19017 .map(|selection| {
19018 let uuid = match version {
19019 UuidVersion::V4 => uuid::Uuid::new_v4(),
19020 UuidVersion::V7 => uuid::Uuid::now_v7(),
19021 };
19022
19023 (selection.range(), uuid.to_string())
19024 });
19025 this.edit(edits, cx);
19026 this.refresh_inline_completion(true, false, window, cx);
19027 });
19028 }
19029
19030 pub fn open_selections_in_multibuffer(
19031 &mut self,
19032 _: &OpenSelectionsInMultibuffer,
19033 window: &mut Window,
19034 cx: &mut Context<Self>,
19035 ) {
19036 let multibuffer = self.buffer.read(cx);
19037
19038 let Some(buffer) = multibuffer.as_singleton() else {
19039 return;
19040 };
19041
19042 let Some(workspace) = self.workspace() else {
19043 return;
19044 };
19045
19046 let title = multibuffer.title(cx).to_string();
19047
19048 let locations = self
19049 .selections
19050 .all_anchors(cx)
19051 .into_iter()
19052 .map(|selection| Location {
19053 buffer: buffer.clone(),
19054 range: selection.start.text_anchor..selection.end.text_anchor,
19055 })
19056 .collect::<Vec<_>>();
19057
19058 cx.spawn_in(window, async move |_, cx| {
19059 workspace.update_in(cx, |workspace, window, cx| {
19060 Self::open_locations_in_multibuffer(
19061 workspace,
19062 locations,
19063 format!("Selections for '{title}'"),
19064 false,
19065 MultibufferSelectionMode::All,
19066 window,
19067 cx,
19068 );
19069 })
19070 })
19071 .detach();
19072 }
19073
19074 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19075 /// last highlight added will be used.
19076 ///
19077 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19078 pub fn highlight_rows<T: 'static>(
19079 &mut self,
19080 range: Range<Anchor>,
19081 color: Hsla,
19082 options: RowHighlightOptions,
19083 cx: &mut Context<Self>,
19084 ) {
19085 let snapshot = self.buffer().read(cx).snapshot(cx);
19086 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19087 let ix = row_highlights.binary_search_by(|highlight| {
19088 Ordering::Equal
19089 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19090 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19091 });
19092
19093 if let Err(mut ix) = ix {
19094 let index = post_inc(&mut self.highlight_order);
19095
19096 // If this range intersects with the preceding highlight, then merge it with
19097 // the preceding highlight. Otherwise insert a new highlight.
19098 let mut merged = false;
19099 if ix > 0 {
19100 let prev_highlight = &mut row_highlights[ix - 1];
19101 if prev_highlight
19102 .range
19103 .end
19104 .cmp(&range.start, &snapshot)
19105 .is_ge()
19106 {
19107 ix -= 1;
19108 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19109 prev_highlight.range.end = range.end;
19110 }
19111 merged = true;
19112 prev_highlight.index = index;
19113 prev_highlight.color = color;
19114 prev_highlight.options = options;
19115 }
19116 }
19117
19118 if !merged {
19119 row_highlights.insert(
19120 ix,
19121 RowHighlight {
19122 range: range.clone(),
19123 index,
19124 color,
19125 options,
19126 type_id: TypeId::of::<T>(),
19127 },
19128 );
19129 }
19130
19131 // If any of the following highlights intersect with this one, merge them.
19132 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19133 let highlight = &row_highlights[ix];
19134 if next_highlight
19135 .range
19136 .start
19137 .cmp(&highlight.range.end, &snapshot)
19138 .is_le()
19139 {
19140 if next_highlight
19141 .range
19142 .end
19143 .cmp(&highlight.range.end, &snapshot)
19144 .is_gt()
19145 {
19146 row_highlights[ix].range.end = next_highlight.range.end;
19147 }
19148 row_highlights.remove(ix + 1);
19149 } else {
19150 break;
19151 }
19152 }
19153 }
19154 }
19155
19156 /// Remove any highlighted row ranges of the given type that intersect the
19157 /// given ranges.
19158 pub fn remove_highlighted_rows<T: 'static>(
19159 &mut self,
19160 ranges_to_remove: Vec<Range<Anchor>>,
19161 cx: &mut Context<Self>,
19162 ) {
19163 let snapshot = self.buffer().read(cx).snapshot(cx);
19164 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19165 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19166 row_highlights.retain(|highlight| {
19167 while let Some(range_to_remove) = ranges_to_remove.peek() {
19168 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19169 Ordering::Less | Ordering::Equal => {
19170 ranges_to_remove.next();
19171 }
19172 Ordering::Greater => {
19173 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19174 Ordering::Less | Ordering::Equal => {
19175 return false;
19176 }
19177 Ordering::Greater => break,
19178 }
19179 }
19180 }
19181 }
19182
19183 true
19184 })
19185 }
19186
19187 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19188 pub fn clear_row_highlights<T: 'static>(&mut self) {
19189 self.highlighted_rows.remove(&TypeId::of::<T>());
19190 }
19191
19192 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19193 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19194 self.highlighted_rows
19195 .get(&TypeId::of::<T>())
19196 .map_or(&[] as &[_], |vec| vec.as_slice())
19197 .iter()
19198 .map(|highlight| (highlight.range.clone(), highlight.color))
19199 }
19200
19201 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19202 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19203 /// Allows to ignore certain kinds of highlights.
19204 pub fn highlighted_display_rows(
19205 &self,
19206 window: &mut Window,
19207 cx: &mut App,
19208 ) -> BTreeMap<DisplayRow, LineHighlight> {
19209 let snapshot = self.snapshot(window, cx);
19210 let mut used_highlight_orders = HashMap::default();
19211 self.highlighted_rows
19212 .iter()
19213 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19214 .fold(
19215 BTreeMap::<DisplayRow, LineHighlight>::new(),
19216 |mut unique_rows, highlight| {
19217 let start = highlight.range.start.to_display_point(&snapshot);
19218 let end = highlight.range.end.to_display_point(&snapshot);
19219 let start_row = start.row().0;
19220 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19221 && end.column() == 0
19222 {
19223 end.row().0.saturating_sub(1)
19224 } else {
19225 end.row().0
19226 };
19227 for row in start_row..=end_row {
19228 let used_index =
19229 used_highlight_orders.entry(row).or_insert(highlight.index);
19230 if highlight.index >= *used_index {
19231 *used_index = highlight.index;
19232 unique_rows.insert(
19233 DisplayRow(row),
19234 LineHighlight {
19235 include_gutter: highlight.options.include_gutter,
19236 border: None,
19237 background: highlight.color.into(),
19238 type_id: Some(highlight.type_id),
19239 },
19240 );
19241 }
19242 }
19243 unique_rows
19244 },
19245 )
19246 }
19247
19248 pub fn highlighted_display_row_for_autoscroll(
19249 &self,
19250 snapshot: &DisplaySnapshot,
19251 ) -> Option<DisplayRow> {
19252 self.highlighted_rows
19253 .values()
19254 .flat_map(|highlighted_rows| highlighted_rows.iter())
19255 .filter_map(|highlight| {
19256 if highlight.options.autoscroll {
19257 Some(highlight.range.start.to_display_point(snapshot).row())
19258 } else {
19259 None
19260 }
19261 })
19262 .min()
19263 }
19264
19265 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19266 self.highlight_background::<SearchWithinRange>(
19267 ranges,
19268 |colors| colors.colors().editor_document_highlight_read_background,
19269 cx,
19270 )
19271 }
19272
19273 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19274 self.breadcrumb_header = Some(new_header);
19275 }
19276
19277 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19278 self.clear_background_highlights::<SearchWithinRange>(cx);
19279 }
19280
19281 pub fn highlight_background<T: 'static>(
19282 &mut self,
19283 ranges: &[Range<Anchor>],
19284 color_fetcher: fn(&Theme) -> Hsla,
19285 cx: &mut Context<Self>,
19286 ) {
19287 self.background_highlights.insert(
19288 HighlightKey::Type(TypeId::of::<T>()),
19289 (color_fetcher, Arc::from(ranges)),
19290 );
19291 self.scrollbar_marker_state.dirty = true;
19292 cx.notify();
19293 }
19294
19295 pub fn highlight_background_key<T: 'static>(
19296 &mut self,
19297 key: usize,
19298 ranges: &[Range<Anchor>],
19299 color_fetcher: fn(&Theme) -> Hsla,
19300 cx: &mut Context<Self>,
19301 ) {
19302 self.background_highlights.insert(
19303 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19304 (color_fetcher, Arc::from(ranges)),
19305 );
19306 self.scrollbar_marker_state.dirty = true;
19307 cx.notify();
19308 }
19309
19310 pub fn clear_background_highlights<T: 'static>(
19311 &mut self,
19312 cx: &mut Context<Self>,
19313 ) -> Option<BackgroundHighlight> {
19314 let text_highlights = self
19315 .background_highlights
19316 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19317 if !text_highlights.1.is_empty() {
19318 self.scrollbar_marker_state.dirty = true;
19319 cx.notify();
19320 }
19321 Some(text_highlights)
19322 }
19323
19324 pub fn highlight_gutter<T: 'static>(
19325 &mut self,
19326 ranges: impl Into<Vec<Range<Anchor>>>,
19327 color_fetcher: fn(&App) -> Hsla,
19328 cx: &mut Context<Self>,
19329 ) {
19330 self.gutter_highlights
19331 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19332 cx.notify();
19333 }
19334
19335 pub fn clear_gutter_highlights<T: 'static>(
19336 &mut self,
19337 cx: &mut Context<Self>,
19338 ) -> Option<GutterHighlight> {
19339 cx.notify();
19340 self.gutter_highlights.remove(&TypeId::of::<T>())
19341 }
19342
19343 pub fn insert_gutter_highlight<T: 'static>(
19344 &mut self,
19345 range: Range<Anchor>,
19346 color_fetcher: fn(&App) -> Hsla,
19347 cx: &mut Context<Self>,
19348 ) {
19349 let snapshot = self.buffer().read(cx).snapshot(cx);
19350 let mut highlights = self
19351 .gutter_highlights
19352 .remove(&TypeId::of::<T>())
19353 .map(|(_, highlights)| highlights)
19354 .unwrap_or_default();
19355 let ix = highlights.binary_search_by(|highlight| {
19356 Ordering::Equal
19357 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19358 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19359 });
19360 if let Err(ix) = ix {
19361 highlights.insert(ix, range);
19362 }
19363 self.gutter_highlights
19364 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19365 }
19366
19367 pub fn remove_gutter_highlights<T: 'static>(
19368 &mut self,
19369 ranges_to_remove: Vec<Range<Anchor>>,
19370 cx: &mut Context<Self>,
19371 ) {
19372 let snapshot = self.buffer().read(cx).snapshot(cx);
19373 let Some((color_fetcher, mut gutter_highlights)) =
19374 self.gutter_highlights.remove(&TypeId::of::<T>())
19375 else {
19376 return;
19377 };
19378 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19379 gutter_highlights.retain(|highlight| {
19380 while let Some(range_to_remove) = ranges_to_remove.peek() {
19381 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19382 Ordering::Less | Ordering::Equal => {
19383 ranges_to_remove.next();
19384 }
19385 Ordering::Greater => {
19386 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19387 Ordering::Less | Ordering::Equal => {
19388 return false;
19389 }
19390 Ordering::Greater => break,
19391 }
19392 }
19393 }
19394 }
19395
19396 true
19397 });
19398 self.gutter_highlights
19399 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19400 }
19401
19402 #[cfg(feature = "test-support")]
19403 pub fn all_text_highlights(
19404 &self,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19408 let snapshot = self.snapshot(window, cx);
19409 self.display_map.update(cx, |display_map, _| {
19410 display_map
19411 .all_text_highlights()
19412 .map(|highlight| {
19413 let (style, ranges) = highlight.as_ref();
19414 (
19415 *style,
19416 ranges
19417 .iter()
19418 .map(|range| range.clone().to_display_points(&snapshot))
19419 .collect(),
19420 )
19421 })
19422 .collect()
19423 })
19424 }
19425
19426 #[cfg(feature = "test-support")]
19427 pub fn all_text_background_highlights(
19428 &self,
19429 window: &mut Window,
19430 cx: &mut Context<Self>,
19431 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19432 let snapshot = self.snapshot(window, cx);
19433 let buffer = &snapshot.buffer_snapshot;
19434 let start = buffer.anchor_before(0);
19435 let end = buffer.anchor_after(buffer.len());
19436 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19437 }
19438
19439 #[cfg(feature = "test-support")]
19440 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19441 let snapshot = self.buffer().read(cx).snapshot(cx);
19442
19443 let highlights = self
19444 .background_highlights
19445 .get(&HighlightKey::Type(TypeId::of::<
19446 items::BufferSearchHighlights,
19447 >()));
19448
19449 if let Some((_color, ranges)) = highlights {
19450 ranges
19451 .iter()
19452 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19453 .collect_vec()
19454 } else {
19455 vec![]
19456 }
19457 }
19458
19459 fn document_highlights_for_position<'a>(
19460 &'a self,
19461 position: Anchor,
19462 buffer: &'a MultiBufferSnapshot,
19463 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19464 let read_highlights = self
19465 .background_highlights
19466 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19467 .map(|h| &h.1);
19468 let write_highlights = self
19469 .background_highlights
19470 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19471 .map(|h| &h.1);
19472 let left_position = position.bias_left(buffer);
19473 let right_position = position.bias_right(buffer);
19474 read_highlights
19475 .into_iter()
19476 .chain(write_highlights)
19477 .flat_map(move |ranges| {
19478 let start_ix = match ranges.binary_search_by(|probe| {
19479 let cmp = probe.end.cmp(&left_position, buffer);
19480 if cmp.is_ge() {
19481 Ordering::Greater
19482 } else {
19483 Ordering::Less
19484 }
19485 }) {
19486 Ok(i) | Err(i) => i,
19487 };
19488
19489 ranges[start_ix..]
19490 .iter()
19491 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19492 })
19493 }
19494
19495 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19496 self.background_highlights
19497 .get(&HighlightKey::Type(TypeId::of::<T>()))
19498 .map_or(false, |(_, highlights)| !highlights.is_empty())
19499 }
19500
19501 pub fn background_highlights_in_range(
19502 &self,
19503 search_range: Range<Anchor>,
19504 display_snapshot: &DisplaySnapshot,
19505 theme: &Theme,
19506 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19507 let mut results = Vec::new();
19508 for (color_fetcher, ranges) in self.background_highlights.values() {
19509 let color = color_fetcher(theme);
19510 let start_ix = match ranges.binary_search_by(|probe| {
19511 let cmp = probe
19512 .end
19513 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19514 if cmp.is_gt() {
19515 Ordering::Greater
19516 } else {
19517 Ordering::Less
19518 }
19519 }) {
19520 Ok(i) | Err(i) => i,
19521 };
19522 for range in &ranges[start_ix..] {
19523 if range
19524 .start
19525 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19526 .is_ge()
19527 {
19528 break;
19529 }
19530
19531 let start = range.start.to_display_point(display_snapshot);
19532 let end = range.end.to_display_point(display_snapshot);
19533 results.push((start..end, color))
19534 }
19535 }
19536 results
19537 }
19538
19539 pub fn background_highlight_row_ranges<T: 'static>(
19540 &self,
19541 search_range: Range<Anchor>,
19542 display_snapshot: &DisplaySnapshot,
19543 count: usize,
19544 ) -> Vec<RangeInclusive<DisplayPoint>> {
19545 let mut results = Vec::new();
19546 let Some((_, ranges)) = self
19547 .background_highlights
19548 .get(&HighlightKey::Type(TypeId::of::<T>()))
19549 else {
19550 return vec![];
19551 };
19552
19553 let start_ix = match ranges.binary_search_by(|probe| {
19554 let cmp = probe
19555 .end
19556 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19557 if cmp.is_gt() {
19558 Ordering::Greater
19559 } else {
19560 Ordering::Less
19561 }
19562 }) {
19563 Ok(i) | Err(i) => i,
19564 };
19565 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19566 if let (Some(start_display), Some(end_display)) = (start, end) {
19567 results.push(
19568 start_display.to_display_point(display_snapshot)
19569 ..=end_display.to_display_point(display_snapshot),
19570 );
19571 }
19572 };
19573 let mut start_row: Option<Point> = None;
19574 let mut end_row: Option<Point> = None;
19575 if ranges.len() > count {
19576 return Vec::new();
19577 }
19578 for range in &ranges[start_ix..] {
19579 if range
19580 .start
19581 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19582 .is_ge()
19583 {
19584 break;
19585 }
19586 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19587 if let Some(current_row) = &end_row {
19588 if end.row == current_row.row {
19589 continue;
19590 }
19591 }
19592 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19593 if start_row.is_none() {
19594 assert_eq!(end_row, None);
19595 start_row = Some(start);
19596 end_row = Some(end);
19597 continue;
19598 }
19599 if let Some(current_end) = end_row.as_mut() {
19600 if start.row > current_end.row + 1 {
19601 push_region(start_row, end_row);
19602 start_row = Some(start);
19603 end_row = Some(end);
19604 } else {
19605 // Merge two hunks.
19606 *current_end = end;
19607 }
19608 } else {
19609 unreachable!();
19610 }
19611 }
19612 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19613 push_region(start_row, end_row);
19614 results
19615 }
19616
19617 pub fn gutter_highlights_in_range(
19618 &self,
19619 search_range: Range<Anchor>,
19620 display_snapshot: &DisplaySnapshot,
19621 cx: &App,
19622 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19623 let mut results = Vec::new();
19624 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19625 let color = color_fetcher(cx);
19626 let start_ix = match ranges.binary_search_by(|probe| {
19627 let cmp = probe
19628 .end
19629 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19630 if cmp.is_gt() {
19631 Ordering::Greater
19632 } else {
19633 Ordering::Less
19634 }
19635 }) {
19636 Ok(i) | Err(i) => i,
19637 };
19638 for range in &ranges[start_ix..] {
19639 if range
19640 .start
19641 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19642 .is_ge()
19643 {
19644 break;
19645 }
19646
19647 let start = range.start.to_display_point(display_snapshot);
19648 let end = range.end.to_display_point(display_snapshot);
19649 results.push((start..end, color))
19650 }
19651 }
19652 results
19653 }
19654
19655 /// Get the text ranges corresponding to the redaction query
19656 pub fn redacted_ranges(
19657 &self,
19658 search_range: Range<Anchor>,
19659 display_snapshot: &DisplaySnapshot,
19660 cx: &App,
19661 ) -> Vec<Range<DisplayPoint>> {
19662 display_snapshot
19663 .buffer_snapshot
19664 .redacted_ranges(search_range, |file| {
19665 if let Some(file) = file {
19666 file.is_private()
19667 && EditorSettings::get(
19668 Some(SettingsLocation {
19669 worktree_id: file.worktree_id(cx),
19670 path: file.path().as_ref(),
19671 }),
19672 cx,
19673 )
19674 .redact_private_values
19675 } else {
19676 false
19677 }
19678 })
19679 .map(|range| {
19680 range.start.to_display_point(display_snapshot)
19681 ..range.end.to_display_point(display_snapshot)
19682 })
19683 .collect()
19684 }
19685
19686 pub fn highlight_text_key<T: 'static>(
19687 &mut self,
19688 key: usize,
19689 ranges: Vec<Range<Anchor>>,
19690 style: HighlightStyle,
19691 cx: &mut Context<Self>,
19692 ) {
19693 self.display_map.update(cx, |map, _| {
19694 map.highlight_text(
19695 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19696 ranges,
19697 style,
19698 );
19699 });
19700 cx.notify();
19701 }
19702
19703 pub fn highlight_text<T: 'static>(
19704 &mut self,
19705 ranges: Vec<Range<Anchor>>,
19706 style: HighlightStyle,
19707 cx: &mut Context<Self>,
19708 ) {
19709 self.display_map.update(cx, |map, _| {
19710 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19711 });
19712 cx.notify();
19713 }
19714
19715 pub(crate) fn highlight_inlays<T: 'static>(
19716 &mut self,
19717 highlights: Vec<InlayHighlight>,
19718 style: HighlightStyle,
19719 cx: &mut Context<Self>,
19720 ) {
19721 self.display_map.update(cx, |map, _| {
19722 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19723 });
19724 cx.notify();
19725 }
19726
19727 pub fn text_highlights<'a, T: 'static>(
19728 &'a self,
19729 cx: &'a App,
19730 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19731 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19732 }
19733
19734 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19735 let cleared = self
19736 .display_map
19737 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19738 if cleared {
19739 cx.notify();
19740 }
19741 }
19742
19743 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19744 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19745 && self.focus_handle.is_focused(window)
19746 }
19747
19748 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19749 self.show_cursor_when_unfocused = is_enabled;
19750 cx.notify();
19751 }
19752
19753 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19754 cx.notify();
19755 }
19756
19757 fn on_debug_session_event(
19758 &mut self,
19759 _session: Entity<Session>,
19760 event: &SessionEvent,
19761 cx: &mut Context<Self>,
19762 ) {
19763 match event {
19764 SessionEvent::InvalidateInlineValue => {
19765 self.refresh_inline_values(cx);
19766 }
19767 _ => {}
19768 }
19769 }
19770
19771 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19772 let Some(project) = self.project.clone() else {
19773 return;
19774 };
19775
19776 if !self.inline_value_cache.enabled {
19777 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19778 self.splice_inlays(&inlays, Vec::new(), cx);
19779 return;
19780 }
19781
19782 let current_execution_position = self
19783 .highlighted_rows
19784 .get(&TypeId::of::<ActiveDebugLine>())
19785 .and_then(|lines| lines.last().map(|line| line.range.end));
19786
19787 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19788 let inline_values = editor
19789 .update(cx, |editor, cx| {
19790 let Some(current_execution_position) = current_execution_position else {
19791 return Some(Task::ready(Ok(Vec::new())));
19792 };
19793
19794 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19795 let snapshot = buffer.snapshot(cx);
19796
19797 let excerpt = snapshot.excerpt_containing(
19798 current_execution_position..current_execution_position,
19799 )?;
19800
19801 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19802 })?;
19803
19804 let range =
19805 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19806
19807 project.inline_values(buffer, range, cx)
19808 })
19809 .ok()
19810 .flatten()?
19811 .await
19812 .context("refreshing debugger inlays")
19813 .log_err()?;
19814
19815 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19816
19817 for (buffer_id, inline_value) in inline_values
19818 .into_iter()
19819 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19820 {
19821 buffer_inline_values
19822 .entry(buffer_id)
19823 .or_default()
19824 .push(inline_value);
19825 }
19826
19827 editor
19828 .update(cx, |editor, cx| {
19829 let snapshot = editor.buffer.read(cx).snapshot(cx);
19830 let mut new_inlays = Vec::default();
19831
19832 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19833 let buffer_id = buffer_snapshot.remote_id();
19834 buffer_inline_values
19835 .get(&buffer_id)
19836 .into_iter()
19837 .flatten()
19838 .for_each(|hint| {
19839 let inlay = Inlay::debugger(
19840 post_inc(&mut editor.next_inlay_id),
19841 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19842 hint.text(),
19843 );
19844 if !inlay.text.chars().contains(&'\n') {
19845 new_inlays.push(inlay);
19846 }
19847 });
19848 }
19849
19850 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19851 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19852
19853 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19854 })
19855 .ok()?;
19856 Some(())
19857 });
19858 }
19859
19860 fn on_buffer_event(
19861 &mut self,
19862 multibuffer: &Entity<MultiBuffer>,
19863 event: &multi_buffer::Event,
19864 window: &mut Window,
19865 cx: &mut Context<Self>,
19866 ) {
19867 match event {
19868 multi_buffer::Event::Edited {
19869 singleton_buffer_edited,
19870 edited_buffer,
19871 } => {
19872 self.scrollbar_marker_state.dirty = true;
19873 self.active_indent_guides_state.dirty = true;
19874 self.refresh_active_diagnostics(cx);
19875 self.refresh_code_actions(window, cx);
19876 self.refresh_selected_text_highlights(true, window, cx);
19877 self.refresh_single_line_folds(window, cx);
19878 refresh_matching_bracket_highlights(self, window, cx);
19879 if self.has_active_inline_completion() {
19880 self.update_visible_inline_completion(window, cx);
19881 }
19882 if let Some(project) = self.project.as_ref() {
19883 if let Some(edited_buffer) = edited_buffer {
19884 project.update(cx, |project, cx| {
19885 self.registered_buffers
19886 .entry(edited_buffer.read(cx).remote_id())
19887 .or_insert_with(|| {
19888 project
19889 .register_buffer_with_language_servers(&edited_buffer, cx)
19890 });
19891 });
19892 }
19893 }
19894 cx.emit(EditorEvent::BufferEdited);
19895 cx.emit(SearchEvent::MatchesInvalidated);
19896
19897 if let Some(buffer) = edited_buffer {
19898 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19899 }
19900
19901 if *singleton_buffer_edited {
19902 if let Some(buffer) = edited_buffer {
19903 if buffer.read(cx).file().is_none() {
19904 cx.emit(EditorEvent::TitleChanged);
19905 }
19906 }
19907 if let Some(project) = &self.project {
19908 #[allow(clippy::mutable_key_type)]
19909 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19910 multibuffer
19911 .all_buffers()
19912 .into_iter()
19913 .filter_map(|buffer| {
19914 buffer.update(cx, |buffer, cx| {
19915 let language = buffer.language()?;
19916 let should_discard = project.update(cx, |project, cx| {
19917 project.is_local()
19918 && !project.has_language_servers_for(buffer, cx)
19919 });
19920 should_discard.not().then_some(language.clone())
19921 })
19922 })
19923 .collect::<HashSet<_>>()
19924 });
19925 if !languages_affected.is_empty() {
19926 self.refresh_inlay_hints(
19927 InlayHintRefreshReason::BufferEdited(languages_affected),
19928 cx,
19929 );
19930 }
19931 }
19932 }
19933
19934 let Some(project) = &self.project else { return };
19935 let (telemetry, is_via_ssh) = {
19936 let project = project.read(cx);
19937 let telemetry = project.client().telemetry().clone();
19938 let is_via_ssh = project.is_via_ssh();
19939 (telemetry, is_via_ssh)
19940 };
19941 refresh_linked_ranges(self, window, cx);
19942 telemetry.log_edit_event("editor", is_via_ssh);
19943 }
19944 multi_buffer::Event::ExcerptsAdded {
19945 buffer,
19946 predecessor,
19947 excerpts,
19948 } => {
19949 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19950 let buffer_id = buffer.read(cx).remote_id();
19951 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19952 if let Some(project) = &self.project {
19953 update_uncommitted_diff_for_buffer(
19954 cx.entity(),
19955 project,
19956 [buffer.clone()],
19957 self.buffer.clone(),
19958 cx,
19959 )
19960 .detach();
19961 }
19962 }
19963 self.update_lsp_data(false, Some(buffer_id), window, cx);
19964 cx.emit(EditorEvent::ExcerptsAdded {
19965 buffer: buffer.clone(),
19966 predecessor: *predecessor,
19967 excerpts: excerpts.clone(),
19968 });
19969 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19970 }
19971 multi_buffer::Event::ExcerptsRemoved {
19972 ids,
19973 removed_buffer_ids,
19974 } => {
19975 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19976 let buffer = self.buffer.read(cx);
19977 self.registered_buffers
19978 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19979 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19980 cx.emit(EditorEvent::ExcerptsRemoved {
19981 ids: ids.clone(),
19982 removed_buffer_ids: removed_buffer_ids.clone(),
19983 });
19984 }
19985 multi_buffer::Event::ExcerptsEdited {
19986 excerpt_ids,
19987 buffer_ids,
19988 } => {
19989 self.display_map.update(cx, |map, cx| {
19990 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19991 });
19992 cx.emit(EditorEvent::ExcerptsEdited {
19993 ids: excerpt_ids.clone(),
19994 });
19995 }
19996 multi_buffer::Event::ExcerptsExpanded { ids } => {
19997 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19998 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19999 }
20000 multi_buffer::Event::Reparsed(buffer_id) => {
20001 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20002 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20003
20004 cx.emit(EditorEvent::Reparsed(*buffer_id));
20005 }
20006 multi_buffer::Event::DiffHunksToggled => {
20007 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20008 }
20009 multi_buffer::Event::LanguageChanged(buffer_id) => {
20010 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20011 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20012 cx.emit(EditorEvent::Reparsed(*buffer_id));
20013 cx.notify();
20014 }
20015 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20016 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20017 multi_buffer::Event::FileHandleChanged
20018 | multi_buffer::Event::Reloaded
20019 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20020 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20021 multi_buffer::Event::DiagnosticsUpdated => {
20022 self.update_diagnostics_state(window, cx);
20023 }
20024 _ => {}
20025 };
20026 }
20027
20028 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20029 if !self.diagnostics_enabled() {
20030 return;
20031 }
20032 self.refresh_active_diagnostics(cx);
20033 self.refresh_inline_diagnostics(true, window, cx);
20034 self.scrollbar_marker_state.dirty = true;
20035 cx.notify();
20036 }
20037
20038 pub fn start_temporary_diff_override(&mut self) {
20039 self.load_diff_task.take();
20040 self.temporary_diff_override = true;
20041 }
20042
20043 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20044 self.temporary_diff_override = false;
20045 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20046 self.buffer.update(cx, |buffer, cx| {
20047 buffer.set_all_diff_hunks_collapsed(cx);
20048 });
20049
20050 if let Some(project) = self.project.clone() {
20051 self.load_diff_task = Some(
20052 update_uncommitted_diff_for_buffer(
20053 cx.entity(),
20054 &project,
20055 self.buffer.read(cx).all_buffers(),
20056 self.buffer.clone(),
20057 cx,
20058 )
20059 .shared(),
20060 );
20061 }
20062 }
20063
20064 fn on_display_map_changed(
20065 &mut self,
20066 _: Entity<DisplayMap>,
20067 _: &mut Window,
20068 cx: &mut Context<Self>,
20069 ) {
20070 cx.notify();
20071 }
20072
20073 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20074 if self.diagnostics_enabled() {
20075 let new_severity = EditorSettings::get_global(cx)
20076 .diagnostics_max_severity
20077 .unwrap_or(DiagnosticSeverity::Hint);
20078 self.set_max_diagnostics_severity(new_severity, cx);
20079 }
20080 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20081 self.update_edit_prediction_settings(cx);
20082 self.refresh_inline_completion(true, false, window, cx);
20083 self.refresh_inline_values(cx);
20084 self.refresh_inlay_hints(
20085 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20086 self.selections.newest_anchor().head(),
20087 &self.buffer.read(cx).snapshot(cx),
20088 cx,
20089 )),
20090 cx,
20091 );
20092
20093 let old_cursor_shape = self.cursor_shape;
20094
20095 {
20096 let editor_settings = EditorSettings::get_global(cx);
20097 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20098 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20099 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20100 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20101 }
20102
20103 if old_cursor_shape != self.cursor_shape {
20104 cx.emit(EditorEvent::CursorShapeChanged);
20105 }
20106
20107 let project_settings = ProjectSettings::get_global(cx);
20108 self.serialize_dirty_buffers =
20109 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20110
20111 if self.mode.is_full() {
20112 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20113 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20114 if self.show_inline_diagnostics != show_inline_diagnostics {
20115 self.show_inline_diagnostics = show_inline_diagnostics;
20116 self.refresh_inline_diagnostics(false, window, cx);
20117 }
20118
20119 if self.git_blame_inline_enabled != inline_blame_enabled {
20120 self.toggle_git_blame_inline_internal(false, window, cx);
20121 }
20122
20123 let minimap_settings = EditorSettings::get_global(cx).minimap;
20124 if self.minimap_visibility != MinimapVisibility::Disabled {
20125 if self.minimap_visibility.settings_visibility()
20126 != minimap_settings.minimap_enabled()
20127 {
20128 self.set_minimap_visibility(
20129 MinimapVisibility::for_mode(self.mode(), cx),
20130 window,
20131 cx,
20132 );
20133 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20134 minimap_entity.update(cx, |minimap_editor, cx| {
20135 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20136 })
20137 }
20138 }
20139 }
20140
20141 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20142 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20143 }) {
20144 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20145 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20146 }
20147 self.refresh_colors(false, None, window, cx);
20148 }
20149
20150 cx.notify();
20151 }
20152
20153 pub fn set_searchable(&mut self, searchable: bool) {
20154 self.searchable = searchable;
20155 }
20156
20157 pub fn searchable(&self) -> bool {
20158 self.searchable
20159 }
20160
20161 fn open_proposed_changes_editor(
20162 &mut self,
20163 _: &OpenProposedChangesEditor,
20164 window: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) {
20167 let Some(workspace) = self.workspace() else {
20168 cx.propagate();
20169 return;
20170 };
20171
20172 let selections = self.selections.all::<usize>(cx);
20173 let multi_buffer = self.buffer.read(cx);
20174 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20175 let mut new_selections_by_buffer = HashMap::default();
20176 for selection in selections {
20177 for (buffer, range, _) in
20178 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20179 {
20180 let mut range = range.to_point(buffer);
20181 range.start.column = 0;
20182 range.end.column = buffer.line_len(range.end.row);
20183 new_selections_by_buffer
20184 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20185 .or_insert(Vec::new())
20186 .push(range)
20187 }
20188 }
20189
20190 let proposed_changes_buffers = new_selections_by_buffer
20191 .into_iter()
20192 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20193 .collect::<Vec<_>>();
20194 let proposed_changes_editor = cx.new(|cx| {
20195 ProposedChangesEditor::new(
20196 "Proposed changes",
20197 proposed_changes_buffers,
20198 self.project.clone(),
20199 window,
20200 cx,
20201 )
20202 });
20203
20204 window.defer(cx, move |window, cx| {
20205 workspace.update(cx, |workspace, cx| {
20206 workspace.active_pane().update(cx, |pane, cx| {
20207 pane.add_item(
20208 Box::new(proposed_changes_editor),
20209 true,
20210 true,
20211 None,
20212 window,
20213 cx,
20214 );
20215 });
20216 });
20217 });
20218 }
20219
20220 pub fn open_excerpts_in_split(
20221 &mut self,
20222 _: &OpenExcerptsSplit,
20223 window: &mut Window,
20224 cx: &mut Context<Self>,
20225 ) {
20226 self.open_excerpts_common(None, true, window, cx)
20227 }
20228
20229 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20230 self.open_excerpts_common(None, false, window, cx)
20231 }
20232
20233 fn open_excerpts_common(
20234 &mut self,
20235 jump_data: Option<JumpData>,
20236 split: bool,
20237 window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 let Some(workspace) = self.workspace() else {
20241 cx.propagate();
20242 return;
20243 };
20244
20245 if self.buffer.read(cx).is_singleton() {
20246 cx.propagate();
20247 return;
20248 }
20249
20250 let mut new_selections_by_buffer = HashMap::default();
20251 match &jump_data {
20252 Some(JumpData::MultiBufferPoint {
20253 excerpt_id,
20254 position,
20255 anchor,
20256 line_offset_from_top,
20257 }) => {
20258 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20259 if let Some(buffer) = multi_buffer_snapshot
20260 .buffer_id_for_excerpt(*excerpt_id)
20261 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20262 {
20263 let buffer_snapshot = buffer.read(cx).snapshot();
20264 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20265 language::ToPoint::to_point(anchor, &buffer_snapshot)
20266 } else {
20267 buffer_snapshot.clip_point(*position, Bias::Left)
20268 };
20269 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20270 new_selections_by_buffer.insert(
20271 buffer,
20272 (
20273 vec![jump_to_offset..jump_to_offset],
20274 Some(*line_offset_from_top),
20275 ),
20276 );
20277 }
20278 }
20279 Some(JumpData::MultiBufferRow {
20280 row,
20281 line_offset_from_top,
20282 }) => {
20283 let point = MultiBufferPoint::new(row.0, 0);
20284 if let Some((buffer, buffer_point, _)) =
20285 self.buffer.read(cx).point_to_buffer_point(point, cx)
20286 {
20287 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20288 new_selections_by_buffer
20289 .entry(buffer)
20290 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20291 .0
20292 .push(buffer_offset..buffer_offset)
20293 }
20294 }
20295 None => {
20296 let selections = self.selections.all::<usize>(cx);
20297 let multi_buffer = self.buffer.read(cx);
20298 for selection in selections {
20299 for (snapshot, range, _, anchor) in multi_buffer
20300 .snapshot(cx)
20301 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20302 {
20303 if let Some(anchor) = anchor {
20304 // selection is in a deleted hunk
20305 let Some(buffer_id) = anchor.buffer_id else {
20306 continue;
20307 };
20308 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20309 continue;
20310 };
20311 let offset = text::ToOffset::to_offset(
20312 &anchor.text_anchor,
20313 &buffer_handle.read(cx).snapshot(),
20314 );
20315 let range = offset..offset;
20316 new_selections_by_buffer
20317 .entry(buffer_handle)
20318 .or_insert((Vec::new(), None))
20319 .0
20320 .push(range)
20321 } else {
20322 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20323 else {
20324 continue;
20325 };
20326 new_selections_by_buffer
20327 .entry(buffer_handle)
20328 .or_insert((Vec::new(), None))
20329 .0
20330 .push(range)
20331 }
20332 }
20333 }
20334 }
20335 }
20336
20337 new_selections_by_buffer
20338 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20339
20340 if new_selections_by_buffer.is_empty() {
20341 return;
20342 }
20343
20344 // We defer the pane interaction because we ourselves are a workspace item
20345 // and activating a new item causes the pane to call a method on us reentrantly,
20346 // which panics if we're on the stack.
20347 window.defer(cx, move |window, cx| {
20348 workspace.update(cx, |workspace, cx| {
20349 let pane = if split {
20350 workspace.adjacent_pane(window, cx)
20351 } else {
20352 workspace.active_pane().clone()
20353 };
20354
20355 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20356 let editor = buffer
20357 .read(cx)
20358 .file()
20359 .is_none()
20360 .then(|| {
20361 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20362 // so `workspace.open_project_item` will never find them, always opening a new editor.
20363 // Instead, we try to activate the existing editor in the pane first.
20364 let (editor, pane_item_index) =
20365 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20366 let editor = item.downcast::<Editor>()?;
20367 let singleton_buffer =
20368 editor.read(cx).buffer().read(cx).as_singleton()?;
20369 if singleton_buffer == buffer {
20370 Some((editor, i))
20371 } else {
20372 None
20373 }
20374 })?;
20375 pane.update(cx, |pane, cx| {
20376 pane.activate_item(pane_item_index, true, true, window, cx)
20377 });
20378 Some(editor)
20379 })
20380 .flatten()
20381 .unwrap_or_else(|| {
20382 workspace.open_project_item::<Self>(
20383 pane.clone(),
20384 buffer,
20385 true,
20386 true,
20387 window,
20388 cx,
20389 )
20390 });
20391
20392 editor.update(cx, |editor, cx| {
20393 let autoscroll = match scroll_offset {
20394 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20395 None => Autoscroll::newest(),
20396 };
20397 let nav_history = editor.nav_history.take();
20398 editor.change_selections(
20399 SelectionEffects::scroll(autoscroll),
20400 window,
20401 cx,
20402 |s| {
20403 s.select_ranges(ranges);
20404 },
20405 );
20406 editor.nav_history = nav_history;
20407 });
20408 }
20409 })
20410 });
20411 }
20412
20413 // For now, don't allow opening excerpts in buffers that aren't backed by
20414 // regular project files.
20415 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20416 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20417 }
20418
20419 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20420 let snapshot = self.buffer.read(cx).read(cx);
20421 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20422 Some(
20423 ranges
20424 .iter()
20425 .map(move |range| {
20426 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20427 })
20428 .collect(),
20429 )
20430 }
20431
20432 fn selection_replacement_ranges(
20433 &self,
20434 range: Range<OffsetUtf16>,
20435 cx: &mut App,
20436 ) -> Vec<Range<OffsetUtf16>> {
20437 let selections = self.selections.all::<OffsetUtf16>(cx);
20438 let newest_selection = selections
20439 .iter()
20440 .max_by_key(|selection| selection.id)
20441 .unwrap();
20442 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20443 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20444 let snapshot = self.buffer.read(cx).read(cx);
20445 selections
20446 .into_iter()
20447 .map(|mut selection| {
20448 selection.start.0 =
20449 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20450 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20451 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20452 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20453 })
20454 .collect()
20455 }
20456
20457 fn report_editor_event(
20458 &self,
20459 event_type: &'static str,
20460 file_extension: Option<String>,
20461 cx: &App,
20462 ) {
20463 if cfg!(any(test, feature = "test-support")) {
20464 return;
20465 }
20466
20467 let Some(project) = &self.project else { return };
20468
20469 // If None, we are in a file without an extension
20470 let file = self
20471 .buffer
20472 .read(cx)
20473 .as_singleton()
20474 .and_then(|b| b.read(cx).file());
20475 let file_extension = file_extension.or(file
20476 .as_ref()
20477 .and_then(|file| Path::new(file.file_name(cx)).extension())
20478 .and_then(|e| e.to_str())
20479 .map(|a| a.to_string()));
20480
20481 let vim_mode = vim_enabled(cx);
20482
20483 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20484 let copilot_enabled = edit_predictions_provider
20485 == language::language_settings::EditPredictionProvider::Copilot;
20486 let copilot_enabled_for_language = self
20487 .buffer
20488 .read(cx)
20489 .language_settings(cx)
20490 .show_edit_predictions;
20491
20492 let project = project.read(cx);
20493 telemetry::event!(
20494 event_type,
20495 file_extension,
20496 vim_mode,
20497 copilot_enabled,
20498 copilot_enabled_for_language,
20499 edit_predictions_provider,
20500 is_via_ssh = project.is_via_ssh(),
20501 );
20502 }
20503
20504 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20505 /// with each line being an array of {text, highlight} objects.
20506 fn copy_highlight_json(
20507 &mut self,
20508 _: &CopyHighlightJson,
20509 window: &mut Window,
20510 cx: &mut Context<Self>,
20511 ) {
20512 #[derive(Serialize)]
20513 struct Chunk<'a> {
20514 text: String,
20515 highlight: Option<&'a str>,
20516 }
20517
20518 let snapshot = self.buffer.read(cx).snapshot(cx);
20519 let range = self
20520 .selected_text_range(false, window, cx)
20521 .and_then(|selection| {
20522 if selection.range.is_empty() {
20523 None
20524 } else {
20525 Some(selection.range)
20526 }
20527 })
20528 .unwrap_or_else(|| 0..snapshot.len());
20529
20530 let chunks = snapshot.chunks(range, true);
20531 let mut lines = Vec::new();
20532 let mut line: VecDeque<Chunk> = VecDeque::new();
20533
20534 let Some(style) = self.style.as_ref() else {
20535 return;
20536 };
20537
20538 for chunk in chunks {
20539 let highlight = chunk
20540 .syntax_highlight_id
20541 .and_then(|id| id.name(&style.syntax));
20542 let mut chunk_lines = chunk.text.split('\n').peekable();
20543 while let Some(text) = chunk_lines.next() {
20544 let mut merged_with_last_token = false;
20545 if let Some(last_token) = line.back_mut() {
20546 if last_token.highlight == highlight {
20547 last_token.text.push_str(text);
20548 merged_with_last_token = true;
20549 }
20550 }
20551
20552 if !merged_with_last_token {
20553 line.push_back(Chunk {
20554 text: text.into(),
20555 highlight,
20556 });
20557 }
20558
20559 if chunk_lines.peek().is_some() {
20560 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20561 line.pop_front();
20562 }
20563 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20564 line.pop_back();
20565 }
20566
20567 lines.push(mem::take(&mut line));
20568 }
20569 }
20570 }
20571
20572 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20573 return;
20574 };
20575 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20576 }
20577
20578 pub fn open_context_menu(
20579 &mut self,
20580 _: &OpenContextMenu,
20581 window: &mut Window,
20582 cx: &mut Context<Self>,
20583 ) {
20584 self.request_autoscroll(Autoscroll::newest(), cx);
20585 let position = self.selections.newest_display(cx).start;
20586 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20587 }
20588
20589 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20590 &self.inlay_hint_cache
20591 }
20592
20593 pub fn replay_insert_event(
20594 &mut self,
20595 text: &str,
20596 relative_utf16_range: Option<Range<isize>>,
20597 window: &mut Window,
20598 cx: &mut Context<Self>,
20599 ) {
20600 if !self.input_enabled {
20601 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20602 return;
20603 }
20604 if let Some(relative_utf16_range) = relative_utf16_range {
20605 let selections = self.selections.all::<OffsetUtf16>(cx);
20606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20607 let new_ranges = selections.into_iter().map(|range| {
20608 let start = OffsetUtf16(
20609 range
20610 .head()
20611 .0
20612 .saturating_add_signed(relative_utf16_range.start),
20613 );
20614 let end = OffsetUtf16(
20615 range
20616 .head()
20617 .0
20618 .saturating_add_signed(relative_utf16_range.end),
20619 );
20620 start..end
20621 });
20622 s.select_ranges(new_ranges);
20623 });
20624 }
20625
20626 self.handle_input(text, window, cx);
20627 }
20628
20629 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20630 let Some(provider) = self.semantics_provider.as_ref() else {
20631 return false;
20632 };
20633
20634 let mut supports = false;
20635 self.buffer().update(cx, |this, cx| {
20636 this.for_each_buffer(|buffer| {
20637 supports |= provider.supports_inlay_hints(buffer, cx);
20638 });
20639 });
20640
20641 supports
20642 }
20643
20644 pub fn is_focused(&self, window: &Window) -> bool {
20645 self.focus_handle.is_focused(window)
20646 }
20647
20648 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20649 cx.emit(EditorEvent::Focused);
20650
20651 if let Some(descendant) = self
20652 .last_focused_descendant
20653 .take()
20654 .and_then(|descendant| descendant.upgrade())
20655 {
20656 window.focus(&descendant);
20657 } else {
20658 if let Some(blame) = self.blame.as_ref() {
20659 blame.update(cx, GitBlame::focus)
20660 }
20661
20662 self.blink_manager.update(cx, BlinkManager::enable);
20663 self.show_cursor_names(window, cx);
20664 self.buffer.update(cx, |buffer, cx| {
20665 buffer.finalize_last_transaction(cx);
20666 if self.leader_id.is_none() {
20667 buffer.set_active_selections(
20668 &self.selections.disjoint_anchors(),
20669 self.selections.line_mode,
20670 self.cursor_shape,
20671 cx,
20672 );
20673 }
20674 });
20675 }
20676 }
20677
20678 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20679 cx.emit(EditorEvent::FocusedIn)
20680 }
20681
20682 fn handle_focus_out(
20683 &mut self,
20684 event: FocusOutEvent,
20685 _window: &mut Window,
20686 cx: &mut Context<Self>,
20687 ) {
20688 if event.blurred != self.focus_handle {
20689 self.last_focused_descendant = Some(event.blurred);
20690 }
20691 self.selection_drag_state = SelectionDragState::None;
20692 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20693 }
20694
20695 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20696 self.blink_manager.update(cx, BlinkManager::disable);
20697 self.buffer
20698 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20699
20700 if let Some(blame) = self.blame.as_ref() {
20701 blame.update(cx, GitBlame::blur)
20702 }
20703 if !self.hover_state.focused(window, cx) {
20704 hide_hover(self, cx);
20705 }
20706 if !self
20707 .context_menu
20708 .borrow()
20709 .as_ref()
20710 .is_some_and(|context_menu| context_menu.focused(window, cx))
20711 {
20712 self.hide_context_menu(window, cx);
20713 }
20714 self.discard_inline_completion(false, cx);
20715 cx.emit(EditorEvent::Blurred);
20716 cx.notify();
20717 }
20718
20719 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20720 let mut pending: String = window
20721 .pending_input_keystrokes()
20722 .into_iter()
20723 .flatten()
20724 .filter_map(|keystroke| {
20725 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20726 keystroke.key_char.clone()
20727 } else {
20728 None
20729 }
20730 })
20731 .collect();
20732
20733 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20734 pending = "".to_string();
20735 }
20736
20737 let existing_pending = self
20738 .text_highlights::<PendingInput>(cx)
20739 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20740 if existing_pending.is_none() && pending.is_empty() {
20741 return;
20742 }
20743 let transaction =
20744 self.transact(window, cx, |this, window, cx| {
20745 let selections = this.selections.all::<usize>(cx);
20746 let edits = selections
20747 .iter()
20748 .map(|selection| (selection.end..selection.end, pending.clone()));
20749 this.edit(edits, cx);
20750 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20751 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20752 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20753 }));
20754 });
20755 if let Some(existing_ranges) = existing_pending {
20756 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20757 this.edit(edits, cx);
20758 }
20759 });
20760
20761 let snapshot = self.snapshot(window, cx);
20762 let ranges = self
20763 .selections
20764 .all::<usize>(cx)
20765 .into_iter()
20766 .map(|selection| {
20767 snapshot.buffer_snapshot.anchor_after(selection.end)
20768 ..snapshot
20769 .buffer_snapshot
20770 .anchor_before(selection.end + pending.len())
20771 })
20772 .collect();
20773
20774 if pending.is_empty() {
20775 self.clear_highlights::<PendingInput>(cx);
20776 } else {
20777 self.highlight_text::<PendingInput>(
20778 ranges,
20779 HighlightStyle {
20780 underline: Some(UnderlineStyle {
20781 thickness: px(1.),
20782 color: None,
20783 wavy: false,
20784 }),
20785 ..Default::default()
20786 },
20787 cx,
20788 );
20789 }
20790
20791 self.ime_transaction = self.ime_transaction.or(transaction);
20792 if let Some(transaction) = self.ime_transaction {
20793 self.buffer.update(cx, |buffer, cx| {
20794 buffer.group_until_transaction(transaction, cx);
20795 });
20796 }
20797
20798 if self.text_highlights::<PendingInput>(cx).is_none() {
20799 self.ime_transaction.take();
20800 }
20801 }
20802
20803 pub fn register_action_renderer(
20804 &mut self,
20805 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20806 ) -> Subscription {
20807 let id = self.next_editor_action_id.post_inc();
20808 self.editor_actions
20809 .borrow_mut()
20810 .insert(id, Box::new(listener));
20811
20812 let editor_actions = self.editor_actions.clone();
20813 Subscription::new(move || {
20814 editor_actions.borrow_mut().remove(&id);
20815 })
20816 }
20817
20818 pub fn register_action<A: Action>(
20819 &mut self,
20820 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20821 ) -> Subscription {
20822 let id = self.next_editor_action_id.post_inc();
20823 let listener = Arc::new(listener);
20824 self.editor_actions.borrow_mut().insert(
20825 id,
20826 Box::new(move |_, window, _| {
20827 let listener = listener.clone();
20828 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20829 let action = action.downcast_ref().unwrap();
20830 if phase == DispatchPhase::Bubble {
20831 listener(action, window, cx)
20832 }
20833 })
20834 }),
20835 );
20836
20837 let editor_actions = self.editor_actions.clone();
20838 Subscription::new(move || {
20839 editor_actions.borrow_mut().remove(&id);
20840 })
20841 }
20842
20843 pub fn file_header_size(&self) -> u32 {
20844 FILE_HEADER_HEIGHT
20845 }
20846
20847 pub fn restore(
20848 &mut self,
20849 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) {
20853 let workspace = self.workspace();
20854 let project = self.project.as_ref();
20855 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20856 let mut tasks = Vec::new();
20857 for (buffer_id, changes) in revert_changes {
20858 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20859 buffer.update(cx, |buffer, cx| {
20860 buffer.edit(
20861 changes
20862 .into_iter()
20863 .map(|(range, text)| (range, text.to_string())),
20864 None,
20865 cx,
20866 );
20867 });
20868
20869 if let Some(project) =
20870 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20871 {
20872 project.update(cx, |project, cx| {
20873 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20874 })
20875 }
20876 }
20877 }
20878 tasks
20879 });
20880 cx.spawn_in(window, async move |_, cx| {
20881 for (buffer, task) in save_tasks {
20882 let result = task.await;
20883 if result.is_err() {
20884 let Some(path) = buffer
20885 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20886 .ok()
20887 else {
20888 continue;
20889 };
20890 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20891 let Some(task) = cx
20892 .update_window_entity(&workspace, |workspace, window, cx| {
20893 workspace
20894 .open_path_preview(path, None, false, false, false, window, cx)
20895 })
20896 .ok()
20897 else {
20898 continue;
20899 };
20900 task.await.log_err();
20901 }
20902 }
20903 }
20904 })
20905 .detach();
20906 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20907 selections.refresh()
20908 });
20909 }
20910
20911 pub fn to_pixel_point(
20912 &self,
20913 source: multi_buffer::Anchor,
20914 editor_snapshot: &EditorSnapshot,
20915 window: &mut Window,
20916 ) -> Option<gpui::Point<Pixels>> {
20917 let source_point = source.to_display_point(editor_snapshot);
20918 self.display_to_pixel_point(source_point, editor_snapshot, window)
20919 }
20920
20921 pub fn display_to_pixel_point(
20922 &self,
20923 source: DisplayPoint,
20924 editor_snapshot: &EditorSnapshot,
20925 window: &mut Window,
20926 ) -> Option<gpui::Point<Pixels>> {
20927 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20928 let text_layout_details = self.text_layout_details(window);
20929 let scroll_top = text_layout_details
20930 .scroll_anchor
20931 .scroll_position(editor_snapshot)
20932 .y;
20933
20934 if source.row().as_f32() < scroll_top.floor() {
20935 return None;
20936 }
20937 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20938 let source_y = line_height * (source.row().as_f32() - scroll_top);
20939 Some(gpui::Point::new(source_x, source_y))
20940 }
20941
20942 pub fn has_visible_completions_menu(&self) -> bool {
20943 !self.edit_prediction_preview_is_active()
20944 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20945 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20946 })
20947 }
20948
20949 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20950 if self.mode.is_minimap() {
20951 return;
20952 }
20953 self.addons
20954 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20955 }
20956
20957 pub fn unregister_addon<T: Addon>(&mut self) {
20958 self.addons.remove(&std::any::TypeId::of::<T>());
20959 }
20960
20961 pub fn addon<T: Addon>(&self) -> Option<&T> {
20962 let type_id = std::any::TypeId::of::<T>();
20963 self.addons
20964 .get(&type_id)
20965 .and_then(|item| item.to_any().downcast_ref::<T>())
20966 }
20967
20968 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20969 let type_id = std::any::TypeId::of::<T>();
20970 self.addons
20971 .get_mut(&type_id)
20972 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20973 }
20974
20975 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20976 let text_layout_details = self.text_layout_details(window);
20977 let style = &text_layout_details.editor_style;
20978 let font_id = window.text_system().resolve_font(&style.text.font());
20979 let font_size = style.text.font_size.to_pixels(window.rem_size());
20980 let line_height = style.text.line_height_in_pixels(window.rem_size());
20981 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20982 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20983
20984 CharacterDimensions {
20985 em_width,
20986 em_advance,
20987 line_height,
20988 }
20989 }
20990
20991 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20992 self.load_diff_task.clone()
20993 }
20994
20995 fn read_metadata_from_db(
20996 &mut self,
20997 item_id: u64,
20998 workspace_id: WorkspaceId,
20999 window: &mut Window,
21000 cx: &mut Context<Editor>,
21001 ) {
21002 if self.is_singleton(cx)
21003 && !self.mode.is_minimap()
21004 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21005 {
21006 let buffer_snapshot = OnceCell::new();
21007
21008 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21009 if !folds.is_empty() {
21010 let snapshot =
21011 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21012 self.fold_ranges(
21013 folds
21014 .into_iter()
21015 .map(|(start, end)| {
21016 snapshot.clip_offset(start, Bias::Left)
21017 ..snapshot.clip_offset(end, Bias::Right)
21018 })
21019 .collect(),
21020 false,
21021 window,
21022 cx,
21023 );
21024 }
21025 }
21026
21027 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21028 if !selections.is_empty() {
21029 let snapshot =
21030 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21031 // skip adding the initial selection to selection history
21032 self.selection_history.mode = SelectionHistoryMode::Skipping;
21033 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21034 s.select_ranges(selections.into_iter().map(|(start, end)| {
21035 snapshot.clip_offset(start, Bias::Left)
21036 ..snapshot.clip_offset(end, Bias::Right)
21037 }));
21038 });
21039 self.selection_history.mode = SelectionHistoryMode::Normal;
21040 }
21041 };
21042 }
21043
21044 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21045 }
21046
21047 fn update_lsp_data(
21048 &mut self,
21049 ignore_cache: bool,
21050 for_buffer: Option<BufferId>,
21051 window: &mut Window,
21052 cx: &mut Context<'_, Self>,
21053 ) {
21054 self.pull_diagnostics(for_buffer, window, cx);
21055 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21056 }
21057}
21058
21059fn vim_enabled(cx: &App) -> bool {
21060 cx.global::<SettingsStore>()
21061 .raw_user_settings()
21062 .get("vim_mode")
21063 == Some(&serde_json::Value::Bool(true))
21064}
21065
21066fn process_completion_for_edit(
21067 completion: &Completion,
21068 intent: CompletionIntent,
21069 buffer: &Entity<Buffer>,
21070 cursor_position: &text::Anchor,
21071 cx: &mut Context<Editor>,
21072) -> CompletionEdit {
21073 let buffer = buffer.read(cx);
21074 let buffer_snapshot = buffer.snapshot();
21075 let (snippet, new_text) = if completion.is_snippet() {
21076 // Workaround for typescript language server issues so that methods don't expand within
21077 // strings and functions with type expressions. The previous point is used because the query
21078 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21079 let mut snippet_source = completion.new_text.clone();
21080 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21081 previous_point.column = previous_point.column.saturating_sub(1);
21082 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21083 if scope.prefers_label_for_snippet_in_completion() {
21084 if let Some(label) = completion.label() {
21085 if matches!(
21086 completion.kind(),
21087 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21088 ) {
21089 snippet_source = label;
21090 }
21091 }
21092 }
21093 }
21094 match Snippet::parse(&snippet_source).log_err() {
21095 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21096 None => (None, completion.new_text.clone()),
21097 }
21098 } else {
21099 (None, completion.new_text.clone())
21100 };
21101
21102 let mut range_to_replace = {
21103 let replace_range = &completion.replace_range;
21104 if let CompletionSource::Lsp {
21105 insert_range: Some(insert_range),
21106 ..
21107 } = &completion.source
21108 {
21109 debug_assert_eq!(
21110 insert_range.start, replace_range.start,
21111 "insert_range and replace_range should start at the same position"
21112 );
21113 debug_assert!(
21114 insert_range
21115 .start
21116 .cmp(&cursor_position, &buffer_snapshot)
21117 .is_le(),
21118 "insert_range should start before or at cursor position"
21119 );
21120 debug_assert!(
21121 replace_range
21122 .start
21123 .cmp(&cursor_position, &buffer_snapshot)
21124 .is_le(),
21125 "replace_range should start before or at cursor position"
21126 );
21127
21128 let should_replace = match intent {
21129 CompletionIntent::CompleteWithInsert => false,
21130 CompletionIntent::CompleteWithReplace => true,
21131 CompletionIntent::Complete | CompletionIntent::Compose => {
21132 let insert_mode =
21133 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21134 .completions
21135 .lsp_insert_mode;
21136 match insert_mode {
21137 LspInsertMode::Insert => false,
21138 LspInsertMode::Replace => true,
21139 LspInsertMode::ReplaceSubsequence => {
21140 let mut text_to_replace = buffer.chars_for_range(
21141 buffer.anchor_before(replace_range.start)
21142 ..buffer.anchor_after(replace_range.end),
21143 );
21144 let mut current_needle = text_to_replace.next();
21145 for haystack_ch in completion.label.text.chars() {
21146 if let Some(needle_ch) = current_needle {
21147 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21148 current_needle = text_to_replace.next();
21149 }
21150 }
21151 }
21152 current_needle.is_none()
21153 }
21154 LspInsertMode::ReplaceSuffix => {
21155 if replace_range
21156 .end
21157 .cmp(&cursor_position, &buffer_snapshot)
21158 .is_gt()
21159 {
21160 let range_after_cursor = *cursor_position..replace_range.end;
21161 let text_after_cursor = buffer
21162 .text_for_range(
21163 buffer.anchor_before(range_after_cursor.start)
21164 ..buffer.anchor_after(range_after_cursor.end),
21165 )
21166 .collect::<String>()
21167 .to_ascii_lowercase();
21168 completion
21169 .label
21170 .text
21171 .to_ascii_lowercase()
21172 .ends_with(&text_after_cursor)
21173 } else {
21174 true
21175 }
21176 }
21177 }
21178 }
21179 };
21180
21181 if should_replace {
21182 replace_range.clone()
21183 } else {
21184 insert_range.clone()
21185 }
21186 } else {
21187 replace_range.clone()
21188 }
21189 };
21190
21191 if range_to_replace
21192 .end
21193 .cmp(&cursor_position, &buffer_snapshot)
21194 .is_lt()
21195 {
21196 range_to_replace.end = *cursor_position;
21197 }
21198
21199 CompletionEdit {
21200 new_text,
21201 replace_range: range_to_replace.to_offset(&buffer),
21202 snippet,
21203 }
21204}
21205
21206struct CompletionEdit {
21207 new_text: String,
21208 replace_range: Range<usize>,
21209 snippet: Option<Snippet>,
21210}
21211
21212fn insert_extra_newline_brackets(
21213 buffer: &MultiBufferSnapshot,
21214 range: Range<usize>,
21215 language: &language::LanguageScope,
21216) -> bool {
21217 let leading_whitespace_len = buffer
21218 .reversed_chars_at(range.start)
21219 .take_while(|c| c.is_whitespace() && *c != '\n')
21220 .map(|c| c.len_utf8())
21221 .sum::<usize>();
21222 let trailing_whitespace_len = buffer
21223 .chars_at(range.end)
21224 .take_while(|c| c.is_whitespace() && *c != '\n')
21225 .map(|c| c.len_utf8())
21226 .sum::<usize>();
21227 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21228
21229 language.brackets().any(|(pair, enabled)| {
21230 let pair_start = pair.start.trim_end();
21231 let pair_end = pair.end.trim_start();
21232
21233 enabled
21234 && pair.newline
21235 && buffer.contains_str_at(range.end, pair_end)
21236 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21237 })
21238}
21239
21240fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21241 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21242 [(buffer, range, _)] => (*buffer, range.clone()),
21243 _ => return false,
21244 };
21245 let pair = {
21246 let mut result: Option<BracketMatch> = None;
21247
21248 for pair in buffer
21249 .all_bracket_ranges(range.clone())
21250 .filter(move |pair| {
21251 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21252 })
21253 {
21254 let len = pair.close_range.end - pair.open_range.start;
21255
21256 if let Some(existing) = &result {
21257 let existing_len = existing.close_range.end - existing.open_range.start;
21258 if len > existing_len {
21259 continue;
21260 }
21261 }
21262
21263 result = Some(pair);
21264 }
21265
21266 result
21267 };
21268 let Some(pair) = pair else {
21269 return false;
21270 };
21271 pair.newline_only
21272 && buffer
21273 .chars_for_range(pair.open_range.end..range.start)
21274 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21275 .all(|c| c.is_whitespace() && c != '\n')
21276}
21277
21278fn update_uncommitted_diff_for_buffer(
21279 editor: Entity<Editor>,
21280 project: &Entity<Project>,
21281 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21282 buffer: Entity<MultiBuffer>,
21283 cx: &mut App,
21284) -> Task<()> {
21285 let mut tasks = Vec::new();
21286 project.update(cx, |project, cx| {
21287 for buffer in buffers {
21288 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21289 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21290 }
21291 }
21292 });
21293 cx.spawn(async move |cx| {
21294 let diffs = future::join_all(tasks).await;
21295 if editor
21296 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21297 .unwrap_or(false)
21298 {
21299 return;
21300 }
21301
21302 buffer
21303 .update(cx, |buffer, cx| {
21304 for diff in diffs.into_iter().flatten() {
21305 buffer.add_diff(diff, cx);
21306 }
21307 })
21308 .ok();
21309 })
21310}
21311
21312fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21313 let tab_size = tab_size.get() as usize;
21314 let mut width = offset;
21315
21316 for ch in text.chars() {
21317 width += if ch == '\t' {
21318 tab_size - (width % tab_size)
21319 } else {
21320 1
21321 };
21322 }
21323
21324 width - offset
21325}
21326
21327#[cfg(test)]
21328mod tests {
21329 use super::*;
21330
21331 #[test]
21332 fn test_string_size_with_expanded_tabs() {
21333 let nz = |val| NonZeroU32::new(val).unwrap();
21334 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21335 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21336 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21337 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21338 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21339 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21340 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21341 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21342 }
21343}
21344
21345/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21346struct WordBreakingTokenizer<'a> {
21347 input: &'a str,
21348}
21349
21350impl<'a> WordBreakingTokenizer<'a> {
21351 fn new(input: &'a str) -> Self {
21352 Self { input }
21353 }
21354}
21355
21356fn is_char_ideographic(ch: char) -> bool {
21357 use unicode_script::Script::*;
21358 use unicode_script::UnicodeScript;
21359 matches!(ch.script(), Han | Tangut | Yi)
21360}
21361
21362fn is_grapheme_ideographic(text: &str) -> bool {
21363 text.chars().any(is_char_ideographic)
21364}
21365
21366fn is_grapheme_whitespace(text: &str) -> bool {
21367 text.chars().any(|x| x.is_whitespace())
21368}
21369
21370fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21371 text.chars().next().map_or(false, |ch| {
21372 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21373 })
21374}
21375
21376#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21377enum WordBreakToken<'a> {
21378 Word { token: &'a str, grapheme_len: usize },
21379 InlineWhitespace { token: &'a str, grapheme_len: usize },
21380 Newline,
21381}
21382
21383impl<'a> Iterator for WordBreakingTokenizer<'a> {
21384 /// Yields a span, the count of graphemes in the token, and whether it was
21385 /// whitespace. Note that it also breaks at word boundaries.
21386 type Item = WordBreakToken<'a>;
21387
21388 fn next(&mut self) -> Option<Self::Item> {
21389 use unicode_segmentation::UnicodeSegmentation;
21390 if self.input.is_empty() {
21391 return None;
21392 }
21393
21394 let mut iter = self.input.graphemes(true).peekable();
21395 let mut offset = 0;
21396 let mut grapheme_len = 0;
21397 if let Some(first_grapheme) = iter.next() {
21398 let is_newline = first_grapheme == "\n";
21399 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21400 offset += first_grapheme.len();
21401 grapheme_len += 1;
21402 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21403 if let Some(grapheme) = iter.peek().copied() {
21404 if should_stay_with_preceding_ideograph(grapheme) {
21405 offset += grapheme.len();
21406 grapheme_len += 1;
21407 }
21408 }
21409 } else {
21410 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21411 let mut next_word_bound = words.peek().copied();
21412 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21413 next_word_bound = words.next();
21414 }
21415 while let Some(grapheme) = iter.peek().copied() {
21416 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21417 break;
21418 };
21419 if is_grapheme_whitespace(grapheme) != is_whitespace
21420 || (grapheme == "\n") != is_newline
21421 {
21422 break;
21423 };
21424 offset += grapheme.len();
21425 grapheme_len += 1;
21426 iter.next();
21427 }
21428 }
21429 let token = &self.input[..offset];
21430 self.input = &self.input[offset..];
21431 if token == "\n" {
21432 Some(WordBreakToken::Newline)
21433 } else if is_whitespace {
21434 Some(WordBreakToken::InlineWhitespace {
21435 token,
21436 grapheme_len,
21437 })
21438 } else {
21439 Some(WordBreakToken::Word {
21440 token,
21441 grapheme_len,
21442 })
21443 }
21444 } else {
21445 None
21446 }
21447 }
21448}
21449
21450#[test]
21451fn test_word_breaking_tokenizer() {
21452 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21453 ("", &[]),
21454 (" ", &[whitespace(" ", 2)]),
21455 ("Ʒ", &[word("Ʒ", 1)]),
21456 ("Ǽ", &[word("Ǽ", 1)]),
21457 ("⋑", &[word("⋑", 1)]),
21458 ("⋑⋑", &[word("⋑⋑", 2)]),
21459 (
21460 "原理,进而",
21461 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21462 ),
21463 (
21464 "hello world",
21465 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21466 ),
21467 (
21468 "hello, world",
21469 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21470 ),
21471 (
21472 " hello world",
21473 &[
21474 whitespace(" ", 2),
21475 word("hello", 5),
21476 whitespace(" ", 1),
21477 word("world", 5),
21478 ],
21479 ),
21480 (
21481 "这是什么 \n 钢笔",
21482 &[
21483 word("这", 1),
21484 word("是", 1),
21485 word("什", 1),
21486 word("么", 1),
21487 whitespace(" ", 1),
21488 newline(),
21489 whitespace(" ", 1),
21490 word("钢", 1),
21491 word("笔", 1),
21492 ],
21493 ),
21494 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21495 ];
21496
21497 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21498 WordBreakToken::Word {
21499 token,
21500 grapheme_len,
21501 }
21502 }
21503
21504 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21505 WordBreakToken::InlineWhitespace {
21506 token,
21507 grapheme_len,
21508 }
21509 }
21510
21511 fn newline() -> WordBreakToken<'static> {
21512 WordBreakToken::Newline
21513 }
21514
21515 for (input, result) in tests {
21516 assert_eq!(
21517 WordBreakingTokenizer::new(input)
21518 .collect::<Vec<_>>()
21519 .as_slice(),
21520 *result,
21521 );
21522 }
21523}
21524
21525fn wrap_with_prefix(
21526 first_line_prefix: String,
21527 subsequent_lines_prefix: String,
21528 unwrapped_text: String,
21529 wrap_column: usize,
21530 tab_size: NonZeroU32,
21531 preserve_existing_whitespace: bool,
21532) -> String {
21533 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21534 let subsequent_lines_prefix_len =
21535 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21536 let mut wrapped_text = String::new();
21537 let mut current_line = first_line_prefix.clone();
21538 let mut is_first_line = true;
21539
21540 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21541 let mut current_line_len = first_line_prefix_len;
21542 let mut in_whitespace = false;
21543 for token in tokenizer {
21544 let have_preceding_whitespace = in_whitespace;
21545 match token {
21546 WordBreakToken::Word {
21547 token,
21548 grapheme_len,
21549 } => {
21550 in_whitespace = false;
21551 let current_prefix_len = if is_first_line {
21552 first_line_prefix_len
21553 } else {
21554 subsequent_lines_prefix_len
21555 };
21556 if current_line_len + grapheme_len > wrap_column
21557 && current_line_len != current_prefix_len
21558 {
21559 wrapped_text.push_str(current_line.trim_end());
21560 wrapped_text.push('\n');
21561 is_first_line = false;
21562 current_line = subsequent_lines_prefix.clone();
21563 current_line_len = subsequent_lines_prefix_len;
21564 }
21565 current_line.push_str(token);
21566 current_line_len += grapheme_len;
21567 }
21568 WordBreakToken::InlineWhitespace {
21569 mut token,
21570 mut grapheme_len,
21571 } => {
21572 in_whitespace = true;
21573 if have_preceding_whitespace && !preserve_existing_whitespace {
21574 continue;
21575 }
21576 if !preserve_existing_whitespace {
21577 token = " ";
21578 grapheme_len = 1;
21579 }
21580 let current_prefix_len = if is_first_line {
21581 first_line_prefix_len
21582 } else {
21583 subsequent_lines_prefix_len
21584 };
21585 if current_line_len + grapheme_len > wrap_column {
21586 wrapped_text.push_str(current_line.trim_end());
21587 wrapped_text.push('\n');
21588 is_first_line = false;
21589 current_line = subsequent_lines_prefix.clone();
21590 current_line_len = subsequent_lines_prefix_len;
21591 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21592 current_line.push_str(token);
21593 current_line_len += grapheme_len;
21594 }
21595 }
21596 WordBreakToken::Newline => {
21597 in_whitespace = true;
21598 let current_prefix_len = if is_first_line {
21599 first_line_prefix_len
21600 } else {
21601 subsequent_lines_prefix_len
21602 };
21603 if preserve_existing_whitespace {
21604 wrapped_text.push_str(current_line.trim_end());
21605 wrapped_text.push('\n');
21606 is_first_line = false;
21607 current_line = subsequent_lines_prefix.clone();
21608 current_line_len = subsequent_lines_prefix_len;
21609 } else if have_preceding_whitespace {
21610 continue;
21611 } else if current_line_len + 1 > wrap_column
21612 && current_line_len != current_prefix_len
21613 {
21614 wrapped_text.push_str(current_line.trim_end());
21615 wrapped_text.push('\n');
21616 is_first_line = false;
21617 current_line = subsequent_lines_prefix.clone();
21618 current_line_len = subsequent_lines_prefix_len;
21619 } else if current_line_len != current_prefix_len {
21620 current_line.push(' ');
21621 current_line_len += 1;
21622 }
21623 }
21624 }
21625 }
21626
21627 if !current_line.is_empty() {
21628 wrapped_text.push_str(¤t_line);
21629 }
21630 wrapped_text
21631}
21632
21633#[test]
21634fn test_wrap_with_prefix() {
21635 assert_eq!(
21636 wrap_with_prefix(
21637 "# ".to_string(),
21638 "# ".to_string(),
21639 "abcdefg".to_string(),
21640 4,
21641 NonZeroU32::new(4).unwrap(),
21642 false,
21643 ),
21644 "# abcdefg"
21645 );
21646 assert_eq!(
21647 wrap_with_prefix(
21648 "".to_string(),
21649 "".to_string(),
21650 "\thello world".to_string(),
21651 8,
21652 NonZeroU32::new(4).unwrap(),
21653 false,
21654 ),
21655 "hello\nworld"
21656 );
21657 assert_eq!(
21658 wrap_with_prefix(
21659 "// ".to_string(),
21660 "// ".to_string(),
21661 "xx \nyy zz aa bb cc".to_string(),
21662 12,
21663 NonZeroU32::new(4).unwrap(),
21664 false,
21665 ),
21666 "// xx yy zz\n// aa bb cc"
21667 );
21668 assert_eq!(
21669 wrap_with_prefix(
21670 String::new(),
21671 String::new(),
21672 "这是什么 \n 钢笔".to_string(),
21673 3,
21674 NonZeroU32::new(4).unwrap(),
21675 false,
21676 ),
21677 "这是什\n么 钢\n笔"
21678 );
21679}
21680
21681pub trait CollaborationHub {
21682 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21683 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21684 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21685}
21686
21687impl CollaborationHub for Entity<Project> {
21688 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21689 self.read(cx).collaborators()
21690 }
21691
21692 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21693 self.read(cx).user_store().read(cx).participant_indices()
21694 }
21695
21696 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21697 let this = self.read(cx);
21698 let user_ids = this.collaborators().values().map(|c| c.user_id);
21699 this.user_store().read(cx).participant_names(user_ids, cx)
21700 }
21701}
21702
21703pub trait SemanticsProvider {
21704 fn hover(
21705 &self,
21706 buffer: &Entity<Buffer>,
21707 position: text::Anchor,
21708 cx: &mut App,
21709 ) -> Option<Task<Vec<project::Hover>>>;
21710
21711 fn inline_values(
21712 &self,
21713 buffer_handle: Entity<Buffer>,
21714 range: Range<text::Anchor>,
21715 cx: &mut App,
21716 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21717
21718 fn inlay_hints(
21719 &self,
21720 buffer_handle: Entity<Buffer>,
21721 range: Range<text::Anchor>,
21722 cx: &mut App,
21723 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21724
21725 fn resolve_inlay_hint(
21726 &self,
21727 hint: InlayHint,
21728 buffer_handle: Entity<Buffer>,
21729 server_id: LanguageServerId,
21730 cx: &mut App,
21731 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21732
21733 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21734
21735 fn document_highlights(
21736 &self,
21737 buffer: &Entity<Buffer>,
21738 position: text::Anchor,
21739 cx: &mut App,
21740 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21741
21742 fn definitions(
21743 &self,
21744 buffer: &Entity<Buffer>,
21745 position: text::Anchor,
21746 kind: GotoDefinitionKind,
21747 cx: &mut App,
21748 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21749
21750 fn range_for_rename(
21751 &self,
21752 buffer: &Entity<Buffer>,
21753 position: text::Anchor,
21754 cx: &mut App,
21755 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21756
21757 fn perform_rename(
21758 &self,
21759 buffer: &Entity<Buffer>,
21760 position: text::Anchor,
21761 new_name: String,
21762 cx: &mut App,
21763 ) -> Option<Task<Result<ProjectTransaction>>>;
21764}
21765
21766pub trait CompletionProvider {
21767 fn completions(
21768 &self,
21769 excerpt_id: ExcerptId,
21770 buffer: &Entity<Buffer>,
21771 buffer_position: text::Anchor,
21772 trigger: CompletionContext,
21773 window: &mut Window,
21774 cx: &mut Context<Editor>,
21775 ) -> Task<Result<Vec<CompletionResponse>>>;
21776
21777 fn resolve_completions(
21778 &self,
21779 _buffer: Entity<Buffer>,
21780 _completion_indices: Vec<usize>,
21781 _completions: Rc<RefCell<Box<[Completion]>>>,
21782 _cx: &mut Context<Editor>,
21783 ) -> Task<Result<bool>> {
21784 Task::ready(Ok(false))
21785 }
21786
21787 fn apply_additional_edits_for_completion(
21788 &self,
21789 _buffer: Entity<Buffer>,
21790 _completions: Rc<RefCell<Box<[Completion]>>>,
21791 _completion_index: usize,
21792 _push_to_history: bool,
21793 _cx: &mut Context<Editor>,
21794 ) -> Task<Result<Option<language::Transaction>>> {
21795 Task::ready(Ok(None))
21796 }
21797
21798 fn is_completion_trigger(
21799 &self,
21800 buffer: &Entity<Buffer>,
21801 position: language::Anchor,
21802 text: &str,
21803 trigger_in_words: bool,
21804 menu_is_open: bool,
21805 cx: &mut Context<Editor>,
21806 ) -> bool;
21807
21808 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21809
21810 fn sort_completions(&self) -> bool {
21811 true
21812 }
21813
21814 fn filter_completions(&self) -> bool {
21815 true
21816 }
21817}
21818
21819pub trait CodeActionProvider {
21820 fn id(&self) -> Arc<str>;
21821
21822 fn code_actions(
21823 &self,
21824 buffer: &Entity<Buffer>,
21825 range: Range<text::Anchor>,
21826 window: &mut Window,
21827 cx: &mut App,
21828 ) -> Task<Result<Vec<CodeAction>>>;
21829
21830 fn apply_code_action(
21831 &self,
21832 buffer_handle: Entity<Buffer>,
21833 action: CodeAction,
21834 excerpt_id: ExcerptId,
21835 push_to_history: bool,
21836 window: &mut Window,
21837 cx: &mut App,
21838 ) -> Task<Result<ProjectTransaction>>;
21839}
21840
21841impl CodeActionProvider for Entity<Project> {
21842 fn id(&self) -> Arc<str> {
21843 "project".into()
21844 }
21845
21846 fn code_actions(
21847 &self,
21848 buffer: &Entity<Buffer>,
21849 range: Range<text::Anchor>,
21850 _window: &mut Window,
21851 cx: &mut App,
21852 ) -> Task<Result<Vec<CodeAction>>> {
21853 self.update(cx, |project, cx| {
21854 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21855 let code_actions = project.code_actions(buffer, range, None, cx);
21856 cx.background_spawn(async move {
21857 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21858 Ok(code_lens_actions
21859 .context("code lens fetch")?
21860 .into_iter()
21861 .chain(code_actions.context("code action fetch")?)
21862 .collect())
21863 })
21864 })
21865 }
21866
21867 fn apply_code_action(
21868 &self,
21869 buffer_handle: Entity<Buffer>,
21870 action: CodeAction,
21871 _excerpt_id: ExcerptId,
21872 push_to_history: bool,
21873 _window: &mut Window,
21874 cx: &mut App,
21875 ) -> Task<Result<ProjectTransaction>> {
21876 self.update(cx, |project, cx| {
21877 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21878 })
21879 }
21880}
21881
21882fn snippet_completions(
21883 project: &Project,
21884 buffer: &Entity<Buffer>,
21885 buffer_position: text::Anchor,
21886 cx: &mut App,
21887) -> Task<Result<CompletionResponse>> {
21888 let languages = buffer.read(cx).languages_at(buffer_position);
21889 let snippet_store = project.snippets().read(cx);
21890
21891 let scopes: Vec<_> = languages
21892 .iter()
21893 .filter_map(|language| {
21894 let language_name = language.lsp_id();
21895 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21896
21897 if snippets.is_empty() {
21898 None
21899 } else {
21900 Some((language.default_scope(), snippets))
21901 }
21902 })
21903 .collect();
21904
21905 if scopes.is_empty() {
21906 return Task::ready(Ok(CompletionResponse {
21907 completions: vec![],
21908 is_incomplete: false,
21909 }));
21910 }
21911
21912 let snapshot = buffer.read(cx).text_snapshot();
21913 let chars: String = snapshot
21914 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21915 .collect();
21916 let executor = cx.background_executor().clone();
21917
21918 cx.background_spawn(async move {
21919 let mut is_incomplete = false;
21920 let mut completions: Vec<Completion> = Vec::new();
21921 for (scope, snippets) in scopes.into_iter() {
21922 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21923 let mut last_word = chars
21924 .chars()
21925 .take_while(|c| classifier.is_word(*c))
21926 .collect::<String>();
21927 last_word = last_word.chars().rev().collect();
21928
21929 if last_word.is_empty() {
21930 return Ok(CompletionResponse {
21931 completions: vec![],
21932 is_incomplete: true,
21933 });
21934 }
21935
21936 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21937 let to_lsp = |point: &text::Anchor| {
21938 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21939 point_to_lsp(end)
21940 };
21941 let lsp_end = to_lsp(&buffer_position);
21942
21943 let candidates = snippets
21944 .iter()
21945 .enumerate()
21946 .flat_map(|(ix, snippet)| {
21947 snippet
21948 .prefix
21949 .iter()
21950 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21951 })
21952 .collect::<Vec<StringMatchCandidate>>();
21953
21954 const MAX_RESULTS: usize = 100;
21955 let mut matches = fuzzy::match_strings(
21956 &candidates,
21957 &last_word,
21958 last_word.chars().any(|c| c.is_uppercase()),
21959 true,
21960 MAX_RESULTS,
21961 &Default::default(),
21962 executor.clone(),
21963 )
21964 .await;
21965
21966 if matches.len() >= MAX_RESULTS {
21967 is_incomplete = true;
21968 }
21969
21970 // Remove all candidates where the query's start does not match the start of any word in the candidate
21971 if let Some(query_start) = last_word.chars().next() {
21972 matches.retain(|string_match| {
21973 split_words(&string_match.string).any(|word| {
21974 // Check that the first codepoint of the word as lowercase matches the first
21975 // codepoint of the query as lowercase
21976 word.chars()
21977 .flat_map(|codepoint| codepoint.to_lowercase())
21978 .zip(query_start.to_lowercase())
21979 .all(|(word_cp, query_cp)| word_cp == query_cp)
21980 })
21981 });
21982 }
21983
21984 let matched_strings = matches
21985 .into_iter()
21986 .map(|m| m.string)
21987 .collect::<HashSet<_>>();
21988
21989 completions.extend(snippets.iter().filter_map(|snippet| {
21990 let matching_prefix = snippet
21991 .prefix
21992 .iter()
21993 .find(|prefix| matched_strings.contains(*prefix))?;
21994 let start = as_offset - last_word.len();
21995 let start = snapshot.anchor_before(start);
21996 let range = start..buffer_position;
21997 let lsp_start = to_lsp(&start);
21998 let lsp_range = lsp::Range {
21999 start: lsp_start,
22000 end: lsp_end,
22001 };
22002 Some(Completion {
22003 replace_range: range,
22004 new_text: snippet.body.clone(),
22005 source: CompletionSource::Lsp {
22006 insert_range: None,
22007 server_id: LanguageServerId(usize::MAX),
22008 resolved: true,
22009 lsp_completion: Box::new(lsp::CompletionItem {
22010 label: snippet.prefix.first().unwrap().clone(),
22011 kind: Some(CompletionItemKind::SNIPPET),
22012 label_details: snippet.description.as_ref().map(|description| {
22013 lsp::CompletionItemLabelDetails {
22014 detail: Some(description.clone()),
22015 description: None,
22016 }
22017 }),
22018 insert_text_format: Some(InsertTextFormat::SNIPPET),
22019 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22020 lsp::InsertReplaceEdit {
22021 new_text: snippet.body.clone(),
22022 insert: lsp_range,
22023 replace: lsp_range,
22024 },
22025 )),
22026 filter_text: Some(snippet.body.clone()),
22027 sort_text: Some(char::MAX.to_string()),
22028 ..lsp::CompletionItem::default()
22029 }),
22030 lsp_defaults: None,
22031 },
22032 label: CodeLabel {
22033 text: matching_prefix.clone(),
22034 runs: Vec::new(),
22035 filter_range: 0..matching_prefix.len(),
22036 },
22037 icon_path: None,
22038 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22039 single_line: snippet.name.clone().into(),
22040 plain_text: snippet
22041 .description
22042 .clone()
22043 .map(|description| description.into()),
22044 }),
22045 insert_text_mode: None,
22046 confirm: None,
22047 })
22048 }))
22049 }
22050
22051 Ok(CompletionResponse {
22052 completions,
22053 is_incomplete,
22054 })
22055 })
22056}
22057
22058impl CompletionProvider for Entity<Project> {
22059 fn completions(
22060 &self,
22061 _excerpt_id: ExcerptId,
22062 buffer: &Entity<Buffer>,
22063 buffer_position: text::Anchor,
22064 options: CompletionContext,
22065 _window: &mut Window,
22066 cx: &mut Context<Editor>,
22067 ) -> Task<Result<Vec<CompletionResponse>>> {
22068 self.update(cx, |project, cx| {
22069 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22070 let project_completions = project.completions(buffer, buffer_position, options, cx);
22071 cx.background_spawn(async move {
22072 let mut responses = project_completions.await?;
22073 let snippets = snippets.await?;
22074 if !snippets.completions.is_empty() {
22075 responses.push(snippets);
22076 }
22077 Ok(responses)
22078 })
22079 })
22080 }
22081
22082 fn resolve_completions(
22083 &self,
22084 buffer: Entity<Buffer>,
22085 completion_indices: Vec<usize>,
22086 completions: Rc<RefCell<Box<[Completion]>>>,
22087 cx: &mut Context<Editor>,
22088 ) -> Task<Result<bool>> {
22089 self.update(cx, |project, cx| {
22090 project.lsp_store().update(cx, |lsp_store, cx| {
22091 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22092 })
22093 })
22094 }
22095
22096 fn apply_additional_edits_for_completion(
22097 &self,
22098 buffer: Entity<Buffer>,
22099 completions: Rc<RefCell<Box<[Completion]>>>,
22100 completion_index: usize,
22101 push_to_history: bool,
22102 cx: &mut Context<Editor>,
22103 ) -> Task<Result<Option<language::Transaction>>> {
22104 self.update(cx, |project, cx| {
22105 project.lsp_store().update(cx, |lsp_store, cx| {
22106 lsp_store.apply_additional_edits_for_completion(
22107 buffer,
22108 completions,
22109 completion_index,
22110 push_to_history,
22111 cx,
22112 )
22113 })
22114 })
22115 }
22116
22117 fn is_completion_trigger(
22118 &self,
22119 buffer: &Entity<Buffer>,
22120 position: language::Anchor,
22121 text: &str,
22122 trigger_in_words: bool,
22123 menu_is_open: bool,
22124 cx: &mut Context<Editor>,
22125 ) -> bool {
22126 let mut chars = text.chars();
22127 let char = if let Some(char) = chars.next() {
22128 char
22129 } else {
22130 return false;
22131 };
22132 if chars.next().is_some() {
22133 return false;
22134 }
22135
22136 let buffer = buffer.read(cx);
22137 let snapshot = buffer.snapshot();
22138 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22139 return false;
22140 }
22141 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22142 if trigger_in_words && classifier.is_word(char) {
22143 return true;
22144 }
22145
22146 buffer.completion_triggers().contains(text)
22147 }
22148}
22149
22150impl SemanticsProvider for Entity<Project> {
22151 fn hover(
22152 &self,
22153 buffer: &Entity<Buffer>,
22154 position: text::Anchor,
22155 cx: &mut App,
22156 ) -> Option<Task<Vec<project::Hover>>> {
22157 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22158 }
22159
22160 fn document_highlights(
22161 &self,
22162 buffer: &Entity<Buffer>,
22163 position: text::Anchor,
22164 cx: &mut App,
22165 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22166 Some(self.update(cx, |project, cx| {
22167 project.document_highlights(buffer, position, cx)
22168 }))
22169 }
22170
22171 fn definitions(
22172 &self,
22173 buffer: &Entity<Buffer>,
22174 position: text::Anchor,
22175 kind: GotoDefinitionKind,
22176 cx: &mut App,
22177 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22178 Some(self.update(cx, |project, cx| match kind {
22179 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22180 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22181 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22182 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22183 }))
22184 }
22185
22186 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22187 // TODO: make this work for remote projects
22188 self.update(cx, |project, cx| {
22189 if project
22190 .active_debug_session(cx)
22191 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22192 {
22193 return true;
22194 }
22195
22196 buffer.update(cx, |buffer, cx| {
22197 project.any_language_server_supports_inlay_hints(buffer, cx)
22198 })
22199 })
22200 }
22201
22202 fn inline_values(
22203 &self,
22204 buffer_handle: Entity<Buffer>,
22205 range: Range<text::Anchor>,
22206 cx: &mut App,
22207 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22208 self.update(cx, |project, cx| {
22209 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22210
22211 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22212 })
22213 }
22214
22215 fn inlay_hints(
22216 &self,
22217 buffer_handle: Entity<Buffer>,
22218 range: Range<text::Anchor>,
22219 cx: &mut App,
22220 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22221 Some(self.update(cx, |project, cx| {
22222 project.inlay_hints(buffer_handle, range, cx)
22223 }))
22224 }
22225
22226 fn resolve_inlay_hint(
22227 &self,
22228 hint: InlayHint,
22229 buffer_handle: Entity<Buffer>,
22230 server_id: LanguageServerId,
22231 cx: &mut App,
22232 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22233 Some(self.update(cx, |project, cx| {
22234 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22235 }))
22236 }
22237
22238 fn range_for_rename(
22239 &self,
22240 buffer: &Entity<Buffer>,
22241 position: text::Anchor,
22242 cx: &mut App,
22243 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22244 Some(self.update(cx, |project, cx| {
22245 let buffer = buffer.clone();
22246 let task = project.prepare_rename(buffer.clone(), position, cx);
22247 cx.spawn(async move |_, cx| {
22248 Ok(match task.await? {
22249 PrepareRenameResponse::Success(range) => Some(range),
22250 PrepareRenameResponse::InvalidPosition => None,
22251 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22252 // Fallback on using TreeSitter info to determine identifier range
22253 buffer.read_with(cx, |buffer, _| {
22254 let snapshot = buffer.snapshot();
22255 let (range, kind) = snapshot.surrounding_word(position, false);
22256 if kind != Some(CharKind::Word) {
22257 return None;
22258 }
22259 Some(
22260 snapshot.anchor_before(range.start)
22261 ..snapshot.anchor_after(range.end),
22262 )
22263 })?
22264 }
22265 })
22266 })
22267 }))
22268 }
22269
22270 fn perform_rename(
22271 &self,
22272 buffer: &Entity<Buffer>,
22273 position: text::Anchor,
22274 new_name: String,
22275 cx: &mut App,
22276 ) -> Option<Task<Result<ProjectTransaction>>> {
22277 Some(self.update(cx, |project, cx| {
22278 project.perform_rename(buffer.clone(), position, new_name, cx)
22279 }))
22280 }
22281}
22282
22283fn inlay_hint_settings(
22284 location: Anchor,
22285 snapshot: &MultiBufferSnapshot,
22286 cx: &mut Context<Editor>,
22287) -> InlayHintSettings {
22288 let file = snapshot.file_at(location);
22289 let language = snapshot.language_at(location).map(|l| l.name());
22290 language_settings(language, file, cx).inlay_hints
22291}
22292
22293fn consume_contiguous_rows(
22294 contiguous_row_selections: &mut Vec<Selection<Point>>,
22295 selection: &Selection<Point>,
22296 display_map: &DisplaySnapshot,
22297 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22298) -> (MultiBufferRow, MultiBufferRow) {
22299 contiguous_row_selections.push(selection.clone());
22300 let start_row = starting_row(selection, display_map);
22301 let mut end_row = ending_row(selection, display_map);
22302
22303 while let Some(next_selection) = selections.peek() {
22304 if next_selection.start.row <= end_row.0 {
22305 end_row = ending_row(next_selection, display_map);
22306 contiguous_row_selections.push(selections.next().unwrap().clone());
22307 } else {
22308 break;
22309 }
22310 }
22311 (start_row, end_row)
22312}
22313
22314fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22315 if selection.start.column > 0 {
22316 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22317 } else {
22318 MultiBufferRow(selection.start.row)
22319 }
22320}
22321
22322fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22323 if next_selection.end.column > 0 || next_selection.is_empty() {
22324 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22325 } else {
22326 MultiBufferRow(next_selection.end.row)
22327 }
22328}
22329
22330impl EditorSnapshot {
22331 pub fn remote_selections_in_range<'a>(
22332 &'a self,
22333 range: &'a Range<Anchor>,
22334 collaboration_hub: &dyn CollaborationHub,
22335 cx: &'a App,
22336 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22337 let participant_names = collaboration_hub.user_names(cx);
22338 let participant_indices = collaboration_hub.user_participant_indices(cx);
22339 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22340 let collaborators_by_replica_id = collaborators_by_peer_id
22341 .values()
22342 .map(|collaborator| (collaborator.replica_id, collaborator))
22343 .collect::<HashMap<_, _>>();
22344 self.buffer_snapshot
22345 .selections_in_range(range, false)
22346 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22347 if replica_id == AGENT_REPLICA_ID {
22348 Some(RemoteSelection {
22349 replica_id,
22350 selection,
22351 cursor_shape,
22352 line_mode,
22353 collaborator_id: CollaboratorId::Agent,
22354 user_name: Some("Agent".into()),
22355 color: cx.theme().players().agent(),
22356 })
22357 } else {
22358 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22359 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22360 let user_name = participant_names.get(&collaborator.user_id).cloned();
22361 Some(RemoteSelection {
22362 replica_id,
22363 selection,
22364 cursor_shape,
22365 line_mode,
22366 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22367 user_name,
22368 color: if let Some(index) = participant_index {
22369 cx.theme().players().color_for_participant(index.0)
22370 } else {
22371 cx.theme().players().absent()
22372 },
22373 })
22374 }
22375 })
22376 }
22377
22378 pub fn hunks_for_ranges(
22379 &self,
22380 ranges: impl IntoIterator<Item = Range<Point>>,
22381 ) -> Vec<MultiBufferDiffHunk> {
22382 let mut hunks = Vec::new();
22383 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22384 HashMap::default();
22385 for query_range in ranges {
22386 let query_rows =
22387 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22388 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22389 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22390 ) {
22391 // Include deleted hunks that are adjacent to the query range, because
22392 // otherwise they would be missed.
22393 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22394 if hunk.status().is_deleted() {
22395 intersects_range |= hunk.row_range.start == query_rows.end;
22396 intersects_range |= hunk.row_range.end == query_rows.start;
22397 }
22398 if intersects_range {
22399 if !processed_buffer_rows
22400 .entry(hunk.buffer_id)
22401 .or_default()
22402 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22403 {
22404 continue;
22405 }
22406 hunks.push(hunk);
22407 }
22408 }
22409 }
22410
22411 hunks
22412 }
22413
22414 fn display_diff_hunks_for_rows<'a>(
22415 &'a self,
22416 display_rows: Range<DisplayRow>,
22417 folded_buffers: &'a HashSet<BufferId>,
22418 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22419 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22420 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22421
22422 self.buffer_snapshot
22423 .diff_hunks_in_range(buffer_start..buffer_end)
22424 .filter_map(|hunk| {
22425 if folded_buffers.contains(&hunk.buffer_id) {
22426 return None;
22427 }
22428
22429 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22430 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22431
22432 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22433 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22434
22435 let display_hunk = if hunk_display_start.column() != 0 {
22436 DisplayDiffHunk::Folded {
22437 display_row: hunk_display_start.row(),
22438 }
22439 } else {
22440 let mut end_row = hunk_display_end.row();
22441 if hunk_display_end.column() > 0 {
22442 end_row.0 += 1;
22443 }
22444 let is_created_file = hunk.is_created_file();
22445 DisplayDiffHunk::Unfolded {
22446 status: hunk.status(),
22447 diff_base_byte_range: hunk.diff_base_byte_range,
22448 display_row_range: hunk_display_start.row()..end_row,
22449 multi_buffer_range: Anchor::range_in_buffer(
22450 hunk.excerpt_id,
22451 hunk.buffer_id,
22452 hunk.buffer_range,
22453 ),
22454 is_created_file,
22455 }
22456 };
22457
22458 Some(display_hunk)
22459 })
22460 }
22461
22462 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22463 self.display_snapshot.buffer_snapshot.language_at(position)
22464 }
22465
22466 pub fn is_focused(&self) -> bool {
22467 self.is_focused
22468 }
22469
22470 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22471 self.placeholder_text.as_ref()
22472 }
22473
22474 pub fn scroll_position(&self) -> gpui::Point<f32> {
22475 self.scroll_anchor.scroll_position(&self.display_snapshot)
22476 }
22477
22478 fn gutter_dimensions(
22479 &self,
22480 font_id: FontId,
22481 font_size: Pixels,
22482 max_line_number_width: Pixels,
22483 cx: &App,
22484 ) -> Option<GutterDimensions> {
22485 if !self.show_gutter {
22486 return None;
22487 }
22488
22489 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22490 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22491
22492 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22493 matches!(
22494 ProjectSettings::get_global(cx).git.git_gutter,
22495 Some(GitGutterSetting::TrackedFiles)
22496 )
22497 });
22498 let gutter_settings = EditorSettings::get_global(cx).gutter;
22499 let show_line_numbers = self
22500 .show_line_numbers
22501 .unwrap_or(gutter_settings.line_numbers);
22502 let line_gutter_width = if show_line_numbers {
22503 // Avoid flicker-like gutter resizes when the line number gains another digit by
22504 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22505 let min_width_for_number_on_gutter =
22506 ch_advance * gutter_settings.min_line_number_digits as f32;
22507 max_line_number_width.max(min_width_for_number_on_gutter)
22508 } else {
22509 0.0.into()
22510 };
22511
22512 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22513 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22514
22515 let git_blame_entries_width =
22516 self.git_blame_gutter_max_author_length
22517 .map(|max_author_length| {
22518 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22519 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22520
22521 /// The number of characters to dedicate to gaps and margins.
22522 const SPACING_WIDTH: usize = 4;
22523
22524 let max_char_count = max_author_length.min(renderer.max_author_length())
22525 + ::git::SHORT_SHA_LENGTH
22526 + MAX_RELATIVE_TIMESTAMP.len()
22527 + SPACING_WIDTH;
22528
22529 ch_advance * max_char_count
22530 });
22531
22532 let is_singleton = self.buffer_snapshot.is_singleton();
22533
22534 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22535 left_padding += if !is_singleton {
22536 ch_width * 4.0
22537 } else if show_runnables || show_breakpoints {
22538 ch_width * 3.0
22539 } else if show_git_gutter && show_line_numbers {
22540 ch_width * 2.0
22541 } else if show_git_gutter || show_line_numbers {
22542 ch_width
22543 } else {
22544 px(0.)
22545 };
22546
22547 let shows_folds = is_singleton && gutter_settings.folds;
22548
22549 let right_padding = if shows_folds && show_line_numbers {
22550 ch_width * 4.0
22551 } else if shows_folds || (!is_singleton && show_line_numbers) {
22552 ch_width * 3.0
22553 } else if show_line_numbers {
22554 ch_width
22555 } else {
22556 px(0.)
22557 };
22558
22559 Some(GutterDimensions {
22560 left_padding,
22561 right_padding,
22562 width: line_gutter_width + left_padding + right_padding,
22563 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22564 git_blame_entries_width,
22565 })
22566 }
22567
22568 pub fn render_crease_toggle(
22569 &self,
22570 buffer_row: MultiBufferRow,
22571 row_contains_cursor: bool,
22572 editor: Entity<Editor>,
22573 window: &mut Window,
22574 cx: &mut App,
22575 ) -> Option<AnyElement> {
22576 let folded = self.is_line_folded(buffer_row);
22577 let mut is_foldable = false;
22578
22579 if let Some(crease) = self
22580 .crease_snapshot
22581 .query_row(buffer_row, &self.buffer_snapshot)
22582 {
22583 is_foldable = true;
22584 match crease {
22585 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22586 if let Some(render_toggle) = render_toggle {
22587 let toggle_callback =
22588 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22589 if folded {
22590 editor.update(cx, |editor, cx| {
22591 editor.fold_at(buffer_row, window, cx)
22592 });
22593 } else {
22594 editor.update(cx, |editor, cx| {
22595 editor.unfold_at(buffer_row, window, cx)
22596 });
22597 }
22598 });
22599 return Some((render_toggle)(
22600 buffer_row,
22601 folded,
22602 toggle_callback,
22603 window,
22604 cx,
22605 ));
22606 }
22607 }
22608 }
22609 }
22610
22611 is_foldable |= self.starts_indent(buffer_row);
22612
22613 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22614 Some(
22615 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22616 .toggle_state(folded)
22617 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22618 if folded {
22619 this.unfold_at(buffer_row, window, cx);
22620 } else {
22621 this.fold_at(buffer_row, window, cx);
22622 }
22623 }))
22624 .into_any_element(),
22625 )
22626 } else {
22627 None
22628 }
22629 }
22630
22631 pub fn render_crease_trailer(
22632 &self,
22633 buffer_row: MultiBufferRow,
22634 window: &mut Window,
22635 cx: &mut App,
22636 ) -> Option<AnyElement> {
22637 let folded = self.is_line_folded(buffer_row);
22638 if let Crease::Inline { render_trailer, .. } = self
22639 .crease_snapshot
22640 .query_row(buffer_row, &self.buffer_snapshot)?
22641 {
22642 let render_trailer = render_trailer.as_ref()?;
22643 Some(render_trailer(buffer_row, folded, window, cx))
22644 } else {
22645 None
22646 }
22647 }
22648}
22649
22650impl Deref for EditorSnapshot {
22651 type Target = DisplaySnapshot;
22652
22653 fn deref(&self) -> &Self::Target {
22654 &self.display_snapshot
22655 }
22656}
22657
22658#[derive(Clone, Debug, PartialEq, Eq)]
22659pub enum EditorEvent {
22660 InputIgnored {
22661 text: Arc<str>,
22662 },
22663 InputHandled {
22664 utf16_range_to_replace: Option<Range<isize>>,
22665 text: Arc<str>,
22666 },
22667 ExcerptsAdded {
22668 buffer: Entity<Buffer>,
22669 predecessor: ExcerptId,
22670 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22671 },
22672 ExcerptsRemoved {
22673 ids: Vec<ExcerptId>,
22674 removed_buffer_ids: Vec<BufferId>,
22675 },
22676 BufferFoldToggled {
22677 ids: Vec<ExcerptId>,
22678 folded: bool,
22679 },
22680 ExcerptsEdited {
22681 ids: Vec<ExcerptId>,
22682 },
22683 ExcerptsExpanded {
22684 ids: Vec<ExcerptId>,
22685 },
22686 BufferEdited,
22687 Edited {
22688 transaction_id: clock::Lamport,
22689 },
22690 Reparsed(BufferId),
22691 Focused,
22692 FocusedIn,
22693 Blurred,
22694 DirtyChanged,
22695 Saved,
22696 TitleChanged,
22697 DiffBaseChanged,
22698 SelectionsChanged {
22699 local: bool,
22700 },
22701 ScrollPositionChanged {
22702 local: bool,
22703 autoscroll: bool,
22704 },
22705 Closed,
22706 TransactionUndone {
22707 transaction_id: clock::Lamport,
22708 },
22709 TransactionBegun {
22710 transaction_id: clock::Lamport,
22711 },
22712 Reloaded,
22713 CursorShapeChanged,
22714 PushedToNavHistory {
22715 anchor: Anchor,
22716 is_deactivate: bool,
22717 },
22718}
22719
22720impl EventEmitter<EditorEvent> for Editor {}
22721
22722impl Focusable for Editor {
22723 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22724 self.focus_handle.clone()
22725 }
22726}
22727
22728impl Render for Editor {
22729 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22730 let settings = ThemeSettings::get_global(cx);
22731
22732 let mut text_style = match self.mode {
22733 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22734 color: cx.theme().colors().editor_foreground,
22735 font_family: settings.ui_font.family.clone(),
22736 font_features: settings.ui_font.features.clone(),
22737 font_fallbacks: settings.ui_font.fallbacks.clone(),
22738 font_size: rems(0.875).into(),
22739 font_weight: settings.ui_font.weight,
22740 line_height: relative(settings.buffer_line_height.value()),
22741 ..Default::default()
22742 },
22743 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22744 color: cx.theme().colors().editor_foreground,
22745 font_family: settings.buffer_font.family.clone(),
22746 font_features: settings.buffer_font.features.clone(),
22747 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22748 font_size: settings.buffer_font_size(cx).into(),
22749 font_weight: settings.buffer_font.weight,
22750 line_height: relative(settings.buffer_line_height.value()),
22751 ..Default::default()
22752 },
22753 };
22754 if let Some(text_style_refinement) = &self.text_style_refinement {
22755 text_style.refine(text_style_refinement)
22756 }
22757
22758 let background = match self.mode {
22759 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22760 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22761 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22762 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22763 };
22764
22765 EditorElement::new(
22766 &cx.entity(),
22767 EditorStyle {
22768 background,
22769 border: cx.theme().colors().border,
22770 local_player: cx.theme().players().local(),
22771 text: text_style,
22772 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22773 syntax: cx.theme().syntax().clone(),
22774 status: cx.theme().status().clone(),
22775 inlay_hints_style: make_inlay_hints_style(cx),
22776 inline_completion_styles: make_suggestion_styles(cx),
22777 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22778 show_underlines: self.diagnostics_enabled(),
22779 },
22780 )
22781 }
22782}
22783
22784impl EntityInputHandler for Editor {
22785 fn text_for_range(
22786 &mut self,
22787 range_utf16: Range<usize>,
22788 adjusted_range: &mut Option<Range<usize>>,
22789 _: &mut Window,
22790 cx: &mut Context<Self>,
22791 ) -> Option<String> {
22792 let snapshot = self.buffer.read(cx).read(cx);
22793 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22794 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22795 if (start.0..end.0) != range_utf16 {
22796 adjusted_range.replace(start.0..end.0);
22797 }
22798 Some(snapshot.text_for_range(start..end).collect())
22799 }
22800
22801 fn selected_text_range(
22802 &mut self,
22803 ignore_disabled_input: bool,
22804 _: &mut Window,
22805 cx: &mut Context<Self>,
22806 ) -> Option<UTF16Selection> {
22807 // Prevent the IME menu from appearing when holding down an alphabetic key
22808 // while input is disabled.
22809 if !ignore_disabled_input && !self.input_enabled {
22810 return None;
22811 }
22812
22813 let selection = self.selections.newest::<OffsetUtf16>(cx);
22814 let range = selection.range();
22815
22816 Some(UTF16Selection {
22817 range: range.start.0..range.end.0,
22818 reversed: selection.reversed,
22819 })
22820 }
22821
22822 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22823 let snapshot = self.buffer.read(cx).read(cx);
22824 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22825 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22826 }
22827
22828 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22829 self.clear_highlights::<InputComposition>(cx);
22830 self.ime_transaction.take();
22831 }
22832
22833 fn replace_text_in_range(
22834 &mut self,
22835 range_utf16: Option<Range<usize>>,
22836 text: &str,
22837 window: &mut Window,
22838 cx: &mut Context<Self>,
22839 ) {
22840 if !self.input_enabled {
22841 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22842 return;
22843 }
22844
22845 self.transact(window, cx, |this, window, cx| {
22846 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22847 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22848 Some(this.selection_replacement_ranges(range_utf16, cx))
22849 } else {
22850 this.marked_text_ranges(cx)
22851 };
22852
22853 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22854 let newest_selection_id = this.selections.newest_anchor().id;
22855 this.selections
22856 .all::<OffsetUtf16>(cx)
22857 .iter()
22858 .zip(ranges_to_replace.iter())
22859 .find_map(|(selection, range)| {
22860 if selection.id == newest_selection_id {
22861 Some(
22862 (range.start.0 as isize - selection.head().0 as isize)
22863 ..(range.end.0 as isize - selection.head().0 as isize),
22864 )
22865 } else {
22866 None
22867 }
22868 })
22869 });
22870
22871 cx.emit(EditorEvent::InputHandled {
22872 utf16_range_to_replace: range_to_replace,
22873 text: text.into(),
22874 });
22875
22876 if let Some(new_selected_ranges) = new_selected_ranges {
22877 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22878 selections.select_ranges(new_selected_ranges)
22879 });
22880 this.backspace(&Default::default(), window, cx);
22881 }
22882
22883 this.handle_input(text, window, cx);
22884 });
22885
22886 if let Some(transaction) = self.ime_transaction {
22887 self.buffer.update(cx, |buffer, cx| {
22888 buffer.group_until_transaction(transaction, cx);
22889 });
22890 }
22891
22892 self.unmark_text(window, cx);
22893 }
22894
22895 fn replace_and_mark_text_in_range(
22896 &mut self,
22897 range_utf16: Option<Range<usize>>,
22898 text: &str,
22899 new_selected_range_utf16: Option<Range<usize>>,
22900 window: &mut Window,
22901 cx: &mut Context<Self>,
22902 ) {
22903 if !self.input_enabled {
22904 return;
22905 }
22906
22907 let transaction = self.transact(window, cx, |this, window, cx| {
22908 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22909 let snapshot = this.buffer.read(cx).read(cx);
22910 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22911 for marked_range in &mut marked_ranges {
22912 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22913 marked_range.start.0 += relative_range_utf16.start;
22914 marked_range.start =
22915 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22916 marked_range.end =
22917 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22918 }
22919 }
22920 Some(marked_ranges)
22921 } else if let Some(range_utf16) = range_utf16 {
22922 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22923 Some(this.selection_replacement_ranges(range_utf16, cx))
22924 } else {
22925 None
22926 };
22927
22928 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22929 let newest_selection_id = this.selections.newest_anchor().id;
22930 this.selections
22931 .all::<OffsetUtf16>(cx)
22932 .iter()
22933 .zip(ranges_to_replace.iter())
22934 .find_map(|(selection, range)| {
22935 if selection.id == newest_selection_id {
22936 Some(
22937 (range.start.0 as isize - selection.head().0 as isize)
22938 ..(range.end.0 as isize - selection.head().0 as isize),
22939 )
22940 } else {
22941 None
22942 }
22943 })
22944 });
22945
22946 cx.emit(EditorEvent::InputHandled {
22947 utf16_range_to_replace: range_to_replace,
22948 text: text.into(),
22949 });
22950
22951 if let Some(ranges) = ranges_to_replace {
22952 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22953 s.select_ranges(ranges)
22954 });
22955 }
22956
22957 let marked_ranges = {
22958 let snapshot = this.buffer.read(cx).read(cx);
22959 this.selections
22960 .disjoint_anchors()
22961 .iter()
22962 .map(|selection| {
22963 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22964 })
22965 .collect::<Vec<_>>()
22966 };
22967
22968 if text.is_empty() {
22969 this.unmark_text(window, cx);
22970 } else {
22971 this.highlight_text::<InputComposition>(
22972 marked_ranges.clone(),
22973 HighlightStyle {
22974 underline: Some(UnderlineStyle {
22975 thickness: px(1.),
22976 color: None,
22977 wavy: false,
22978 }),
22979 ..Default::default()
22980 },
22981 cx,
22982 );
22983 }
22984
22985 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22986 let use_autoclose = this.use_autoclose;
22987 let use_auto_surround = this.use_auto_surround;
22988 this.set_use_autoclose(false);
22989 this.set_use_auto_surround(false);
22990 this.handle_input(text, window, cx);
22991 this.set_use_autoclose(use_autoclose);
22992 this.set_use_auto_surround(use_auto_surround);
22993
22994 if let Some(new_selected_range) = new_selected_range_utf16 {
22995 let snapshot = this.buffer.read(cx).read(cx);
22996 let new_selected_ranges = marked_ranges
22997 .into_iter()
22998 .map(|marked_range| {
22999 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23000 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23001 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23002 snapshot.clip_offset_utf16(new_start, Bias::Left)
23003 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23004 })
23005 .collect::<Vec<_>>();
23006
23007 drop(snapshot);
23008 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23009 selections.select_ranges(new_selected_ranges)
23010 });
23011 }
23012 });
23013
23014 self.ime_transaction = self.ime_transaction.or(transaction);
23015 if let Some(transaction) = self.ime_transaction {
23016 self.buffer.update(cx, |buffer, cx| {
23017 buffer.group_until_transaction(transaction, cx);
23018 });
23019 }
23020
23021 if self.text_highlights::<InputComposition>(cx).is_none() {
23022 self.ime_transaction.take();
23023 }
23024 }
23025
23026 fn bounds_for_range(
23027 &mut self,
23028 range_utf16: Range<usize>,
23029 element_bounds: gpui::Bounds<Pixels>,
23030 window: &mut Window,
23031 cx: &mut Context<Self>,
23032 ) -> Option<gpui::Bounds<Pixels>> {
23033 let text_layout_details = self.text_layout_details(window);
23034 let CharacterDimensions {
23035 em_width,
23036 em_advance,
23037 line_height,
23038 } = self.character_dimensions(window);
23039
23040 let snapshot = self.snapshot(window, cx);
23041 let scroll_position = snapshot.scroll_position();
23042 let scroll_left = scroll_position.x * em_advance;
23043
23044 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23045 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23046 + self.gutter_dimensions.full_width();
23047 let y = line_height * (start.row().as_f32() - scroll_position.y);
23048
23049 Some(Bounds {
23050 origin: element_bounds.origin + point(x, y),
23051 size: size(em_width, line_height),
23052 })
23053 }
23054
23055 fn character_index_for_point(
23056 &mut self,
23057 point: gpui::Point<Pixels>,
23058 _window: &mut Window,
23059 _cx: &mut Context<Self>,
23060 ) -> Option<usize> {
23061 let position_map = self.last_position_map.as_ref()?;
23062 if !position_map.text_hitbox.contains(&point) {
23063 return None;
23064 }
23065 let display_point = position_map.point_for_position(point).previous_valid;
23066 let anchor = position_map
23067 .snapshot
23068 .display_point_to_anchor(display_point, Bias::Left);
23069 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23070 Some(utf16_offset.0)
23071 }
23072}
23073
23074trait SelectionExt {
23075 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23076 fn spanned_rows(
23077 &self,
23078 include_end_if_at_line_start: bool,
23079 map: &DisplaySnapshot,
23080 ) -> Range<MultiBufferRow>;
23081}
23082
23083impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23084 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23085 let start = self
23086 .start
23087 .to_point(&map.buffer_snapshot)
23088 .to_display_point(map);
23089 let end = self
23090 .end
23091 .to_point(&map.buffer_snapshot)
23092 .to_display_point(map);
23093 if self.reversed {
23094 end..start
23095 } else {
23096 start..end
23097 }
23098 }
23099
23100 fn spanned_rows(
23101 &self,
23102 include_end_if_at_line_start: bool,
23103 map: &DisplaySnapshot,
23104 ) -> Range<MultiBufferRow> {
23105 let start = self.start.to_point(&map.buffer_snapshot);
23106 let mut end = self.end.to_point(&map.buffer_snapshot);
23107 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23108 end.row -= 1;
23109 }
23110
23111 let buffer_start = map.prev_line_boundary(start).0;
23112 let buffer_end = map.next_line_boundary(end).0;
23113 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23114 }
23115}
23116
23117impl<T: InvalidationRegion> InvalidationStack<T> {
23118 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23119 where
23120 S: Clone + ToOffset,
23121 {
23122 while let Some(region) = self.last() {
23123 let all_selections_inside_invalidation_ranges =
23124 if selections.len() == region.ranges().len() {
23125 selections
23126 .iter()
23127 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23128 .all(|(selection, invalidation_range)| {
23129 let head = selection.head().to_offset(buffer);
23130 invalidation_range.start <= head && invalidation_range.end >= head
23131 })
23132 } else {
23133 false
23134 };
23135
23136 if all_selections_inside_invalidation_ranges {
23137 break;
23138 } else {
23139 self.pop();
23140 }
23141 }
23142 }
23143}
23144
23145impl<T> Default for InvalidationStack<T> {
23146 fn default() -> Self {
23147 Self(Default::default())
23148 }
23149}
23150
23151impl<T> Deref for InvalidationStack<T> {
23152 type Target = Vec<T>;
23153
23154 fn deref(&self) -> &Self::Target {
23155 &self.0
23156 }
23157}
23158
23159impl<T> DerefMut for InvalidationStack<T> {
23160 fn deref_mut(&mut self) -> &mut Self::Target {
23161 &mut self.0
23162 }
23163}
23164
23165impl InvalidationRegion for SnippetState {
23166 fn ranges(&self) -> &[Range<Anchor>] {
23167 &self.ranges[self.active_index]
23168 }
23169}
23170
23171fn inline_completion_edit_text(
23172 current_snapshot: &BufferSnapshot,
23173 edits: &[(Range<Anchor>, String)],
23174 edit_preview: &EditPreview,
23175 include_deletions: bool,
23176 cx: &App,
23177) -> HighlightedText {
23178 let edits = edits
23179 .iter()
23180 .map(|(anchor, text)| {
23181 (
23182 anchor.start.text_anchor..anchor.end.text_anchor,
23183 text.clone(),
23184 )
23185 })
23186 .collect::<Vec<_>>();
23187
23188 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23189}
23190
23191pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23192 match severity {
23193 lsp::DiagnosticSeverity::ERROR => colors.error,
23194 lsp::DiagnosticSeverity::WARNING => colors.warning,
23195 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23196 lsp::DiagnosticSeverity::HINT => colors.info,
23197 _ => colors.ignored,
23198 }
23199}
23200
23201pub fn styled_runs_for_code_label<'a>(
23202 label: &'a CodeLabel,
23203 syntax_theme: &'a theme::SyntaxTheme,
23204) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23205 let fade_out = HighlightStyle {
23206 fade_out: Some(0.35),
23207 ..Default::default()
23208 };
23209
23210 let mut prev_end = label.filter_range.end;
23211 label
23212 .runs
23213 .iter()
23214 .enumerate()
23215 .flat_map(move |(ix, (range, highlight_id))| {
23216 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23217 style
23218 } else {
23219 return Default::default();
23220 };
23221 let mut muted_style = style;
23222 muted_style.highlight(fade_out);
23223
23224 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23225 if range.start >= label.filter_range.end {
23226 if range.start > prev_end {
23227 runs.push((prev_end..range.start, fade_out));
23228 }
23229 runs.push((range.clone(), muted_style));
23230 } else if range.end <= label.filter_range.end {
23231 runs.push((range.clone(), style));
23232 } else {
23233 runs.push((range.start..label.filter_range.end, style));
23234 runs.push((label.filter_range.end..range.end, muted_style));
23235 }
23236 prev_end = cmp::max(prev_end, range.end);
23237
23238 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23239 runs.push((prev_end..label.text.len(), fade_out));
23240 }
23241
23242 runs
23243 })
23244}
23245
23246pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23247 let mut prev_index = 0;
23248 let mut prev_codepoint: Option<char> = None;
23249 text.char_indices()
23250 .chain([(text.len(), '\0')])
23251 .filter_map(move |(index, codepoint)| {
23252 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23253 let is_boundary = index == text.len()
23254 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23255 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23256 if is_boundary {
23257 let chunk = &text[prev_index..index];
23258 prev_index = index;
23259 Some(chunk)
23260 } else {
23261 None
23262 }
23263 })
23264}
23265
23266pub trait RangeToAnchorExt: Sized {
23267 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23268
23269 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23270 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23271 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23272 }
23273}
23274
23275impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23276 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23277 let start_offset = self.start.to_offset(snapshot);
23278 let end_offset = self.end.to_offset(snapshot);
23279 if start_offset == end_offset {
23280 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23281 } else {
23282 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23283 }
23284 }
23285}
23286
23287pub trait RowExt {
23288 fn as_f32(&self) -> f32;
23289
23290 fn next_row(&self) -> Self;
23291
23292 fn previous_row(&self) -> Self;
23293
23294 fn minus(&self, other: Self) -> u32;
23295}
23296
23297impl RowExt for DisplayRow {
23298 fn as_f32(&self) -> f32 {
23299 self.0 as f32
23300 }
23301
23302 fn next_row(&self) -> Self {
23303 Self(self.0 + 1)
23304 }
23305
23306 fn previous_row(&self) -> Self {
23307 Self(self.0.saturating_sub(1))
23308 }
23309
23310 fn minus(&self, other: Self) -> u32 {
23311 self.0 - other.0
23312 }
23313}
23314
23315impl RowExt for MultiBufferRow {
23316 fn as_f32(&self) -> f32 {
23317 self.0 as f32
23318 }
23319
23320 fn next_row(&self) -> Self {
23321 Self(self.0 + 1)
23322 }
23323
23324 fn previous_row(&self) -> Self {
23325 Self(self.0.saturating_sub(1))
23326 }
23327
23328 fn minus(&self, other: Self) -> u32 {
23329 self.0 - other.0
23330 }
23331}
23332
23333trait RowRangeExt {
23334 type Row;
23335
23336 fn len(&self) -> usize;
23337
23338 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23339}
23340
23341impl RowRangeExt for Range<MultiBufferRow> {
23342 type Row = MultiBufferRow;
23343
23344 fn len(&self) -> usize {
23345 (self.end.0 - self.start.0) as usize
23346 }
23347
23348 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23349 (self.start.0..self.end.0).map(MultiBufferRow)
23350 }
23351}
23352
23353impl RowRangeExt for Range<DisplayRow> {
23354 type Row = DisplayRow;
23355
23356 fn len(&self) -> usize {
23357 (self.end.0 - self.start.0) as usize
23358 }
23359
23360 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23361 (self.start.0..self.end.0).map(DisplayRow)
23362 }
23363}
23364
23365/// If select range has more than one line, we
23366/// just point the cursor to range.start.
23367fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23368 if range.start.row == range.end.row {
23369 range
23370 } else {
23371 range.start..range.start
23372 }
23373}
23374pub struct KillRing(ClipboardItem);
23375impl Global for KillRing {}
23376
23377const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23378
23379enum BreakpointPromptEditAction {
23380 Log,
23381 Condition,
23382 HitCondition,
23383}
23384
23385struct BreakpointPromptEditor {
23386 pub(crate) prompt: Entity<Editor>,
23387 editor: WeakEntity<Editor>,
23388 breakpoint_anchor: Anchor,
23389 breakpoint: Breakpoint,
23390 edit_action: BreakpointPromptEditAction,
23391 block_ids: HashSet<CustomBlockId>,
23392 editor_margins: Arc<Mutex<EditorMargins>>,
23393 _subscriptions: Vec<Subscription>,
23394}
23395
23396impl BreakpointPromptEditor {
23397 const MAX_LINES: u8 = 4;
23398
23399 fn new(
23400 editor: WeakEntity<Editor>,
23401 breakpoint_anchor: Anchor,
23402 breakpoint: Breakpoint,
23403 edit_action: BreakpointPromptEditAction,
23404 window: &mut Window,
23405 cx: &mut Context<Self>,
23406 ) -> Self {
23407 let base_text = match edit_action {
23408 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23409 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23410 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23411 }
23412 .map(|msg| msg.to_string())
23413 .unwrap_or_default();
23414
23415 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23416 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23417
23418 let prompt = cx.new(|cx| {
23419 let mut prompt = Editor::new(
23420 EditorMode::AutoHeight {
23421 min_lines: 1,
23422 max_lines: Some(Self::MAX_LINES as usize),
23423 },
23424 buffer,
23425 None,
23426 window,
23427 cx,
23428 );
23429 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23430 prompt.set_show_cursor_when_unfocused(false, cx);
23431 prompt.set_placeholder_text(
23432 match edit_action {
23433 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23434 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23435 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23436 },
23437 cx,
23438 );
23439
23440 prompt
23441 });
23442
23443 Self {
23444 prompt,
23445 editor,
23446 breakpoint_anchor,
23447 breakpoint,
23448 edit_action,
23449 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23450 block_ids: Default::default(),
23451 _subscriptions: vec![],
23452 }
23453 }
23454
23455 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23456 self.block_ids.extend(block_ids)
23457 }
23458
23459 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23460 if let Some(editor) = self.editor.upgrade() {
23461 let message = self
23462 .prompt
23463 .read(cx)
23464 .buffer
23465 .read(cx)
23466 .as_singleton()
23467 .expect("A multi buffer in breakpoint prompt isn't possible")
23468 .read(cx)
23469 .as_rope()
23470 .to_string();
23471
23472 editor.update(cx, |editor, cx| {
23473 editor.edit_breakpoint_at_anchor(
23474 self.breakpoint_anchor,
23475 self.breakpoint.clone(),
23476 match self.edit_action {
23477 BreakpointPromptEditAction::Log => {
23478 BreakpointEditAction::EditLogMessage(message.into())
23479 }
23480 BreakpointPromptEditAction::Condition => {
23481 BreakpointEditAction::EditCondition(message.into())
23482 }
23483 BreakpointPromptEditAction::HitCondition => {
23484 BreakpointEditAction::EditHitCondition(message.into())
23485 }
23486 },
23487 cx,
23488 );
23489
23490 editor.remove_blocks(self.block_ids.clone(), None, cx);
23491 cx.focus_self(window);
23492 });
23493 }
23494 }
23495
23496 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23497 self.editor
23498 .update(cx, |editor, cx| {
23499 editor.remove_blocks(self.block_ids.clone(), None, cx);
23500 window.focus(&editor.focus_handle);
23501 })
23502 .log_err();
23503 }
23504
23505 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23506 let settings = ThemeSettings::get_global(cx);
23507 let text_style = TextStyle {
23508 color: if self.prompt.read(cx).read_only(cx) {
23509 cx.theme().colors().text_disabled
23510 } else {
23511 cx.theme().colors().text
23512 },
23513 font_family: settings.buffer_font.family.clone(),
23514 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23515 font_size: settings.buffer_font_size(cx).into(),
23516 font_weight: settings.buffer_font.weight,
23517 line_height: relative(settings.buffer_line_height.value()),
23518 ..Default::default()
23519 };
23520 EditorElement::new(
23521 &self.prompt,
23522 EditorStyle {
23523 background: cx.theme().colors().editor_background,
23524 local_player: cx.theme().players().local(),
23525 text: text_style,
23526 ..Default::default()
23527 },
23528 )
23529 }
23530}
23531
23532impl Render for BreakpointPromptEditor {
23533 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23534 let editor_margins = *self.editor_margins.lock();
23535 let gutter_dimensions = editor_margins.gutter;
23536 h_flex()
23537 .key_context("Editor")
23538 .bg(cx.theme().colors().editor_background)
23539 .border_y_1()
23540 .border_color(cx.theme().status().info_border)
23541 .size_full()
23542 .py(window.line_height() / 2.5)
23543 .on_action(cx.listener(Self::confirm))
23544 .on_action(cx.listener(Self::cancel))
23545 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23546 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23547 }
23548}
23549
23550impl Focusable for BreakpointPromptEditor {
23551 fn focus_handle(&self, cx: &App) -> FocusHandle {
23552 self.prompt.focus_handle(cx)
23553 }
23554}
23555
23556fn all_edits_insertions_or_deletions(
23557 edits: &Vec<(Range<Anchor>, String)>,
23558 snapshot: &MultiBufferSnapshot,
23559) -> bool {
23560 let mut all_insertions = true;
23561 let mut all_deletions = true;
23562
23563 for (range, new_text) in edits.iter() {
23564 let range_is_empty = range.to_offset(&snapshot).is_empty();
23565 let text_is_empty = new_text.is_empty();
23566
23567 if range_is_empty != text_is_empty {
23568 if range_is_empty {
23569 all_deletions = false;
23570 } else {
23571 all_insertions = false;
23572 }
23573 } else {
23574 return false;
23575 }
23576
23577 if !all_insertions && !all_deletions {
23578 return false;
23579 }
23580 }
23581 all_insertions || all_deletions
23582}
23583
23584struct MissingEditPredictionKeybindingTooltip;
23585
23586impl Render for MissingEditPredictionKeybindingTooltip {
23587 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23588 ui::tooltip_container(window, cx, |container, _, cx| {
23589 container
23590 .flex_shrink_0()
23591 .max_w_80()
23592 .min_h(rems_from_px(124.))
23593 .justify_between()
23594 .child(
23595 v_flex()
23596 .flex_1()
23597 .text_ui_sm(cx)
23598 .child(Label::new("Conflict with Accept Keybinding"))
23599 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23600 )
23601 .child(
23602 h_flex()
23603 .pb_1()
23604 .gap_1()
23605 .items_end()
23606 .w_full()
23607 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23608 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23609 }))
23610 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23611 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23612 })),
23613 )
23614 })
23615 }
23616}
23617
23618#[derive(Debug, Clone, Copy, PartialEq)]
23619pub struct LineHighlight {
23620 pub background: Background,
23621 pub border: Option<gpui::Hsla>,
23622 pub include_gutter: bool,
23623 pub type_id: Option<TypeId>,
23624}
23625
23626struct LineManipulationResult {
23627 pub new_text: String,
23628 pub line_count_before: usize,
23629 pub line_count_after: usize,
23630}
23631
23632fn render_diff_hunk_controls(
23633 row: u32,
23634 status: &DiffHunkStatus,
23635 hunk_range: Range<Anchor>,
23636 is_created_file: bool,
23637 line_height: Pixels,
23638 editor: &Entity<Editor>,
23639 _window: &mut Window,
23640 cx: &mut App,
23641) -> AnyElement {
23642 h_flex()
23643 .h(line_height)
23644 .mr_1()
23645 .gap_1()
23646 .px_0p5()
23647 .pb_1()
23648 .border_x_1()
23649 .border_b_1()
23650 .border_color(cx.theme().colors().border_variant)
23651 .rounded_b_lg()
23652 .bg(cx.theme().colors().editor_background)
23653 .gap_1()
23654 .block_mouse_except_scroll()
23655 .shadow_md()
23656 .child(if status.has_secondary_hunk() {
23657 Button::new(("stage", row as u64), "Stage")
23658 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23659 .tooltip({
23660 let focus_handle = editor.focus_handle(cx);
23661 move |window, cx| {
23662 Tooltip::for_action_in(
23663 "Stage Hunk",
23664 &::git::ToggleStaged,
23665 &focus_handle,
23666 window,
23667 cx,
23668 )
23669 }
23670 })
23671 .on_click({
23672 let editor = editor.clone();
23673 move |_event, _window, cx| {
23674 editor.update(cx, |editor, cx| {
23675 editor.stage_or_unstage_diff_hunks(
23676 true,
23677 vec![hunk_range.start..hunk_range.start],
23678 cx,
23679 );
23680 });
23681 }
23682 })
23683 } else {
23684 Button::new(("unstage", row as u64), "Unstage")
23685 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23686 .tooltip({
23687 let focus_handle = editor.focus_handle(cx);
23688 move |window, cx| {
23689 Tooltip::for_action_in(
23690 "Unstage Hunk",
23691 &::git::ToggleStaged,
23692 &focus_handle,
23693 window,
23694 cx,
23695 )
23696 }
23697 })
23698 .on_click({
23699 let editor = editor.clone();
23700 move |_event, _window, cx| {
23701 editor.update(cx, |editor, cx| {
23702 editor.stage_or_unstage_diff_hunks(
23703 false,
23704 vec![hunk_range.start..hunk_range.start],
23705 cx,
23706 );
23707 });
23708 }
23709 })
23710 })
23711 .child(
23712 Button::new(("restore", row as u64), "Restore")
23713 .tooltip({
23714 let focus_handle = editor.focus_handle(cx);
23715 move |window, cx| {
23716 Tooltip::for_action_in(
23717 "Restore Hunk",
23718 &::git::Restore,
23719 &focus_handle,
23720 window,
23721 cx,
23722 )
23723 }
23724 })
23725 .on_click({
23726 let editor = editor.clone();
23727 move |_event, window, cx| {
23728 editor.update(cx, |editor, cx| {
23729 let snapshot = editor.snapshot(window, cx);
23730 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23731 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23732 });
23733 }
23734 })
23735 .disabled(is_created_file),
23736 )
23737 .when(
23738 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23739 |el| {
23740 el.child(
23741 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23742 .shape(IconButtonShape::Square)
23743 .icon_size(IconSize::Small)
23744 // .disabled(!has_multiple_hunks)
23745 .tooltip({
23746 let focus_handle = editor.focus_handle(cx);
23747 move |window, cx| {
23748 Tooltip::for_action_in(
23749 "Next Hunk",
23750 &GoToHunk,
23751 &focus_handle,
23752 window,
23753 cx,
23754 )
23755 }
23756 })
23757 .on_click({
23758 let editor = editor.clone();
23759 move |_event, window, cx| {
23760 editor.update(cx, |editor, cx| {
23761 let snapshot = editor.snapshot(window, cx);
23762 let position =
23763 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23764 editor.go_to_hunk_before_or_after_position(
23765 &snapshot,
23766 position,
23767 Direction::Next,
23768 window,
23769 cx,
23770 );
23771 editor.expand_selected_diff_hunks(cx);
23772 });
23773 }
23774 }),
23775 )
23776 .child(
23777 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23778 .shape(IconButtonShape::Square)
23779 .icon_size(IconSize::Small)
23780 // .disabled(!has_multiple_hunks)
23781 .tooltip({
23782 let focus_handle = editor.focus_handle(cx);
23783 move |window, cx| {
23784 Tooltip::for_action_in(
23785 "Previous Hunk",
23786 &GoToPreviousHunk,
23787 &focus_handle,
23788 window,
23789 cx,
23790 )
23791 }
23792 })
23793 .on_click({
23794 let editor = editor.clone();
23795 move |_event, window, cx| {
23796 editor.update(cx, |editor, cx| {
23797 let snapshot = editor.snapshot(window, cx);
23798 let point =
23799 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23800 editor.go_to_hunk_before_or_after_position(
23801 &snapshot,
23802 point,
23803 Direction::Prev,
23804 window,
23805 cx,
23806 );
23807 editor.expand_selected_diff_hunks(cx);
23808 });
23809 }
23810 }),
23811 )
23812 },
23813 )
23814 .into_any_element()
23815}