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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
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)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699#[derive(Clone)]
700pub struct BackgroundHighlight {
701 pub range: Range<Anchor>,
702 pub color_fetcher: fn(&Theme) -> Hsla,
703}
704
705type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
706
707#[derive(Default)]
708struct ScrollbarMarkerState {
709 scrollbar_size: Size<Pixels>,
710 dirty: bool,
711 markers: Arc<[PaintQuad]>,
712 pending_refresh: Option<Task<Result<()>>>,
713}
714
715impl ScrollbarMarkerState {
716 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
717 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
718 }
719}
720
721#[derive(Clone, Copy, PartialEq, Eq)]
722pub enum MinimapVisibility {
723 Disabled,
724 Enabled {
725 /// The configuration currently present in the users settings.
726 setting_configuration: bool,
727 /// Whether to override the currently set visibility from the users setting.
728 toggle_override: bool,
729 },
730}
731
732impl MinimapVisibility {
733 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
734 if mode.is_full() {
735 Self::Enabled {
736 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
737 toggle_override: false,
738 }
739 } else {
740 Self::Disabled
741 }
742 }
743
744 fn hidden(&self) -> Self {
745 match *self {
746 Self::Enabled {
747 setting_configuration,
748 ..
749 } => Self::Enabled {
750 setting_configuration,
751 toggle_override: setting_configuration,
752 },
753 Self::Disabled => Self::Disabled,
754 }
755 }
756
757 fn disabled(&self) -> bool {
758 match *self {
759 Self::Disabled => true,
760 _ => false,
761 }
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn to_any(&self) -> &dyn std::any::Any;
846
847 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
848 None
849 }
850}
851
852/// A set of caret positions, registered when the editor was edited.
853pub struct ChangeList {
854 changes: Vec<Vec<Anchor>>,
855 /// Currently "selected" change.
856 position: Option<usize>,
857}
858
859impl ChangeList {
860 pub fn new() -> Self {
861 Self {
862 changes: Vec::new(),
863 position: None,
864 }
865 }
866
867 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
868 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
869 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
870 if self.changes.is_empty() {
871 return None;
872 }
873
874 let prev = self.position.unwrap_or(self.changes.len());
875 let next = if direction == Direction::Prev {
876 prev.saturating_sub(count)
877 } else {
878 (prev + count).min(self.changes.len() - 1)
879 };
880 self.position = Some(next);
881 self.changes.get(next).map(|anchors| anchors.as_slice())
882 }
883
884 /// Adds a new change to the list, resetting the change list position.
885 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
886 self.position.take();
887 if pop_state {
888 self.changes.pop();
889 }
890 self.changes.push(new_positions.clone());
891 }
892
893 pub fn last(&self) -> Option<&[Anchor]> {
894 self.changes.last().map(|anchors| anchors.as_slice())
895 }
896}
897
898#[derive(Clone)]
899struct InlineBlamePopoverState {
900 scroll_handle: ScrollHandle,
901 commit_message: Option<ParsedCommitMessage>,
902 markdown: Entity<Markdown>,
903}
904
905struct InlineBlamePopover {
906 position: gpui::Point<Pixels>,
907 show_task: Option<Task<()>>,
908 hide_task: Option<Task<()>>,
909 popover_bounds: Option<Bounds<Pixels>>,
910 popover_state: InlineBlamePopoverState,
911}
912
913enum SelectionDragState {
914 /// State when no drag related activity is detected.
915 None,
916 /// State when the mouse is down on a selection that is about to be dragged.
917 ReadyToDrag {
918 selection: Selection<Anchor>,
919 click_position: gpui::Point<Pixels>,
920 },
921 /// State when the mouse is dragging the selection in the editor.
922 Dragging {
923 selection: Selection<Anchor>,
924 drop_cursor: Selection<Anchor>,
925 hide_drop_cursor: bool,
926 },
927}
928
929/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
930/// a breakpoint on them.
931#[derive(Clone, Copy, Debug, PartialEq, Eq)]
932struct PhantomBreakpointIndicator {
933 display_row: DisplayRow,
934 /// There's a small debounce between hovering over the line and showing the indicator.
935 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
936 is_active: bool,
937 collides_with_existing_breakpoint: bool,
938}
939
940/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
941///
942/// See the [module level documentation](self) for more information.
943pub struct Editor {
944 focus_handle: FocusHandle,
945 last_focused_descendant: Option<WeakFocusHandle>,
946 /// The text buffer being edited
947 buffer: Entity<MultiBuffer>,
948 /// Map of how text in the buffer should be displayed.
949 /// Handles soft wraps, folds, fake inlay text insertions, etc.
950 pub display_map: Entity<DisplayMap>,
951 pub selections: SelectionsCollection,
952 pub scroll_manager: ScrollManager,
953 /// When inline assist editors are linked, they all render cursors because
954 /// typing enters text into each of them, even the ones that aren't focused.
955 pub(crate) show_cursor_when_unfocused: bool,
956 columnar_selection_tail: Option<Anchor>,
957 columnar_display_point: Option<DisplayPoint>,
958 add_selections_state: Option<AddSelectionsState>,
959 select_next_state: Option<SelectNextState>,
960 select_prev_state: Option<SelectNextState>,
961 selection_history: SelectionHistory,
962 defer_selection_effects: bool,
963 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
964 autoclose_regions: Vec<AutocloseRegion>,
965 snippet_stack: InvalidationStack<SnippetState>,
966 select_syntax_node_history: SelectSyntaxNodeHistory,
967 ime_transaction: Option<TransactionId>,
968 pub diagnostics_max_severity: DiagnosticSeverity,
969 active_diagnostics: ActiveDiagnostic,
970 show_inline_diagnostics: bool,
971 inline_diagnostics_update: Task<()>,
972 inline_diagnostics_enabled: bool,
973 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
974 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
975 hard_wrap: Option<usize>,
976
977 // TODO: make this a access method
978 pub project: Option<Entity<Project>>,
979 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
980 completion_provider: Option<Rc<dyn CompletionProvider>>,
981 collaboration_hub: Option<Box<dyn CollaborationHub>>,
982 blink_manager: Entity<BlinkManager>,
983 show_cursor_names: bool,
984 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
985 pub show_local_selections: bool,
986 mode: EditorMode,
987 show_breadcrumbs: bool,
988 show_gutter: bool,
989 show_scrollbars: ScrollbarAxes,
990 minimap_visibility: MinimapVisibility,
991 offset_content: bool,
992 disable_expand_excerpt_buttons: bool,
993 show_line_numbers: Option<bool>,
994 use_relative_line_numbers: Option<bool>,
995 show_git_diff_gutter: Option<bool>,
996 show_code_actions: Option<bool>,
997 show_runnables: Option<bool>,
998 show_breakpoints: Option<bool>,
999 show_wrap_guides: Option<bool>,
1000 show_indent_guides: Option<bool>,
1001 placeholder_text: Option<Arc<str>>,
1002 highlight_order: usize,
1003 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1004 background_highlights: TreeMap<TypeId, Vec<BackgroundHighlight>>,
1005 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1006 scrollbar_marker_state: ScrollbarMarkerState,
1007 active_indent_guides_state: ActiveIndentGuidesState,
1008 nav_history: Option<ItemNavHistory>,
1009 context_menu: RefCell<Option<CodeContextMenu>>,
1010 context_menu_options: Option<ContextMenuOptions>,
1011 mouse_context_menu: Option<MouseContextMenu>,
1012 completion_tasks: Vec<(CompletionId, Task<()>)>,
1013 inline_blame_popover: Option<InlineBlamePopover>,
1014 signature_help_state: SignatureHelpState,
1015 auto_signature_help: Option<bool>,
1016 find_all_references_task_sources: Vec<Anchor>,
1017 next_completion_id: CompletionId,
1018 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1019 code_actions_task: Option<Task<Result<()>>>,
1020 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1021 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1022 document_highlights_task: Option<Task<()>>,
1023 linked_editing_range_task: Option<Task<Option<()>>>,
1024 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1025 pending_rename: Option<RenameState>,
1026 searchable: bool,
1027 cursor_shape: CursorShape,
1028 current_line_highlight: Option<CurrentLineHighlight>,
1029 collapse_matches: bool,
1030 autoindent_mode: Option<AutoindentMode>,
1031 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1032 input_enabled: bool,
1033 use_modal_editing: bool,
1034 read_only: bool,
1035 leader_id: Option<CollaboratorId>,
1036 remote_id: Option<ViewId>,
1037 pub hover_state: HoverState,
1038 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1039 gutter_hovered: bool,
1040 hovered_link_state: Option<HoveredLinkState>,
1041 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1042 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1043 active_inline_completion: Option<InlineCompletionState>,
1044 /// Used to prevent flickering as the user types while the menu is open
1045 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1046 edit_prediction_settings: EditPredictionSettings,
1047 inline_completions_hidden_for_vim_mode: bool,
1048 show_inline_completions_override: Option<bool>,
1049 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1050 edit_prediction_preview: EditPredictionPreview,
1051 edit_prediction_indent_conflict: bool,
1052 edit_prediction_requires_modifier_in_indent_conflict: bool,
1053 inlay_hint_cache: InlayHintCache,
1054 next_inlay_id: usize,
1055 _subscriptions: Vec<Subscription>,
1056 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1057 gutter_dimensions: GutterDimensions,
1058 style: Option<EditorStyle>,
1059 text_style_refinement: Option<TextStyleRefinement>,
1060 next_editor_action_id: EditorActionId,
1061 editor_actions: Rc<
1062 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1063 >,
1064 use_autoclose: bool,
1065 use_auto_surround: bool,
1066 auto_replace_emoji_shortcode: bool,
1067 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1068 show_git_blame_gutter: bool,
1069 show_git_blame_inline: bool,
1070 show_git_blame_inline_delay_task: Option<Task<()>>,
1071 git_blame_inline_enabled: bool,
1072 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1073 serialize_dirty_buffers: bool,
1074 show_selection_menu: Option<bool>,
1075 blame: Option<Entity<GitBlame>>,
1076 blame_subscription: Option<Subscription>,
1077 custom_context_menu: Option<
1078 Box<
1079 dyn 'static
1080 + Fn(
1081 &mut Self,
1082 DisplayPoint,
1083 &mut Window,
1084 &mut Context<Self>,
1085 ) -> Option<Entity<ui::ContextMenu>>,
1086 >,
1087 >,
1088 last_bounds: Option<Bounds<Pixels>>,
1089 last_position_map: Option<Rc<PositionMap>>,
1090 expect_bounds_change: Option<Bounds<Pixels>>,
1091 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1092 tasks_update_task: Option<Task<()>>,
1093 breakpoint_store: Option<Entity<BreakpointStore>>,
1094 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1095 pull_diagnostics_task: Task<()>,
1096 in_project_search: bool,
1097 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1098 breadcrumb_header: Option<String>,
1099 focused_block: Option<FocusedBlock>,
1100 next_scroll_position: NextScrollCursorCenterTopBottom,
1101 addons: HashMap<TypeId, Box<dyn Addon>>,
1102 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1103 load_diff_task: Option<Shared<Task<()>>>,
1104 /// Whether we are temporarily displaying a diff other than git's
1105 temporary_diff_override: bool,
1106 selection_mark_mode: bool,
1107 toggle_fold_multiple_buffers: Task<()>,
1108 _scroll_cursor_center_top_bottom_task: Task<()>,
1109 serialize_selections: Task<()>,
1110 serialize_folds: Task<()>,
1111 mouse_cursor_hidden: bool,
1112 minimap: Option<Entity<Self>>,
1113 hide_mouse_mode: HideMouseMode,
1114 pub change_list: ChangeList,
1115 inline_value_cache: InlineValueCache,
1116 selection_drag_state: SelectionDragState,
1117 drag_and_drop_selection_enabled: bool,
1118}
1119
1120#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1121enum NextScrollCursorCenterTopBottom {
1122 #[default]
1123 Center,
1124 Top,
1125 Bottom,
1126}
1127
1128impl NextScrollCursorCenterTopBottom {
1129 fn next(&self) -> Self {
1130 match self {
1131 Self::Center => Self::Top,
1132 Self::Top => Self::Bottom,
1133 Self::Bottom => Self::Center,
1134 }
1135 }
1136}
1137
1138#[derive(Clone)]
1139pub struct EditorSnapshot {
1140 pub mode: EditorMode,
1141 show_gutter: bool,
1142 show_line_numbers: Option<bool>,
1143 show_git_diff_gutter: Option<bool>,
1144 show_code_actions: Option<bool>,
1145 show_runnables: Option<bool>,
1146 show_breakpoints: Option<bool>,
1147 git_blame_gutter_max_author_length: Option<usize>,
1148 pub display_snapshot: DisplaySnapshot,
1149 pub placeholder_text: Option<Arc<str>>,
1150 is_focused: bool,
1151 scroll_anchor: ScrollAnchor,
1152 ongoing_scroll: OngoingScroll,
1153 current_line_highlight: CurrentLineHighlight,
1154 gutter_hovered: bool,
1155}
1156
1157#[derive(Default, Debug, Clone, Copy)]
1158pub struct GutterDimensions {
1159 pub left_padding: Pixels,
1160 pub right_padding: Pixels,
1161 pub width: Pixels,
1162 pub margin: Pixels,
1163 pub git_blame_entries_width: Option<Pixels>,
1164}
1165
1166impl GutterDimensions {
1167 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1168 Self {
1169 margin: Self::default_gutter_margin(font_id, font_size, cx),
1170 ..Default::default()
1171 }
1172 }
1173
1174 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1175 -cx.text_system().descent(font_id, font_size)
1176 }
1177 /// The full width of the space taken up by the gutter.
1178 pub fn full_width(&self) -> Pixels {
1179 self.margin + self.width
1180 }
1181
1182 /// The width of the space reserved for the fold indicators,
1183 /// use alongside 'justify_end' and `gutter_width` to
1184 /// right align content with the line numbers
1185 pub fn fold_area_width(&self) -> Pixels {
1186 self.margin + self.right_padding
1187 }
1188}
1189
1190#[derive(Debug)]
1191pub struct RemoteSelection {
1192 pub replica_id: ReplicaId,
1193 pub selection: Selection<Anchor>,
1194 pub cursor_shape: CursorShape,
1195 pub collaborator_id: CollaboratorId,
1196 pub line_mode: bool,
1197 pub user_name: Option<SharedString>,
1198 pub color: PlayerColor,
1199}
1200
1201#[derive(Clone, Debug)]
1202struct SelectionHistoryEntry {
1203 selections: Arc<[Selection<Anchor>]>,
1204 select_next_state: Option<SelectNextState>,
1205 select_prev_state: Option<SelectNextState>,
1206 add_selections_state: Option<AddSelectionsState>,
1207}
1208
1209#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1210enum SelectionHistoryMode {
1211 Normal,
1212 Undoing,
1213 Redoing,
1214 Skipping,
1215}
1216
1217#[derive(Clone, PartialEq, Eq, Hash)]
1218struct HoveredCursor {
1219 replica_id: u16,
1220 selection_id: usize,
1221}
1222
1223impl Default for SelectionHistoryMode {
1224 fn default() -> Self {
1225 Self::Normal
1226 }
1227}
1228
1229struct DeferredSelectionEffectsState {
1230 changed: bool,
1231 should_update_completions: bool,
1232 autoscroll: Option<Autoscroll>,
1233 old_cursor_position: Anchor,
1234 history_entry: SelectionHistoryEntry,
1235}
1236
1237#[derive(Default)]
1238struct SelectionHistory {
1239 #[allow(clippy::type_complexity)]
1240 selections_by_transaction:
1241 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1242 mode: SelectionHistoryMode,
1243 undo_stack: VecDeque<SelectionHistoryEntry>,
1244 redo_stack: VecDeque<SelectionHistoryEntry>,
1245}
1246
1247impl SelectionHistory {
1248 #[track_caller]
1249 fn insert_transaction(
1250 &mut self,
1251 transaction_id: TransactionId,
1252 selections: Arc<[Selection<Anchor>]>,
1253 ) {
1254 if selections.is_empty() {
1255 log::error!(
1256 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1257 std::panic::Location::caller()
1258 );
1259 return;
1260 }
1261 self.selections_by_transaction
1262 .insert(transaction_id, (selections, None));
1263 }
1264
1265 #[allow(clippy::type_complexity)]
1266 fn transaction(
1267 &self,
1268 transaction_id: TransactionId,
1269 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1270 self.selections_by_transaction.get(&transaction_id)
1271 }
1272
1273 #[allow(clippy::type_complexity)]
1274 fn transaction_mut(
1275 &mut self,
1276 transaction_id: TransactionId,
1277 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1278 self.selections_by_transaction.get_mut(&transaction_id)
1279 }
1280
1281 fn push(&mut self, entry: SelectionHistoryEntry) {
1282 if !entry.selections.is_empty() {
1283 match self.mode {
1284 SelectionHistoryMode::Normal => {
1285 self.push_undo(entry);
1286 self.redo_stack.clear();
1287 }
1288 SelectionHistoryMode::Undoing => self.push_redo(entry),
1289 SelectionHistoryMode::Redoing => self.push_undo(entry),
1290 SelectionHistoryMode::Skipping => {}
1291 }
1292 }
1293 }
1294
1295 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1296 if self
1297 .undo_stack
1298 .back()
1299 .map_or(true, |e| e.selections != entry.selections)
1300 {
1301 self.undo_stack.push_back(entry);
1302 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1303 self.undo_stack.pop_front();
1304 }
1305 }
1306 }
1307
1308 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1309 if self
1310 .redo_stack
1311 .back()
1312 .map_or(true, |e| e.selections != entry.selections)
1313 {
1314 self.redo_stack.push_back(entry);
1315 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1316 self.redo_stack.pop_front();
1317 }
1318 }
1319 }
1320}
1321
1322#[derive(Clone, Copy)]
1323pub struct RowHighlightOptions {
1324 pub autoscroll: bool,
1325 pub include_gutter: bool,
1326}
1327
1328impl Default for RowHighlightOptions {
1329 fn default() -> Self {
1330 Self {
1331 autoscroll: Default::default(),
1332 include_gutter: true,
1333 }
1334 }
1335}
1336
1337struct RowHighlight {
1338 index: usize,
1339 range: Range<Anchor>,
1340 color: Hsla,
1341 options: RowHighlightOptions,
1342 type_id: TypeId,
1343}
1344
1345#[derive(Clone, Debug)]
1346struct AddSelectionsState {
1347 groups: Vec<AddSelectionsGroup>,
1348}
1349
1350#[derive(Clone, Debug)]
1351struct AddSelectionsGroup {
1352 above: bool,
1353 stack: Vec<usize>,
1354}
1355
1356#[derive(Clone)]
1357struct SelectNextState {
1358 query: AhoCorasick,
1359 wordwise: bool,
1360 done: bool,
1361}
1362
1363impl std::fmt::Debug for SelectNextState {
1364 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1365 f.debug_struct(std::any::type_name::<Self>())
1366 .field("wordwise", &self.wordwise)
1367 .field("done", &self.done)
1368 .finish()
1369 }
1370}
1371
1372#[derive(Debug)]
1373struct AutocloseRegion {
1374 selection_id: usize,
1375 range: Range<Anchor>,
1376 pair: BracketPair,
1377}
1378
1379#[derive(Debug)]
1380struct SnippetState {
1381 ranges: Vec<Vec<Range<Anchor>>>,
1382 active_index: usize,
1383 choices: Vec<Option<Vec<String>>>,
1384}
1385
1386#[doc(hidden)]
1387pub struct RenameState {
1388 pub range: Range<Anchor>,
1389 pub old_name: Arc<str>,
1390 pub editor: Entity<Editor>,
1391 block_id: CustomBlockId,
1392}
1393
1394struct InvalidationStack<T>(Vec<T>);
1395
1396struct RegisteredInlineCompletionProvider {
1397 provider: Arc<dyn InlineCompletionProviderHandle>,
1398 _subscription: Subscription,
1399}
1400
1401#[derive(Debug, PartialEq, Eq)]
1402pub struct ActiveDiagnosticGroup {
1403 pub active_range: Range<Anchor>,
1404 pub active_message: String,
1405 pub group_id: usize,
1406 pub blocks: HashSet<CustomBlockId>,
1407}
1408
1409#[derive(Debug, PartialEq, Eq)]
1410
1411pub(crate) enum ActiveDiagnostic {
1412 None,
1413 All,
1414 Group(ActiveDiagnosticGroup),
1415}
1416
1417#[derive(Serialize, Deserialize, Clone, Debug)]
1418pub struct ClipboardSelection {
1419 /// The number of bytes in this selection.
1420 pub len: usize,
1421 /// Whether this was a full-line selection.
1422 pub is_entire_line: bool,
1423 /// The indentation of the first line when this content was originally copied.
1424 pub first_line_indent: u32,
1425}
1426
1427// selections, scroll behavior, was newest selection reversed
1428type SelectSyntaxNodeHistoryState = (
1429 Box<[Selection<usize>]>,
1430 SelectSyntaxNodeScrollBehavior,
1431 bool,
1432);
1433
1434#[derive(Default)]
1435struct SelectSyntaxNodeHistory {
1436 stack: Vec<SelectSyntaxNodeHistoryState>,
1437 // disable temporarily to allow changing selections without losing the stack
1438 pub disable_clearing: bool,
1439}
1440
1441impl SelectSyntaxNodeHistory {
1442 pub fn try_clear(&mut self) {
1443 if !self.disable_clearing {
1444 self.stack.clear();
1445 }
1446 }
1447
1448 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1449 self.stack.push(selection);
1450 }
1451
1452 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1453 self.stack.pop()
1454 }
1455}
1456
1457enum SelectSyntaxNodeScrollBehavior {
1458 CursorTop,
1459 FitSelection,
1460 CursorBottom,
1461}
1462
1463#[derive(Debug)]
1464pub(crate) struct NavigationData {
1465 cursor_anchor: Anchor,
1466 cursor_position: Point,
1467 scroll_anchor: ScrollAnchor,
1468 scroll_top_row: u32,
1469}
1470
1471#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1472pub enum GotoDefinitionKind {
1473 Symbol,
1474 Declaration,
1475 Type,
1476 Implementation,
1477}
1478
1479#[derive(Debug, Clone)]
1480enum InlayHintRefreshReason {
1481 ModifiersChanged(bool),
1482 Toggle(bool),
1483 SettingsChange(InlayHintSettings),
1484 NewLinesShown,
1485 BufferEdited(HashSet<Arc<Language>>),
1486 RefreshRequested,
1487 ExcerptsRemoved(Vec<ExcerptId>),
1488}
1489
1490impl InlayHintRefreshReason {
1491 fn description(&self) -> &'static str {
1492 match self {
1493 Self::ModifiersChanged(_) => "modifiers changed",
1494 Self::Toggle(_) => "toggle",
1495 Self::SettingsChange(_) => "settings change",
1496 Self::NewLinesShown => "new lines shown",
1497 Self::BufferEdited(_) => "buffer edited",
1498 Self::RefreshRequested => "refresh requested",
1499 Self::ExcerptsRemoved(_) => "excerpts removed",
1500 }
1501 }
1502}
1503
1504pub enum FormatTarget {
1505 Buffers,
1506 Ranges(Vec<Range<MultiBufferPoint>>),
1507}
1508
1509pub(crate) struct FocusedBlock {
1510 id: BlockId,
1511 focus_handle: WeakFocusHandle,
1512}
1513
1514#[derive(Clone)]
1515enum JumpData {
1516 MultiBufferRow {
1517 row: MultiBufferRow,
1518 line_offset_from_top: u32,
1519 },
1520 MultiBufferPoint {
1521 excerpt_id: ExcerptId,
1522 position: Point,
1523 anchor: text::Anchor,
1524 line_offset_from_top: u32,
1525 },
1526}
1527
1528pub enum MultibufferSelectionMode {
1529 First,
1530 All,
1531}
1532
1533#[derive(Clone, Copy, Debug, Default)]
1534pub struct RewrapOptions {
1535 pub override_language_settings: bool,
1536 pub preserve_existing_whitespace: bool,
1537}
1538
1539impl Editor {
1540 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1541 let buffer = cx.new(|cx| Buffer::local("", cx));
1542 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1543 Self::new(
1544 EditorMode::SingleLine { auto_width: false },
1545 buffer,
1546 None,
1547 window,
1548 cx,
1549 )
1550 }
1551
1552 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1553 let buffer = cx.new(|cx| Buffer::local("", cx));
1554 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1555 Self::new(EditorMode::full(), buffer, None, window, cx)
1556 }
1557
1558 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1559 let buffer = cx.new(|cx| Buffer::local("", cx));
1560 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1561 Self::new(
1562 EditorMode::SingleLine { auto_width: true },
1563 buffer,
1564 None,
1565 window,
1566 cx,
1567 )
1568 }
1569
1570 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1571 let buffer = cx.new(|cx| Buffer::local("", cx));
1572 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1573 Self::new(
1574 EditorMode::AutoHeight { max_lines },
1575 buffer,
1576 None,
1577 window,
1578 cx,
1579 )
1580 }
1581
1582 pub fn for_buffer(
1583 buffer: Entity<Buffer>,
1584 project: Option<Entity<Project>>,
1585 window: &mut Window,
1586 cx: &mut Context<Self>,
1587 ) -> Self {
1588 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1589 Self::new(EditorMode::full(), buffer, project, window, cx)
1590 }
1591
1592 pub fn for_multibuffer(
1593 buffer: Entity<MultiBuffer>,
1594 project: Option<Entity<Project>>,
1595 window: &mut Window,
1596 cx: &mut Context<Self>,
1597 ) -> Self {
1598 Self::new(EditorMode::full(), buffer, project, window, cx)
1599 }
1600
1601 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1602 let mut clone = Self::new(
1603 self.mode.clone(),
1604 self.buffer.clone(),
1605 self.project.clone(),
1606 window,
1607 cx,
1608 );
1609 self.display_map.update(cx, |display_map, cx| {
1610 let snapshot = display_map.snapshot(cx);
1611 clone.display_map.update(cx, |display_map, cx| {
1612 display_map.set_state(&snapshot, cx);
1613 });
1614 });
1615 clone.folds_did_change(cx);
1616 clone.selections.clone_state(&self.selections);
1617 clone.scroll_manager.clone_state(&self.scroll_manager);
1618 clone.searchable = self.searchable;
1619 clone.read_only = self.read_only;
1620 clone
1621 }
1622
1623 pub fn new(
1624 mode: EditorMode,
1625 buffer: Entity<MultiBuffer>,
1626 project: Option<Entity<Project>>,
1627 window: &mut Window,
1628 cx: &mut Context<Self>,
1629 ) -> Self {
1630 Editor::new_internal(mode, buffer, project, None, window, cx)
1631 }
1632
1633 fn new_internal(
1634 mode: EditorMode,
1635 buffer: Entity<MultiBuffer>,
1636 project: Option<Entity<Project>>,
1637 display_map: Option<Entity<DisplayMap>>,
1638 window: &mut Window,
1639 cx: &mut Context<Self>,
1640 ) -> Self {
1641 debug_assert!(
1642 display_map.is_none() || mode.is_minimap(),
1643 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1644 );
1645
1646 let full_mode = mode.is_full();
1647 let diagnostics_max_severity = if full_mode {
1648 EditorSettings::get_global(cx)
1649 .diagnostics_max_severity
1650 .unwrap_or(DiagnosticSeverity::Hint)
1651 } else {
1652 DiagnosticSeverity::Off
1653 };
1654 let style = window.text_style();
1655 let font_size = style.font_size.to_pixels(window.rem_size());
1656 let editor = cx.entity().downgrade();
1657 let fold_placeholder = FoldPlaceholder {
1658 constrain_width: true,
1659 render: Arc::new(move |fold_id, fold_range, cx| {
1660 let editor = editor.clone();
1661 div()
1662 .id(fold_id)
1663 .bg(cx.theme().colors().ghost_element_background)
1664 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1665 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1666 .rounded_xs()
1667 .size_full()
1668 .cursor_pointer()
1669 .child("⋯")
1670 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1671 .on_click(move |_, _window, cx| {
1672 editor
1673 .update(cx, |editor, cx| {
1674 editor.unfold_ranges(
1675 &[fold_range.start..fold_range.end],
1676 true,
1677 false,
1678 cx,
1679 );
1680 cx.stop_propagation();
1681 })
1682 .ok();
1683 })
1684 .into_any()
1685 }),
1686 merge_adjacent: true,
1687 ..FoldPlaceholder::default()
1688 };
1689 let display_map = display_map.unwrap_or_else(|| {
1690 cx.new(|cx| {
1691 DisplayMap::new(
1692 buffer.clone(),
1693 style.font(),
1694 font_size,
1695 None,
1696 FILE_HEADER_HEIGHT,
1697 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1698 fold_placeholder,
1699 diagnostics_max_severity,
1700 cx,
1701 )
1702 })
1703 });
1704
1705 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1706
1707 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1708
1709 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1710 .then(|| language_settings::SoftWrap::None);
1711
1712 let mut project_subscriptions = Vec::new();
1713 if mode.is_full() {
1714 if let Some(project) = project.as_ref() {
1715 project_subscriptions.push(cx.subscribe_in(
1716 project,
1717 window,
1718 |editor, _, event, window, cx| match event {
1719 project::Event::RefreshCodeLens => {
1720 // we always query lens with actions, without storing them, always refreshing them
1721 }
1722 project::Event::RefreshInlayHints => {
1723 editor
1724 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1725 }
1726 project::Event::LanguageServerAdded(..)
1727 | project::Event::LanguageServerRemoved(..) => {
1728 if editor.tasks_update_task.is_none() {
1729 editor.tasks_update_task =
1730 Some(editor.refresh_runnables(window, cx));
1731 }
1732 editor.pull_diagnostics(None, window, cx);
1733 }
1734 project::Event::SnippetEdit(id, snippet_edits) => {
1735 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1736 let focus_handle = editor.focus_handle(cx);
1737 if focus_handle.is_focused(window) {
1738 let snapshot = buffer.read(cx).snapshot();
1739 for (range, snippet) in snippet_edits {
1740 let editor_range =
1741 language::range_from_lsp(*range).to_offset(&snapshot);
1742 editor
1743 .insert_snippet(
1744 &[editor_range],
1745 snippet.clone(),
1746 window,
1747 cx,
1748 )
1749 .ok();
1750 }
1751 }
1752 }
1753 }
1754 _ => {}
1755 },
1756 ));
1757 if let Some(task_inventory) = project
1758 .read(cx)
1759 .task_store()
1760 .read(cx)
1761 .task_inventory()
1762 .cloned()
1763 {
1764 project_subscriptions.push(cx.observe_in(
1765 &task_inventory,
1766 window,
1767 |editor, _, window, cx| {
1768 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1769 },
1770 ));
1771 };
1772
1773 project_subscriptions.push(cx.subscribe_in(
1774 &project.read(cx).breakpoint_store(),
1775 window,
1776 |editor, _, event, window, cx| match event {
1777 BreakpointStoreEvent::ClearDebugLines => {
1778 editor.clear_row_highlights::<ActiveDebugLine>();
1779 editor.refresh_inline_values(cx);
1780 }
1781 BreakpointStoreEvent::SetDebugLine => {
1782 if editor.go_to_active_debug_line(window, cx) {
1783 cx.stop_propagation();
1784 }
1785
1786 editor.refresh_inline_values(cx);
1787 }
1788 _ => {}
1789 },
1790 ));
1791 }
1792 }
1793
1794 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1795
1796 let inlay_hint_settings =
1797 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1798 let focus_handle = cx.focus_handle();
1799 cx.on_focus(&focus_handle, window, Self::handle_focus)
1800 .detach();
1801 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1802 .detach();
1803 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1804 .detach();
1805 cx.on_blur(&focus_handle, window, Self::handle_blur)
1806 .detach();
1807 cx.observe_pending_input(window, Self::observe_pending_input)
1808 .detach();
1809
1810 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1811 Some(false)
1812 } else {
1813 None
1814 };
1815
1816 let breakpoint_store = match (&mode, project.as_ref()) {
1817 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1818 _ => None,
1819 };
1820
1821 let mut code_action_providers = Vec::new();
1822 let mut load_uncommitted_diff = None;
1823 if let Some(project) = project.clone() {
1824 load_uncommitted_diff = Some(
1825 update_uncommitted_diff_for_buffer(
1826 cx.entity(),
1827 &project,
1828 buffer.read(cx).all_buffers(),
1829 buffer.clone(),
1830 cx,
1831 )
1832 .shared(),
1833 );
1834 code_action_providers.push(Rc::new(project) as Rc<_>);
1835 }
1836
1837 let mut editor = Self {
1838 focus_handle,
1839 show_cursor_when_unfocused: false,
1840 last_focused_descendant: None,
1841 buffer: buffer.clone(),
1842 display_map: display_map.clone(),
1843 selections,
1844 scroll_manager: ScrollManager::new(cx),
1845 columnar_selection_tail: None,
1846 columnar_display_point: None,
1847 add_selections_state: None,
1848 select_next_state: None,
1849 select_prev_state: None,
1850 selection_history: SelectionHistory::default(),
1851 defer_selection_effects: false,
1852 deferred_selection_effects_state: None,
1853 autoclose_regions: Vec::new(),
1854 snippet_stack: InvalidationStack::default(),
1855 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1856 ime_transaction: None,
1857 active_diagnostics: ActiveDiagnostic::None,
1858 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1859 inline_diagnostics_update: Task::ready(()),
1860 inline_diagnostics: Vec::new(),
1861 soft_wrap_mode_override,
1862 diagnostics_max_severity,
1863 hard_wrap: None,
1864 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1865 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1866 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1867 project,
1868 blink_manager: blink_manager.clone(),
1869 show_local_selections: true,
1870 show_scrollbars: ScrollbarAxes {
1871 horizontal: full_mode,
1872 vertical: full_mode,
1873 },
1874 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1875 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1876 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1877 show_gutter: mode.is_full(),
1878 show_line_numbers: None,
1879 use_relative_line_numbers: None,
1880 disable_expand_excerpt_buttons: false,
1881 show_git_diff_gutter: None,
1882 show_code_actions: None,
1883 show_runnables: None,
1884 show_breakpoints: None,
1885 show_wrap_guides: None,
1886 show_indent_guides,
1887 placeholder_text: None,
1888 highlight_order: 0,
1889 highlighted_rows: HashMap::default(),
1890 background_highlights: TreeMap::default(),
1891 gutter_highlights: TreeMap::default(),
1892 scrollbar_marker_state: ScrollbarMarkerState::default(),
1893 active_indent_guides_state: ActiveIndentGuidesState::default(),
1894 nav_history: None,
1895 context_menu: RefCell::new(None),
1896 context_menu_options: None,
1897 mouse_context_menu: None,
1898 completion_tasks: Vec::new(),
1899 inline_blame_popover: None,
1900 signature_help_state: SignatureHelpState::default(),
1901 auto_signature_help: None,
1902 find_all_references_task_sources: Vec::new(),
1903 next_completion_id: 0,
1904 next_inlay_id: 0,
1905 code_action_providers,
1906 available_code_actions: None,
1907 code_actions_task: None,
1908 quick_selection_highlight_task: None,
1909 debounced_selection_highlight_task: None,
1910 document_highlights_task: None,
1911 linked_editing_range_task: None,
1912 pending_rename: None,
1913 searchable: true,
1914 cursor_shape: EditorSettings::get_global(cx)
1915 .cursor_shape
1916 .unwrap_or_default(),
1917 current_line_highlight: None,
1918 autoindent_mode: Some(AutoindentMode::EachLine),
1919 collapse_matches: false,
1920 workspace: None,
1921 input_enabled: true,
1922 use_modal_editing: mode.is_full(),
1923 read_only: mode.is_minimap(),
1924 use_autoclose: true,
1925 use_auto_surround: true,
1926 auto_replace_emoji_shortcode: false,
1927 jsx_tag_auto_close_enabled_in_any_buffer: false,
1928 leader_id: None,
1929 remote_id: None,
1930 hover_state: HoverState::default(),
1931 pending_mouse_down: None,
1932 hovered_link_state: None,
1933 edit_prediction_provider: None,
1934 active_inline_completion: None,
1935 stale_inline_completion_in_menu: None,
1936 edit_prediction_preview: EditPredictionPreview::Inactive {
1937 released_too_fast: false,
1938 },
1939 inline_diagnostics_enabled: mode.is_full(),
1940 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1941 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1942
1943 gutter_hovered: false,
1944 pixel_position_of_newest_cursor: None,
1945 last_bounds: None,
1946 last_position_map: None,
1947 expect_bounds_change: None,
1948 gutter_dimensions: GutterDimensions::default(),
1949 style: None,
1950 show_cursor_names: false,
1951 hovered_cursors: HashMap::default(),
1952 next_editor_action_id: EditorActionId::default(),
1953 editor_actions: Rc::default(),
1954 inline_completions_hidden_for_vim_mode: false,
1955 show_inline_completions_override: None,
1956 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1957 edit_prediction_settings: EditPredictionSettings::Disabled,
1958 edit_prediction_indent_conflict: false,
1959 edit_prediction_requires_modifier_in_indent_conflict: true,
1960 custom_context_menu: None,
1961 show_git_blame_gutter: false,
1962 show_git_blame_inline: false,
1963 show_selection_menu: None,
1964 show_git_blame_inline_delay_task: None,
1965 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1966 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1967 serialize_dirty_buffers: !mode.is_minimap()
1968 && ProjectSettings::get_global(cx)
1969 .session
1970 .restore_unsaved_buffers,
1971 blame: None,
1972 blame_subscription: None,
1973 tasks: BTreeMap::default(),
1974
1975 breakpoint_store,
1976 gutter_breakpoint_indicator: (None, None),
1977 _subscriptions: vec![
1978 cx.observe(&buffer, Self::on_buffer_changed),
1979 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1980 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1981 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1982 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1983 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1984 cx.observe_window_activation(window, |editor, window, cx| {
1985 let active = window.is_window_active();
1986 editor.blink_manager.update(cx, |blink_manager, cx| {
1987 if active {
1988 blink_manager.enable(cx);
1989 } else {
1990 blink_manager.disable(cx);
1991 }
1992 });
1993 if active {
1994 editor.show_mouse_cursor();
1995 }
1996 }),
1997 ],
1998 tasks_update_task: None,
1999 pull_diagnostics_task: Task::ready(()),
2000 linked_edit_ranges: Default::default(),
2001 in_project_search: false,
2002 previous_search_ranges: None,
2003 breadcrumb_header: None,
2004 focused_block: None,
2005 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2006 addons: HashMap::default(),
2007 registered_buffers: HashMap::default(),
2008 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2009 selection_mark_mode: false,
2010 toggle_fold_multiple_buffers: Task::ready(()),
2011 serialize_selections: Task::ready(()),
2012 serialize_folds: Task::ready(()),
2013 text_style_refinement: None,
2014 load_diff_task: load_uncommitted_diff,
2015 temporary_diff_override: false,
2016 mouse_cursor_hidden: false,
2017 minimap: None,
2018 hide_mouse_mode: EditorSettings::get_global(cx)
2019 .hide_mouse
2020 .unwrap_or_default(),
2021 change_list: ChangeList::new(),
2022 mode,
2023 selection_drag_state: SelectionDragState::None,
2024 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2025 };
2026 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2027 editor
2028 ._subscriptions
2029 .push(cx.observe(breakpoints, |_, _, cx| {
2030 cx.notify();
2031 }));
2032 }
2033 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2034 editor._subscriptions.extend(project_subscriptions);
2035
2036 editor._subscriptions.push(cx.subscribe_in(
2037 &cx.entity(),
2038 window,
2039 |editor, _, e: &EditorEvent, window, cx| match e {
2040 EditorEvent::ScrollPositionChanged { local, .. } => {
2041 if *local {
2042 let new_anchor = editor.scroll_manager.anchor();
2043 let snapshot = editor.snapshot(window, cx);
2044 editor.update_restoration_data(cx, move |data| {
2045 data.scroll_position = (
2046 new_anchor.top_row(&snapshot.buffer_snapshot),
2047 new_anchor.offset,
2048 );
2049 });
2050 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2051 editor.inline_blame_popover.take();
2052 }
2053 }
2054 EditorEvent::Edited { .. } => {
2055 if !vim_enabled(cx) {
2056 let (map, selections) = editor.selections.all_adjusted_display(cx);
2057 let pop_state = editor
2058 .change_list
2059 .last()
2060 .map(|previous| {
2061 previous.len() == selections.len()
2062 && previous.iter().enumerate().all(|(ix, p)| {
2063 p.to_display_point(&map).row()
2064 == selections[ix].head().row()
2065 })
2066 })
2067 .unwrap_or(false);
2068 let new_positions = selections
2069 .into_iter()
2070 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2071 .collect();
2072 editor
2073 .change_list
2074 .push_to_change_list(pop_state, new_positions);
2075 }
2076 }
2077 _ => (),
2078 },
2079 ));
2080
2081 if let Some(dap_store) = editor
2082 .project
2083 .as_ref()
2084 .map(|project| project.read(cx).dap_store())
2085 {
2086 let weak_editor = cx.weak_entity();
2087
2088 editor
2089 ._subscriptions
2090 .push(
2091 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2092 let session_entity = cx.entity();
2093 weak_editor
2094 .update(cx, |editor, cx| {
2095 editor._subscriptions.push(
2096 cx.subscribe(&session_entity, Self::on_debug_session_event),
2097 );
2098 })
2099 .ok();
2100 }),
2101 );
2102
2103 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2104 editor
2105 ._subscriptions
2106 .push(cx.subscribe(&session, Self::on_debug_session_event));
2107 }
2108 }
2109
2110 // skip adding the initial selection to selection history
2111 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2112 editor.end_selection(window, cx);
2113 editor.selection_history.mode = SelectionHistoryMode::Normal;
2114
2115 editor.scroll_manager.show_scrollbars(window, cx);
2116 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2117
2118 if full_mode {
2119 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2120 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2121
2122 if editor.git_blame_inline_enabled {
2123 editor.start_git_blame_inline(false, window, cx);
2124 }
2125
2126 editor.go_to_active_debug_line(window, cx);
2127
2128 if let Some(buffer) = buffer.read(cx).as_singleton() {
2129 if let Some(project) = editor.project.as_ref() {
2130 let handle = project.update(cx, |project, cx| {
2131 project.register_buffer_with_language_servers(&buffer, cx)
2132 });
2133 editor
2134 .registered_buffers
2135 .insert(buffer.read(cx).remote_id(), handle);
2136 }
2137 }
2138
2139 editor.minimap =
2140 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2141 editor.pull_diagnostics(None, window, cx);
2142 }
2143
2144 editor.report_editor_event("Editor Opened", None, cx);
2145 editor
2146 }
2147
2148 pub fn deploy_mouse_context_menu(
2149 &mut self,
2150 position: gpui::Point<Pixels>,
2151 context_menu: Entity<ContextMenu>,
2152 window: &mut Window,
2153 cx: &mut Context<Self>,
2154 ) {
2155 self.mouse_context_menu = Some(MouseContextMenu::new(
2156 self,
2157 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2158 context_menu,
2159 window,
2160 cx,
2161 ));
2162 }
2163
2164 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2165 self.mouse_context_menu
2166 .as_ref()
2167 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2168 }
2169
2170 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2171 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2172 }
2173
2174 fn key_context_internal(
2175 &self,
2176 has_active_edit_prediction: bool,
2177 window: &Window,
2178 cx: &App,
2179 ) -> KeyContext {
2180 let mut key_context = KeyContext::new_with_defaults();
2181 key_context.add("Editor");
2182 let mode = match self.mode {
2183 EditorMode::SingleLine { .. } => "single_line",
2184 EditorMode::AutoHeight { .. } => "auto_height",
2185 EditorMode::Minimap { .. } => "minimap",
2186 EditorMode::Full { .. } => "full",
2187 };
2188
2189 if EditorSettings::jupyter_enabled(cx) {
2190 key_context.add("jupyter");
2191 }
2192
2193 key_context.set("mode", mode);
2194 if self.pending_rename.is_some() {
2195 key_context.add("renaming");
2196 }
2197
2198 match self.context_menu.borrow().as_ref() {
2199 Some(CodeContextMenu::Completions(_)) => {
2200 key_context.add("menu");
2201 key_context.add("showing_completions");
2202 }
2203 Some(CodeContextMenu::CodeActions(_)) => {
2204 key_context.add("menu");
2205 key_context.add("showing_code_actions")
2206 }
2207 None => {}
2208 }
2209
2210 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2211 if !self.focus_handle(cx).contains_focused(window, cx)
2212 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2213 {
2214 for addon in self.addons.values() {
2215 addon.extend_key_context(&mut key_context, cx)
2216 }
2217 }
2218
2219 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2220 if let Some(extension) = singleton_buffer
2221 .read(cx)
2222 .file()
2223 .and_then(|file| file.path().extension()?.to_str())
2224 {
2225 key_context.set("extension", extension.to_string());
2226 }
2227 } else {
2228 key_context.add("multibuffer");
2229 }
2230
2231 if has_active_edit_prediction {
2232 if self.edit_prediction_in_conflict() {
2233 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2234 } else {
2235 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2236 key_context.add("copilot_suggestion");
2237 }
2238 }
2239
2240 if self.selection_mark_mode {
2241 key_context.add("selection_mode");
2242 }
2243
2244 key_context
2245 }
2246
2247 fn show_mouse_cursor(&mut self) {
2248 self.mouse_cursor_hidden = false;
2249 }
2250
2251 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2252 self.mouse_cursor_hidden = match origin {
2253 HideMouseCursorOrigin::TypingAction => {
2254 matches!(
2255 self.hide_mouse_mode,
2256 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2257 )
2258 }
2259 HideMouseCursorOrigin::MovementAction => {
2260 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2261 }
2262 };
2263 }
2264
2265 pub fn edit_prediction_in_conflict(&self) -> bool {
2266 if !self.show_edit_predictions_in_menu() {
2267 return false;
2268 }
2269
2270 let showing_completions = self
2271 .context_menu
2272 .borrow()
2273 .as_ref()
2274 .map_or(false, |context| {
2275 matches!(context, CodeContextMenu::Completions(_))
2276 });
2277
2278 showing_completions
2279 || self.edit_prediction_requires_modifier()
2280 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2281 // bindings to insert tab characters.
2282 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2283 }
2284
2285 pub fn accept_edit_prediction_keybind(
2286 &self,
2287 accept_partial: bool,
2288 window: &Window,
2289 cx: &App,
2290 ) -> AcceptEditPredictionBinding {
2291 let key_context = self.key_context_internal(true, window, cx);
2292 let in_conflict = self.edit_prediction_in_conflict();
2293
2294 let bindings = if accept_partial {
2295 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2296 } else {
2297 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2298 };
2299
2300 // TODO: if the binding contains multiple keystrokes, display all of them, not
2301 // just the first one.
2302 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2303 !in_conflict
2304 || binding
2305 .keystrokes()
2306 .first()
2307 .map_or(false, |keystroke| keystroke.modifiers.modified())
2308 }))
2309 }
2310
2311 pub fn new_file(
2312 workspace: &mut Workspace,
2313 _: &workspace::NewFile,
2314 window: &mut Window,
2315 cx: &mut Context<Workspace>,
2316 ) {
2317 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2318 "Failed to create buffer",
2319 window,
2320 cx,
2321 |e, _, _| match e.error_code() {
2322 ErrorCode::RemoteUpgradeRequired => Some(format!(
2323 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2324 e.error_tag("required").unwrap_or("the latest version")
2325 )),
2326 _ => None,
2327 },
2328 );
2329 }
2330
2331 pub fn new_in_workspace(
2332 workspace: &mut Workspace,
2333 window: &mut Window,
2334 cx: &mut Context<Workspace>,
2335 ) -> Task<Result<Entity<Editor>>> {
2336 let project = workspace.project().clone();
2337 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2338
2339 cx.spawn_in(window, async move |workspace, cx| {
2340 let buffer = create.await?;
2341 workspace.update_in(cx, |workspace, window, cx| {
2342 let editor =
2343 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2344 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2345 editor
2346 })
2347 })
2348 }
2349
2350 fn new_file_vertical(
2351 workspace: &mut Workspace,
2352 _: &workspace::NewFileSplitVertical,
2353 window: &mut Window,
2354 cx: &mut Context<Workspace>,
2355 ) {
2356 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2357 }
2358
2359 fn new_file_horizontal(
2360 workspace: &mut Workspace,
2361 _: &workspace::NewFileSplitHorizontal,
2362 window: &mut Window,
2363 cx: &mut Context<Workspace>,
2364 ) {
2365 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2366 }
2367
2368 fn new_file_in_direction(
2369 workspace: &mut Workspace,
2370 direction: SplitDirection,
2371 window: &mut Window,
2372 cx: &mut Context<Workspace>,
2373 ) {
2374 let project = workspace.project().clone();
2375 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2376
2377 cx.spawn_in(window, async move |workspace, cx| {
2378 let buffer = create.await?;
2379 workspace.update_in(cx, move |workspace, window, cx| {
2380 workspace.split_item(
2381 direction,
2382 Box::new(
2383 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2384 ),
2385 window,
2386 cx,
2387 )
2388 })?;
2389 anyhow::Ok(())
2390 })
2391 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2392 match e.error_code() {
2393 ErrorCode::RemoteUpgradeRequired => Some(format!(
2394 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2395 e.error_tag("required").unwrap_or("the latest version")
2396 )),
2397 _ => None,
2398 }
2399 });
2400 }
2401
2402 pub fn leader_id(&self) -> Option<CollaboratorId> {
2403 self.leader_id
2404 }
2405
2406 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2407 &self.buffer
2408 }
2409
2410 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2411 self.workspace.as_ref()?.0.upgrade()
2412 }
2413
2414 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2415 self.buffer().read(cx).title(cx)
2416 }
2417
2418 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2419 let git_blame_gutter_max_author_length = self
2420 .render_git_blame_gutter(cx)
2421 .then(|| {
2422 if let Some(blame) = self.blame.as_ref() {
2423 let max_author_length =
2424 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2425 Some(max_author_length)
2426 } else {
2427 None
2428 }
2429 })
2430 .flatten();
2431
2432 EditorSnapshot {
2433 mode: self.mode.clone(),
2434 show_gutter: self.show_gutter,
2435 show_line_numbers: self.show_line_numbers,
2436 show_git_diff_gutter: self.show_git_diff_gutter,
2437 show_code_actions: self.show_code_actions,
2438 show_runnables: self.show_runnables,
2439 show_breakpoints: self.show_breakpoints,
2440 git_blame_gutter_max_author_length,
2441 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2442 scroll_anchor: self.scroll_manager.anchor(),
2443 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2444 placeholder_text: self.placeholder_text.clone(),
2445 is_focused: self.focus_handle.is_focused(window),
2446 current_line_highlight: self
2447 .current_line_highlight
2448 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2449 gutter_hovered: self.gutter_hovered,
2450 }
2451 }
2452
2453 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2454 self.buffer.read(cx).language_at(point, cx)
2455 }
2456
2457 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2458 self.buffer.read(cx).read(cx).file_at(point).cloned()
2459 }
2460
2461 pub fn active_excerpt(
2462 &self,
2463 cx: &App,
2464 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2465 self.buffer
2466 .read(cx)
2467 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2468 }
2469
2470 pub fn mode(&self) -> &EditorMode {
2471 &self.mode
2472 }
2473
2474 pub fn set_mode(&mut self, mode: EditorMode) {
2475 self.mode = mode;
2476 }
2477
2478 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2479 self.collaboration_hub.as_deref()
2480 }
2481
2482 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2483 self.collaboration_hub = Some(hub);
2484 }
2485
2486 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2487 self.in_project_search = in_project_search;
2488 }
2489
2490 pub fn set_custom_context_menu(
2491 &mut self,
2492 f: impl 'static
2493 + Fn(
2494 &mut Self,
2495 DisplayPoint,
2496 &mut Window,
2497 &mut Context<Self>,
2498 ) -> Option<Entity<ui::ContextMenu>>,
2499 ) {
2500 self.custom_context_menu = Some(Box::new(f))
2501 }
2502
2503 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2504 self.completion_provider = provider;
2505 }
2506
2507 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2508 self.semantics_provider.clone()
2509 }
2510
2511 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2512 self.semantics_provider = provider;
2513 }
2514
2515 pub fn set_edit_prediction_provider<T>(
2516 &mut self,
2517 provider: Option<Entity<T>>,
2518 window: &mut Window,
2519 cx: &mut Context<Self>,
2520 ) where
2521 T: EditPredictionProvider,
2522 {
2523 self.edit_prediction_provider =
2524 provider.map(|provider| RegisteredInlineCompletionProvider {
2525 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2526 if this.focus_handle.is_focused(window) {
2527 this.update_visible_inline_completion(window, cx);
2528 }
2529 }),
2530 provider: Arc::new(provider),
2531 });
2532 self.update_edit_prediction_settings(cx);
2533 self.refresh_inline_completion(false, false, window, cx);
2534 }
2535
2536 pub fn placeholder_text(&self) -> Option<&str> {
2537 self.placeholder_text.as_deref()
2538 }
2539
2540 pub fn set_placeholder_text(
2541 &mut self,
2542 placeholder_text: impl Into<Arc<str>>,
2543 cx: &mut Context<Self>,
2544 ) {
2545 let placeholder_text = Some(placeholder_text.into());
2546 if self.placeholder_text != placeholder_text {
2547 self.placeholder_text = placeholder_text;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2553 self.cursor_shape = cursor_shape;
2554
2555 // Disrupt blink for immediate user feedback that the cursor shape has changed
2556 self.blink_manager.update(cx, BlinkManager::show_cursor);
2557
2558 cx.notify();
2559 }
2560
2561 pub fn set_current_line_highlight(
2562 &mut self,
2563 current_line_highlight: Option<CurrentLineHighlight>,
2564 ) {
2565 self.current_line_highlight = current_line_highlight;
2566 }
2567
2568 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2569 self.collapse_matches = collapse_matches;
2570 }
2571
2572 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2573 let buffers = self.buffer.read(cx).all_buffers();
2574 let Some(project) = self.project.as_ref() else {
2575 return;
2576 };
2577 project.update(cx, |project, cx| {
2578 for buffer in buffers {
2579 self.registered_buffers
2580 .entry(buffer.read(cx).remote_id())
2581 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2582 }
2583 })
2584 }
2585
2586 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2587 if self.collapse_matches {
2588 return range.start..range.start;
2589 }
2590 range.clone()
2591 }
2592
2593 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2594 if self.display_map.read(cx).clip_at_line_ends != clip {
2595 self.display_map
2596 .update(cx, |map, _| map.clip_at_line_ends = clip);
2597 }
2598 }
2599
2600 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2601 self.input_enabled = input_enabled;
2602 }
2603
2604 pub fn set_inline_completions_hidden_for_vim_mode(
2605 &mut self,
2606 hidden: bool,
2607 window: &mut Window,
2608 cx: &mut Context<Self>,
2609 ) {
2610 if hidden != self.inline_completions_hidden_for_vim_mode {
2611 self.inline_completions_hidden_for_vim_mode = hidden;
2612 if hidden {
2613 self.update_visible_inline_completion(window, cx);
2614 } else {
2615 self.refresh_inline_completion(true, false, window, cx);
2616 }
2617 }
2618 }
2619
2620 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2621 self.menu_inline_completions_policy = value;
2622 }
2623
2624 pub fn set_autoindent(&mut self, autoindent: bool) {
2625 if autoindent {
2626 self.autoindent_mode = Some(AutoindentMode::EachLine);
2627 } else {
2628 self.autoindent_mode = None;
2629 }
2630 }
2631
2632 pub fn read_only(&self, cx: &App) -> bool {
2633 self.read_only || self.buffer.read(cx).read_only()
2634 }
2635
2636 pub fn set_read_only(&mut self, read_only: bool) {
2637 self.read_only = read_only;
2638 }
2639
2640 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2641 self.use_autoclose = autoclose;
2642 }
2643
2644 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2645 self.use_auto_surround = auto_surround;
2646 }
2647
2648 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2649 self.auto_replace_emoji_shortcode = auto_replace;
2650 }
2651
2652 pub fn toggle_edit_predictions(
2653 &mut self,
2654 _: &ToggleEditPrediction,
2655 window: &mut Window,
2656 cx: &mut Context<Self>,
2657 ) {
2658 if self.show_inline_completions_override.is_some() {
2659 self.set_show_edit_predictions(None, window, cx);
2660 } else {
2661 let show_edit_predictions = !self.edit_predictions_enabled();
2662 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2663 }
2664 }
2665
2666 pub fn set_show_edit_predictions(
2667 &mut self,
2668 show_edit_predictions: Option<bool>,
2669 window: &mut Window,
2670 cx: &mut Context<Self>,
2671 ) {
2672 self.show_inline_completions_override = show_edit_predictions;
2673 self.update_edit_prediction_settings(cx);
2674
2675 if let Some(false) = show_edit_predictions {
2676 self.discard_inline_completion(false, cx);
2677 } else {
2678 self.refresh_inline_completion(false, true, window, cx);
2679 }
2680 }
2681
2682 fn inline_completions_disabled_in_scope(
2683 &self,
2684 buffer: &Entity<Buffer>,
2685 buffer_position: language::Anchor,
2686 cx: &App,
2687 ) -> bool {
2688 let snapshot = buffer.read(cx).snapshot();
2689 let settings = snapshot.settings_at(buffer_position, cx);
2690
2691 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2692 return false;
2693 };
2694
2695 scope.override_name().map_or(false, |scope_name| {
2696 settings
2697 .edit_predictions_disabled_in
2698 .iter()
2699 .any(|s| s == scope_name)
2700 })
2701 }
2702
2703 pub fn set_use_modal_editing(&mut self, to: bool) {
2704 self.use_modal_editing = to;
2705 }
2706
2707 pub fn use_modal_editing(&self) -> bool {
2708 self.use_modal_editing
2709 }
2710
2711 fn selections_did_change(
2712 &mut self,
2713 local: bool,
2714 old_cursor_position: &Anchor,
2715 should_update_completions: bool,
2716 window: &mut Window,
2717 cx: &mut Context<Self>,
2718 ) {
2719 window.invalidate_character_coordinates();
2720
2721 // Copy selections to primary selection buffer
2722 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2723 if local {
2724 let selections = self.selections.all::<usize>(cx);
2725 let buffer_handle = self.buffer.read(cx).read(cx);
2726
2727 let mut text = String::new();
2728 for (index, selection) in selections.iter().enumerate() {
2729 let text_for_selection = buffer_handle
2730 .text_for_range(selection.start..selection.end)
2731 .collect::<String>();
2732
2733 text.push_str(&text_for_selection);
2734 if index != selections.len() - 1 {
2735 text.push('\n');
2736 }
2737 }
2738
2739 if !text.is_empty() {
2740 cx.write_to_primary(ClipboardItem::new_string(text));
2741 }
2742 }
2743
2744 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2745 self.buffer.update(cx, |buffer, cx| {
2746 buffer.set_active_selections(
2747 &self.selections.disjoint_anchors(),
2748 self.selections.line_mode,
2749 self.cursor_shape,
2750 cx,
2751 )
2752 });
2753 }
2754 let display_map = self
2755 .display_map
2756 .update(cx, |display_map, cx| display_map.snapshot(cx));
2757 let buffer = &display_map.buffer_snapshot;
2758 if self.selections.count() == 1 {
2759 self.add_selections_state = None;
2760 }
2761 self.select_next_state = None;
2762 self.select_prev_state = None;
2763 self.select_syntax_node_history.try_clear();
2764 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2765 self.snippet_stack
2766 .invalidate(&self.selections.disjoint_anchors(), buffer);
2767 self.take_rename(false, window, cx);
2768
2769 let newest_selection = self.selections.newest_anchor();
2770 let new_cursor_position = newest_selection.head();
2771 let selection_start = newest_selection.start;
2772
2773 self.push_to_nav_history(
2774 *old_cursor_position,
2775 Some(new_cursor_position.to_point(buffer)),
2776 false,
2777 cx,
2778 );
2779
2780 if local {
2781 if let Some(buffer_id) = new_cursor_position.buffer_id {
2782 if !self.registered_buffers.contains_key(&buffer_id) {
2783 if let Some(project) = self.project.as_ref() {
2784 project.update(cx, |project, cx| {
2785 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2786 return;
2787 };
2788 self.registered_buffers.insert(
2789 buffer_id,
2790 project.register_buffer_with_language_servers(&buffer, cx),
2791 );
2792 })
2793 }
2794 }
2795 }
2796
2797 let mut context_menu = self.context_menu.borrow_mut();
2798 let completion_menu = match context_menu.as_ref() {
2799 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2800 Some(CodeContextMenu::CodeActions(_)) => {
2801 *context_menu = None;
2802 None
2803 }
2804 None => None,
2805 };
2806 let completion_position = completion_menu.map(|menu| menu.initial_position);
2807 drop(context_menu);
2808
2809 if should_update_completions {
2810 if let Some(completion_position) = completion_position {
2811 let start_offset = selection_start.to_offset(buffer);
2812 let position_matches = start_offset == completion_position.to_offset(buffer);
2813 let continue_showing = if position_matches {
2814 if self.snippet_stack.is_empty() {
2815 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2816 } else {
2817 // Snippet choices can be shown even when the cursor is in whitespace.
2818 // Dismissing the menu when actions like backspace
2819 true
2820 }
2821 } else {
2822 false
2823 };
2824
2825 if continue_showing {
2826 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2827 } else {
2828 self.hide_context_menu(window, cx);
2829 }
2830 }
2831 }
2832
2833 hide_hover(self, cx);
2834
2835 if old_cursor_position.to_display_point(&display_map).row()
2836 != new_cursor_position.to_display_point(&display_map).row()
2837 {
2838 self.available_code_actions.take();
2839 }
2840 self.refresh_code_actions(window, cx);
2841 self.refresh_document_highlights(cx);
2842 self.refresh_selected_text_highlights(false, window, cx);
2843 refresh_matching_bracket_highlights(self, window, cx);
2844 self.update_visible_inline_completion(window, cx);
2845 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2846 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2847 self.inline_blame_popover.take();
2848 if self.git_blame_inline_enabled {
2849 self.start_inline_blame_timer(window, cx);
2850 }
2851 }
2852
2853 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2854 cx.emit(EditorEvent::SelectionsChanged { local });
2855
2856 let selections = &self.selections.disjoint;
2857 if selections.len() == 1 {
2858 cx.emit(SearchEvent::ActiveMatchChanged)
2859 }
2860 if local {
2861 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2862 let inmemory_selections = selections
2863 .iter()
2864 .map(|s| {
2865 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2866 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2867 })
2868 .collect();
2869 self.update_restoration_data(cx, |data| {
2870 data.selections = inmemory_selections;
2871 });
2872
2873 if WorkspaceSettings::get(None, cx).restore_on_startup
2874 != RestoreOnStartupBehavior::None
2875 {
2876 if let Some(workspace_id) =
2877 self.workspace.as_ref().and_then(|workspace| workspace.1)
2878 {
2879 let snapshot = self.buffer().read(cx).snapshot(cx);
2880 let selections = selections.clone();
2881 let background_executor = cx.background_executor().clone();
2882 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2883 self.serialize_selections = cx.background_spawn(async move {
2884 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2885 let db_selections = selections
2886 .iter()
2887 .map(|selection| {
2888 (
2889 selection.start.to_offset(&snapshot),
2890 selection.end.to_offset(&snapshot),
2891 )
2892 })
2893 .collect();
2894
2895 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2896 .await
2897 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2898 .log_err();
2899 });
2900 }
2901 }
2902 }
2903 }
2904
2905 cx.notify();
2906 }
2907
2908 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2909 use text::ToOffset as _;
2910 use text::ToPoint as _;
2911
2912 if self.mode.is_minimap()
2913 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2914 {
2915 return;
2916 }
2917
2918 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2919 return;
2920 };
2921
2922 let snapshot = singleton.read(cx).snapshot();
2923 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2924 let display_snapshot = display_map.snapshot(cx);
2925
2926 display_snapshot
2927 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2928 .map(|fold| {
2929 fold.range.start.text_anchor.to_point(&snapshot)
2930 ..fold.range.end.text_anchor.to_point(&snapshot)
2931 })
2932 .collect()
2933 });
2934 self.update_restoration_data(cx, |data| {
2935 data.folds = inmemory_folds;
2936 });
2937
2938 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2939 return;
2940 };
2941 let background_executor = cx.background_executor().clone();
2942 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2943 let db_folds = self.display_map.update(cx, |display_map, cx| {
2944 display_map
2945 .snapshot(cx)
2946 .folds_in_range(0..snapshot.len())
2947 .map(|fold| {
2948 (
2949 fold.range.start.text_anchor.to_offset(&snapshot),
2950 fold.range.end.text_anchor.to_offset(&snapshot),
2951 )
2952 })
2953 .collect()
2954 });
2955 self.serialize_folds = cx.background_spawn(async move {
2956 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2957 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2958 .await
2959 .with_context(|| {
2960 format!(
2961 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2962 )
2963 })
2964 .log_err();
2965 });
2966 }
2967
2968 pub fn sync_selections(
2969 &mut self,
2970 other: Entity<Editor>,
2971 cx: &mut Context<Self>,
2972 ) -> gpui::Subscription {
2973 let other_selections = other.read(cx).selections.disjoint.to_vec();
2974 self.selections.change_with(cx, |selections| {
2975 selections.select_anchors(other_selections);
2976 });
2977
2978 let other_subscription =
2979 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2980 EditorEvent::SelectionsChanged { local: true } => {
2981 let other_selections = other.read(cx).selections.disjoint.to_vec();
2982 if other_selections.is_empty() {
2983 return;
2984 }
2985 this.selections.change_with(cx, |selections| {
2986 selections.select_anchors(other_selections);
2987 });
2988 }
2989 _ => {}
2990 });
2991
2992 let this_subscription =
2993 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2994 EditorEvent::SelectionsChanged { local: true } => {
2995 let these_selections = this.selections.disjoint.to_vec();
2996 if these_selections.is_empty() {
2997 return;
2998 }
2999 other.update(cx, |other_editor, cx| {
3000 other_editor.selections.change_with(cx, |selections| {
3001 selections.select_anchors(these_selections);
3002 })
3003 });
3004 }
3005 _ => {}
3006 });
3007
3008 Subscription::join(other_subscription, this_subscription)
3009 }
3010
3011 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3012 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3013 /// effects of selection change occur at the end of the transaction.
3014 pub fn change_selections<R>(
3015 &mut self,
3016 autoscroll: Option<Autoscroll>,
3017 window: &mut Window,
3018 cx: &mut Context<Self>,
3019 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3020 ) -> R {
3021 self.change_selections_inner(true, autoscroll, window, cx, change)
3022 }
3023
3024 pub(crate) fn change_selections_without_updating_completions<R>(
3025 &mut self,
3026 autoscroll: Option<Autoscroll>,
3027 window: &mut Window,
3028 cx: &mut Context<Self>,
3029 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3030 ) -> R {
3031 self.change_selections_inner(false, autoscroll, window, cx, change)
3032 }
3033
3034 fn change_selections_inner<R>(
3035 &mut self,
3036 should_update_completions: bool,
3037 autoscroll: Option<Autoscroll>,
3038 window: &mut Window,
3039 cx: &mut Context<Self>,
3040 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3041 ) -> R {
3042 if let Some(state) = &mut self.deferred_selection_effects_state {
3043 state.autoscroll = autoscroll.or(state.autoscroll);
3044 state.should_update_completions = should_update_completions;
3045 let (changed, result) = self.selections.change_with(cx, change);
3046 state.changed |= changed;
3047 return result;
3048 }
3049 let mut state = DeferredSelectionEffectsState {
3050 changed: false,
3051 should_update_completions,
3052 autoscroll,
3053 old_cursor_position: self.selections.newest_anchor().head(),
3054 history_entry: SelectionHistoryEntry {
3055 selections: self.selections.disjoint_anchors(),
3056 select_next_state: self.select_next_state.clone(),
3057 select_prev_state: self.select_prev_state.clone(),
3058 add_selections_state: self.add_selections_state.clone(),
3059 },
3060 };
3061 let (changed, result) = self.selections.change_with(cx, change);
3062 state.changed = state.changed || changed;
3063 if self.defer_selection_effects {
3064 self.deferred_selection_effects_state = Some(state);
3065 } else {
3066 self.apply_selection_effects(state, window, cx);
3067 }
3068 result
3069 }
3070
3071 /// Defers the effects of selection change, so that the effects of multiple calls to
3072 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3073 /// to selection history and the state of popovers based on selection position aren't
3074 /// erroneously updated.
3075 pub fn with_selection_effects_deferred<R>(
3076 &mut self,
3077 window: &mut Window,
3078 cx: &mut Context<Self>,
3079 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3080 ) -> R {
3081 let already_deferred = self.defer_selection_effects;
3082 self.defer_selection_effects = true;
3083 let result = update(self, window, cx);
3084 if !already_deferred {
3085 self.defer_selection_effects = false;
3086 if let Some(state) = self.deferred_selection_effects_state.take() {
3087 self.apply_selection_effects(state, window, cx);
3088 }
3089 }
3090 result
3091 }
3092
3093 fn apply_selection_effects(
3094 &mut self,
3095 state: DeferredSelectionEffectsState,
3096 window: &mut Window,
3097 cx: &mut Context<Self>,
3098 ) {
3099 if state.changed {
3100 self.selection_history.push(state.history_entry);
3101
3102 if let Some(autoscroll) = state.autoscroll {
3103 self.request_autoscroll(autoscroll, cx);
3104 }
3105
3106 let old_cursor_position = &state.old_cursor_position;
3107
3108 self.selections_did_change(
3109 true,
3110 &old_cursor_position,
3111 state.should_update_completions,
3112 window,
3113 cx,
3114 );
3115
3116 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3117 self.show_signature_help(&ShowSignatureHelp, window, cx);
3118 }
3119 }
3120 }
3121
3122 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3123 where
3124 I: IntoIterator<Item = (Range<S>, T)>,
3125 S: ToOffset,
3126 T: Into<Arc<str>>,
3127 {
3128 if self.read_only(cx) {
3129 return;
3130 }
3131
3132 self.buffer
3133 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3134 }
3135
3136 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3137 where
3138 I: IntoIterator<Item = (Range<S>, T)>,
3139 S: ToOffset,
3140 T: Into<Arc<str>>,
3141 {
3142 if self.read_only(cx) {
3143 return;
3144 }
3145
3146 self.buffer.update(cx, |buffer, cx| {
3147 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3148 });
3149 }
3150
3151 pub fn edit_with_block_indent<I, S, T>(
3152 &mut self,
3153 edits: I,
3154 original_indent_columns: Vec<Option<u32>>,
3155 cx: &mut Context<Self>,
3156 ) where
3157 I: IntoIterator<Item = (Range<S>, T)>,
3158 S: ToOffset,
3159 T: Into<Arc<str>>,
3160 {
3161 if self.read_only(cx) {
3162 return;
3163 }
3164
3165 self.buffer.update(cx, |buffer, cx| {
3166 buffer.edit(
3167 edits,
3168 Some(AutoindentMode::Block {
3169 original_indent_columns,
3170 }),
3171 cx,
3172 )
3173 });
3174 }
3175
3176 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3177 self.hide_context_menu(window, cx);
3178
3179 match phase {
3180 SelectPhase::Begin {
3181 position,
3182 add,
3183 click_count,
3184 } => self.begin_selection(position, add, click_count, window, cx),
3185 SelectPhase::BeginColumnar {
3186 position,
3187 goal_column,
3188 reset,
3189 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3190 SelectPhase::Extend {
3191 position,
3192 click_count,
3193 } => self.extend_selection(position, click_count, window, cx),
3194 SelectPhase::Update {
3195 position,
3196 goal_column,
3197 scroll_delta,
3198 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3199 SelectPhase::End => self.end_selection(window, cx),
3200 }
3201 }
3202
3203 fn extend_selection(
3204 &mut self,
3205 position: DisplayPoint,
3206 click_count: usize,
3207 window: &mut Window,
3208 cx: &mut Context<Self>,
3209 ) {
3210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3211 let tail = self.selections.newest::<usize>(cx).tail();
3212 self.begin_selection(position, false, click_count, window, cx);
3213
3214 let position = position.to_offset(&display_map, Bias::Left);
3215 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3216
3217 let mut pending_selection = self
3218 .selections
3219 .pending_anchor()
3220 .expect("extend_selection not called with pending selection");
3221 if position >= tail {
3222 pending_selection.start = tail_anchor;
3223 } else {
3224 pending_selection.end = tail_anchor;
3225 pending_selection.reversed = true;
3226 }
3227
3228 let mut pending_mode = self.selections.pending_mode().unwrap();
3229 match &mut pending_mode {
3230 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3231 _ => {}
3232 }
3233
3234 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3235
3236 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3237 s.set_pending(pending_selection, pending_mode)
3238 });
3239 }
3240
3241 fn begin_selection(
3242 &mut self,
3243 position: DisplayPoint,
3244 add: bool,
3245 click_count: usize,
3246 window: &mut Window,
3247 cx: &mut Context<Self>,
3248 ) {
3249 if !self.focus_handle.is_focused(window) {
3250 self.last_focused_descendant = None;
3251 window.focus(&self.focus_handle);
3252 }
3253
3254 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3255 let buffer = &display_map.buffer_snapshot;
3256 let position = display_map.clip_point(position, Bias::Left);
3257
3258 let start;
3259 let end;
3260 let mode;
3261 let mut auto_scroll;
3262 match click_count {
3263 1 => {
3264 start = buffer.anchor_before(position.to_point(&display_map));
3265 end = start;
3266 mode = SelectMode::Character;
3267 auto_scroll = true;
3268 }
3269 2 => {
3270 let range = movement::surrounding_word(&display_map, position);
3271 start = buffer.anchor_before(range.start.to_point(&display_map));
3272 end = buffer.anchor_before(range.end.to_point(&display_map));
3273 mode = SelectMode::Word(start..end);
3274 auto_scroll = true;
3275 }
3276 3 => {
3277 let position = display_map
3278 .clip_point(position, Bias::Left)
3279 .to_point(&display_map);
3280 let line_start = display_map.prev_line_boundary(position).0;
3281 let next_line_start = buffer.clip_point(
3282 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3283 Bias::Left,
3284 );
3285 start = buffer.anchor_before(line_start);
3286 end = buffer.anchor_before(next_line_start);
3287 mode = SelectMode::Line(start..end);
3288 auto_scroll = true;
3289 }
3290 _ => {
3291 start = buffer.anchor_before(0);
3292 end = buffer.anchor_before(buffer.len());
3293 mode = SelectMode::All;
3294 auto_scroll = false;
3295 }
3296 }
3297 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3298
3299 let point_to_delete: Option<usize> = {
3300 let selected_points: Vec<Selection<Point>> =
3301 self.selections.disjoint_in_range(start..end, cx);
3302
3303 if !add || click_count > 1 {
3304 None
3305 } else if !selected_points.is_empty() {
3306 Some(selected_points[0].id)
3307 } else {
3308 let clicked_point_already_selected =
3309 self.selections.disjoint.iter().find(|selection| {
3310 selection.start.to_point(buffer) == start.to_point(buffer)
3311 || selection.end.to_point(buffer) == end.to_point(buffer)
3312 });
3313
3314 clicked_point_already_selected.map(|selection| selection.id)
3315 }
3316 };
3317
3318 let selections_count = self.selections.count();
3319
3320 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3321 if let Some(point_to_delete) = point_to_delete {
3322 s.delete(point_to_delete);
3323
3324 if selections_count == 1 {
3325 s.set_pending_anchor_range(start..end, mode);
3326 }
3327 } else {
3328 if !add {
3329 s.clear_disjoint();
3330 }
3331
3332 s.set_pending_anchor_range(start..end, mode);
3333 }
3334 });
3335 }
3336
3337 fn begin_columnar_selection(
3338 &mut self,
3339 position: DisplayPoint,
3340 goal_column: u32,
3341 reset: bool,
3342 window: &mut Window,
3343 cx: &mut Context<Self>,
3344 ) {
3345 if !self.focus_handle.is_focused(window) {
3346 self.last_focused_descendant = None;
3347 window.focus(&self.focus_handle);
3348 }
3349
3350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3351
3352 if reset {
3353 let pointer_position = display_map
3354 .buffer_snapshot
3355 .anchor_before(position.to_point(&display_map));
3356
3357 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3358 s.clear_disjoint();
3359 s.set_pending_anchor_range(
3360 pointer_position..pointer_position,
3361 SelectMode::Character,
3362 );
3363 });
3364 if position.column() != goal_column {
3365 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3366 } else {
3367 self.columnar_display_point = None;
3368 }
3369 }
3370
3371 let tail = self.selections.newest::<Point>(cx).tail();
3372 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3373
3374 if !reset {
3375 self.columnar_display_point = None;
3376 self.select_columns(
3377 tail.to_display_point(&display_map),
3378 position,
3379 goal_column,
3380 &display_map,
3381 window,
3382 cx,
3383 );
3384 }
3385 }
3386
3387 fn update_selection(
3388 &mut self,
3389 position: DisplayPoint,
3390 goal_column: u32,
3391 scroll_delta: gpui::Point<f32>,
3392 window: &mut Window,
3393 cx: &mut Context<Self>,
3394 ) {
3395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3396
3397 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3398 let tail = self
3399 .columnar_display_point
3400 .unwrap_or_else(|| tail.to_display_point(&display_map));
3401 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3402 } else if let Some(mut pending) = self.selections.pending_anchor() {
3403 let buffer = self.buffer.read(cx).snapshot(cx);
3404 let head;
3405 let tail;
3406 let mode = self.selections.pending_mode().unwrap();
3407 match &mode {
3408 SelectMode::Character => {
3409 head = position.to_point(&display_map);
3410 tail = pending.tail().to_point(&buffer);
3411 }
3412 SelectMode::Word(original_range) => {
3413 let original_display_range = original_range.start.to_display_point(&display_map)
3414 ..original_range.end.to_display_point(&display_map);
3415 let original_buffer_range = original_display_range.start.to_point(&display_map)
3416 ..original_display_range.end.to_point(&display_map);
3417 if movement::is_inside_word(&display_map, position)
3418 || original_display_range.contains(&position)
3419 {
3420 let word_range = movement::surrounding_word(&display_map, position);
3421 if word_range.start < original_display_range.start {
3422 head = word_range.start.to_point(&display_map);
3423 } else {
3424 head = word_range.end.to_point(&display_map);
3425 }
3426 } else {
3427 head = position.to_point(&display_map);
3428 }
3429
3430 if head <= original_buffer_range.start {
3431 tail = original_buffer_range.end;
3432 } else {
3433 tail = original_buffer_range.start;
3434 }
3435 }
3436 SelectMode::Line(original_range) => {
3437 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3438
3439 let position = display_map
3440 .clip_point(position, Bias::Left)
3441 .to_point(&display_map);
3442 let line_start = display_map.prev_line_boundary(position).0;
3443 let next_line_start = buffer.clip_point(
3444 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3445 Bias::Left,
3446 );
3447
3448 if line_start < original_range.start {
3449 head = line_start
3450 } else {
3451 head = next_line_start
3452 }
3453
3454 if head <= original_range.start {
3455 tail = original_range.end;
3456 } else {
3457 tail = original_range.start;
3458 }
3459 }
3460 SelectMode::All => {
3461 return;
3462 }
3463 };
3464
3465 if head < tail {
3466 pending.start = buffer.anchor_before(head);
3467 pending.end = buffer.anchor_before(tail);
3468 pending.reversed = true;
3469 } else {
3470 pending.start = buffer.anchor_before(tail);
3471 pending.end = buffer.anchor_before(head);
3472 pending.reversed = false;
3473 }
3474
3475 self.change_selections(None, window, cx, |s| {
3476 s.set_pending(pending, mode);
3477 });
3478 } else {
3479 log::error!("update_selection dispatched with no pending selection");
3480 return;
3481 }
3482
3483 self.apply_scroll_delta(scroll_delta, window, cx);
3484 cx.notify();
3485 }
3486
3487 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3488 self.columnar_selection_tail.take();
3489 if self.selections.pending_anchor().is_some() {
3490 let selections = self.selections.all::<usize>(cx);
3491 self.change_selections(None, window, cx, |s| {
3492 s.select(selections);
3493 s.clear_pending();
3494 });
3495 }
3496 }
3497
3498 fn select_columns(
3499 &mut self,
3500 tail: DisplayPoint,
3501 head: DisplayPoint,
3502 goal_column: u32,
3503 display_map: &DisplaySnapshot,
3504 window: &mut Window,
3505 cx: &mut Context<Self>,
3506 ) {
3507 let start_row = cmp::min(tail.row(), head.row());
3508 let end_row = cmp::max(tail.row(), head.row());
3509 let start_column = cmp::min(tail.column(), goal_column);
3510 let end_column = cmp::max(tail.column(), goal_column);
3511 let reversed = start_column < tail.column();
3512
3513 let selection_ranges = (start_row.0..=end_row.0)
3514 .map(DisplayRow)
3515 .filter_map(|row| {
3516 if !display_map.is_block_line(row) {
3517 let start = display_map
3518 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3519 .to_point(display_map);
3520 let end = display_map
3521 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3522 .to_point(display_map);
3523 if reversed {
3524 Some(end..start)
3525 } else {
3526 Some(start..end)
3527 }
3528 } else {
3529 None
3530 }
3531 })
3532 .collect::<Vec<_>>();
3533
3534 let mut non_empty_ranges = selection_ranges
3535 .iter()
3536 .filter(|selection_range| selection_range.start != selection_range.end)
3537 .peekable();
3538
3539 let ranges = if non_empty_ranges.peek().is_some() {
3540 non_empty_ranges.cloned().collect()
3541 } else {
3542 selection_ranges
3543 };
3544
3545 self.change_selections(None, window, cx, |s| {
3546 s.select_ranges(ranges);
3547 });
3548 cx.notify();
3549 }
3550
3551 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3552 self.selections
3553 .all_adjusted(cx)
3554 .iter()
3555 .any(|selection| !selection.is_empty())
3556 }
3557
3558 pub fn has_pending_nonempty_selection(&self) -> bool {
3559 let pending_nonempty_selection = match self.selections.pending_anchor() {
3560 Some(Selection { start, end, .. }) => start != end,
3561 None => false,
3562 };
3563
3564 pending_nonempty_selection
3565 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3566 }
3567
3568 pub fn has_pending_selection(&self) -> bool {
3569 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3570 }
3571
3572 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3573 self.selection_mark_mode = false;
3574 self.selection_drag_state = SelectionDragState::None;
3575
3576 if self.clear_expanded_diff_hunks(cx) {
3577 cx.notify();
3578 return;
3579 }
3580 if self.dismiss_menus_and_popups(true, window, cx) {
3581 return;
3582 }
3583
3584 if self.mode.is_full()
3585 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3586 {
3587 return;
3588 }
3589
3590 cx.propagate();
3591 }
3592
3593 pub fn dismiss_menus_and_popups(
3594 &mut self,
3595 is_user_requested: bool,
3596 window: &mut Window,
3597 cx: &mut Context<Self>,
3598 ) -> bool {
3599 if self.take_rename(false, window, cx).is_some() {
3600 return true;
3601 }
3602
3603 if hide_hover(self, cx) {
3604 return true;
3605 }
3606
3607 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3608 return true;
3609 }
3610
3611 if self.hide_context_menu(window, cx).is_some() {
3612 return true;
3613 }
3614
3615 if self.mouse_context_menu.take().is_some() {
3616 return true;
3617 }
3618
3619 if is_user_requested && self.discard_inline_completion(true, cx) {
3620 return true;
3621 }
3622
3623 if self.snippet_stack.pop().is_some() {
3624 return true;
3625 }
3626
3627 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3628 self.dismiss_diagnostics(cx);
3629 return true;
3630 }
3631
3632 false
3633 }
3634
3635 fn linked_editing_ranges_for(
3636 &self,
3637 selection: Range<text::Anchor>,
3638 cx: &App,
3639 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3640 if self.linked_edit_ranges.is_empty() {
3641 return None;
3642 }
3643 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3644 selection.end.buffer_id.and_then(|end_buffer_id| {
3645 if selection.start.buffer_id != Some(end_buffer_id) {
3646 return None;
3647 }
3648 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3649 let snapshot = buffer.read(cx).snapshot();
3650 self.linked_edit_ranges
3651 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3652 .map(|ranges| (ranges, snapshot, buffer))
3653 })?;
3654 use text::ToOffset as TO;
3655 // find offset from the start of current range to current cursor position
3656 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3657
3658 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3659 let start_difference = start_offset - start_byte_offset;
3660 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3661 let end_difference = end_offset - start_byte_offset;
3662 // Current range has associated linked ranges.
3663 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3664 for range in linked_ranges.iter() {
3665 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3666 let end_offset = start_offset + end_difference;
3667 let start_offset = start_offset + start_difference;
3668 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3669 continue;
3670 }
3671 if self.selections.disjoint_anchor_ranges().any(|s| {
3672 if s.start.buffer_id != selection.start.buffer_id
3673 || s.end.buffer_id != selection.end.buffer_id
3674 {
3675 return false;
3676 }
3677 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3678 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3679 }) {
3680 continue;
3681 }
3682 let start = buffer_snapshot.anchor_after(start_offset);
3683 let end = buffer_snapshot.anchor_after(end_offset);
3684 linked_edits
3685 .entry(buffer.clone())
3686 .or_default()
3687 .push(start..end);
3688 }
3689 Some(linked_edits)
3690 }
3691
3692 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3693 let text: Arc<str> = text.into();
3694
3695 if self.read_only(cx) {
3696 return;
3697 }
3698
3699 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3700
3701 let selections = self.selections.all_adjusted(cx);
3702 let mut bracket_inserted = false;
3703 let mut edits = Vec::new();
3704 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3705 let mut new_selections = Vec::with_capacity(selections.len());
3706 let mut new_autoclose_regions = Vec::new();
3707 let snapshot = self.buffer.read(cx).read(cx);
3708 let mut clear_linked_edit_ranges = false;
3709
3710 for (selection, autoclose_region) in
3711 self.selections_with_autoclose_regions(selections, &snapshot)
3712 {
3713 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3714 // Determine if the inserted text matches the opening or closing
3715 // bracket of any of this language's bracket pairs.
3716 let mut bracket_pair = None;
3717 let mut is_bracket_pair_start = false;
3718 let mut is_bracket_pair_end = false;
3719 if !text.is_empty() {
3720 let mut bracket_pair_matching_end = None;
3721 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3722 // and they are removing the character that triggered IME popup.
3723 for (pair, enabled) in scope.brackets() {
3724 if !pair.close && !pair.surround {
3725 continue;
3726 }
3727
3728 if enabled && pair.start.ends_with(text.as_ref()) {
3729 let prefix_len = pair.start.len() - text.len();
3730 let preceding_text_matches_prefix = prefix_len == 0
3731 || (selection.start.column >= (prefix_len as u32)
3732 && snapshot.contains_str_at(
3733 Point::new(
3734 selection.start.row,
3735 selection.start.column - (prefix_len as u32),
3736 ),
3737 &pair.start[..prefix_len],
3738 ));
3739 if preceding_text_matches_prefix {
3740 bracket_pair = Some(pair.clone());
3741 is_bracket_pair_start = true;
3742 break;
3743 }
3744 }
3745 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3746 {
3747 // take first bracket pair matching end, but don't break in case a later bracket
3748 // pair matches start
3749 bracket_pair_matching_end = Some(pair.clone());
3750 }
3751 }
3752 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3753 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3754 is_bracket_pair_end = true;
3755 }
3756 }
3757
3758 if let Some(bracket_pair) = bracket_pair {
3759 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3760 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3761 let auto_surround =
3762 self.use_auto_surround && snapshot_settings.use_auto_surround;
3763 if selection.is_empty() {
3764 if is_bracket_pair_start {
3765 // If the inserted text is a suffix of an opening bracket and the
3766 // selection is preceded by the rest of the opening bracket, then
3767 // insert the closing bracket.
3768 let following_text_allows_autoclose = snapshot
3769 .chars_at(selection.start)
3770 .next()
3771 .map_or(true, |c| scope.should_autoclose_before(c));
3772
3773 let preceding_text_allows_autoclose = selection.start.column == 0
3774 || snapshot.reversed_chars_at(selection.start).next().map_or(
3775 true,
3776 |c| {
3777 bracket_pair.start != bracket_pair.end
3778 || !snapshot
3779 .char_classifier_at(selection.start)
3780 .is_word(c)
3781 },
3782 );
3783
3784 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3785 && bracket_pair.start.len() == 1
3786 {
3787 let target = bracket_pair.start.chars().next().unwrap();
3788 let current_line_count = snapshot
3789 .reversed_chars_at(selection.start)
3790 .take_while(|&c| c != '\n')
3791 .filter(|&c| c == target)
3792 .count();
3793 current_line_count % 2 == 1
3794 } else {
3795 false
3796 };
3797
3798 if autoclose
3799 && bracket_pair.close
3800 && following_text_allows_autoclose
3801 && preceding_text_allows_autoclose
3802 && !is_closing_quote
3803 {
3804 let anchor = snapshot.anchor_before(selection.end);
3805 new_selections.push((selection.map(|_| anchor), text.len()));
3806 new_autoclose_regions.push((
3807 anchor,
3808 text.len(),
3809 selection.id,
3810 bracket_pair.clone(),
3811 ));
3812 edits.push((
3813 selection.range(),
3814 format!("{}{}", text, bracket_pair.end).into(),
3815 ));
3816 bracket_inserted = true;
3817 continue;
3818 }
3819 }
3820
3821 if let Some(region) = autoclose_region {
3822 // If the selection is followed by an auto-inserted closing bracket,
3823 // then don't insert that closing bracket again; just move the selection
3824 // past the closing bracket.
3825 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3826 && text.as_ref() == region.pair.end.as_str();
3827 if should_skip {
3828 let anchor = snapshot.anchor_after(selection.end);
3829 new_selections
3830 .push((selection.map(|_| anchor), region.pair.end.len()));
3831 continue;
3832 }
3833 }
3834
3835 let always_treat_brackets_as_autoclosed = snapshot
3836 .language_settings_at(selection.start, cx)
3837 .always_treat_brackets_as_autoclosed;
3838 if always_treat_brackets_as_autoclosed
3839 && is_bracket_pair_end
3840 && snapshot.contains_str_at(selection.end, text.as_ref())
3841 {
3842 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3843 // and the inserted text is a closing bracket and the selection is followed
3844 // by the closing bracket then move the selection past the closing bracket.
3845 let anchor = snapshot.anchor_after(selection.end);
3846 new_selections.push((selection.map(|_| anchor), text.len()));
3847 continue;
3848 }
3849 }
3850 // If an opening bracket is 1 character long and is typed while
3851 // text is selected, then surround that text with the bracket pair.
3852 else if auto_surround
3853 && bracket_pair.surround
3854 && is_bracket_pair_start
3855 && bracket_pair.start.chars().count() == 1
3856 {
3857 edits.push((selection.start..selection.start, text.clone()));
3858 edits.push((
3859 selection.end..selection.end,
3860 bracket_pair.end.as_str().into(),
3861 ));
3862 bracket_inserted = true;
3863 new_selections.push((
3864 Selection {
3865 id: selection.id,
3866 start: snapshot.anchor_after(selection.start),
3867 end: snapshot.anchor_before(selection.end),
3868 reversed: selection.reversed,
3869 goal: selection.goal,
3870 },
3871 0,
3872 ));
3873 continue;
3874 }
3875 }
3876 }
3877
3878 if self.auto_replace_emoji_shortcode
3879 && selection.is_empty()
3880 && text.as_ref().ends_with(':')
3881 {
3882 if let Some(possible_emoji_short_code) =
3883 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3884 {
3885 if !possible_emoji_short_code.is_empty() {
3886 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3887 let emoji_shortcode_start = Point::new(
3888 selection.start.row,
3889 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3890 );
3891
3892 // Remove shortcode from buffer
3893 edits.push((
3894 emoji_shortcode_start..selection.start,
3895 "".to_string().into(),
3896 ));
3897 new_selections.push((
3898 Selection {
3899 id: selection.id,
3900 start: snapshot.anchor_after(emoji_shortcode_start),
3901 end: snapshot.anchor_before(selection.start),
3902 reversed: selection.reversed,
3903 goal: selection.goal,
3904 },
3905 0,
3906 ));
3907
3908 // Insert emoji
3909 let selection_start_anchor = snapshot.anchor_after(selection.start);
3910 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3911 edits.push((selection.start..selection.end, emoji.to_string().into()));
3912
3913 continue;
3914 }
3915 }
3916 }
3917 }
3918
3919 // If not handling any auto-close operation, then just replace the selected
3920 // text with the given input and move the selection to the end of the
3921 // newly inserted text.
3922 let anchor = snapshot.anchor_after(selection.end);
3923 if !self.linked_edit_ranges.is_empty() {
3924 let start_anchor = snapshot.anchor_before(selection.start);
3925
3926 let is_word_char = text.chars().next().map_or(true, |char| {
3927 let classifier = snapshot
3928 .char_classifier_at(start_anchor.to_offset(&snapshot))
3929 .ignore_punctuation(true);
3930 classifier.is_word(char)
3931 });
3932
3933 if is_word_char {
3934 if let Some(ranges) = self
3935 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3936 {
3937 for (buffer, edits) in ranges {
3938 linked_edits
3939 .entry(buffer.clone())
3940 .or_default()
3941 .extend(edits.into_iter().map(|range| (range, text.clone())));
3942 }
3943 }
3944 } else {
3945 clear_linked_edit_ranges = true;
3946 }
3947 }
3948
3949 new_selections.push((selection.map(|_| anchor), 0));
3950 edits.push((selection.start..selection.end, text.clone()));
3951 }
3952
3953 drop(snapshot);
3954
3955 self.transact(window, cx, |this, window, cx| {
3956 if clear_linked_edit_ranges {
3957 this.linked_edit_ranges.clear();
3958 }
3959 let initial_buffer_versions =
3960 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3961
3962 this.buffer.update(cx, |buffer, cx| {
3963 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3964 });
3965 for (buffer, edits) in linked_edits {
3966 buffer.update(cx, |buffer, cx| {
3967 let snapshot = buffer.snapshot();
3968 let edits = edits
3969 .into_iter()
3970 .map(|(range, text)| {
3971 use text::ToPoint as TP;
3972 let end_point = TP::to_point(&range.end, &snapshot);
3973 let start_point = TP::to_point(&range.start, &snapshot);
3974 (start_point..end_point, text)
3975 })
3976 .sorted_by_key(|(range, _)| range.start);
3977 buffer.edit(edits, None, cx);
3978 })
3979 }
3980 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3981 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3982 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3983 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3984 .zip(new_selection_deltas)
3985 .map(|(selection, delta)| Selection {
3986 id: selection.id,
3987 start: selection.start + delta,
3988 end: selection.end + delta,
3989 reversed: selection.reversed,
3990 goal: SelectionGoal::None,
3991 })
3992 .collect::<Vec<_>>();
3993
3994 let mut i = 0;
3995 for (position, delta, selection_id, pair) in new_autoclose_regions {
3996 let position = position.to_offset(&map.buffer_snapshot) + delta;
3997 let start = map.buffer_snapshot.anchor_before(position);
3998 let end = map.buffer_snapshot.anchor_after(position);
3999 while let Some(existing_state) = this.autoclose_regions.get(i) {
4000 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4001 Ordering::Less => i += 1,
4002 Ordering::Greater => break,
4003 Ordering::Equal => {
4004 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4005 Ordering::Less => i += 1,
4006 Ordering::Equal => break,
4007 Ordering::Greater => break,
4008 }
4009 }
4010 }
4011 }
4012 this.autoclose_regions.insert(
4013 i,
4014 AutocloseRegion {
4015 selection_id,
4016 range: start..end,
4017 pair,
4018 },
4019 );
4020 }
4021
4022 let had_active_inline_completion = this.has_active_inline_completion();
4023 this.change_selections_without_updating_completions(
4024 Some(Autoscroll::fit()),
4025 window,
4026 cx,
4027 |s| s.select(new_selections),
4028 );
4029
4030 if !bracket_inserted {
4031 if let Some(on_type_format_task) =
4032 this.trigger_on_type_formatting(text.to_string(), window, cx)
4033 {
4034 on_type_format_task.detach_and_log_err(cx);
4035 }
4036 }
4037
4038 let editor_settings = EditorSettings::get_global(cx);
4039 if bracket_inserted
4040 && (editor_settings.auto_signature_help
4041 || editor_settings.show_signature_help_after_edits)
4042 {
4043 this.show_signature_help(&ShowSignatureHelp, window, cx);
4044 }
4045
4046 let trigger_in_words =
4047 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4048 if this.hard_wrap.is_some() {
4049 let latest: Range<Point> = this.selections.newest(cx).range();
4050 if latest.is_empty()
4051 && this
4052 .buffer()
4053 .read(cx)
4054 .snapshot(cx)
4055 .line_len(MultiBufferRow(latest.start.row))
4056 == latest.start.column
4057 {
4058 this.rewrap_impl(
4059 RewrapOptions {
4060 override_language_settings: true,
4061 preserve_existing_whitespace: true,
4062 },
4063 cx,
4064 )
4065 }
4066 }
4067 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4068 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4069 this.refresh_inline_completion(true, false, window, cx);
4070 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4071 });
4072 }
4073
4074 fn find_possible_emoji_shortcode_at_position(
4075 snapshot: &MultiBufferSnapshot,
4076 position: Point,
4077 ) -> Option<String> {
4078 let mut chars = Vec::new();
4079 let mut found_colon = false;
4080 for char in snapshot.reversed_chars_at(position).take(100) {
4081 // Found a possible emoji shortcode in the middle of the buffer
4082 if found_colon {
4083 if char.is_whitespace() {
4084 chars.reverse();
4085 return Some(chars.iter().collect());
4086 }
4087 // If the previous character is not a whitespace, we are in the middle of a word
4088 // and we only want to complete the shortcode if the word is made up of other emojis
4089 let mut containing_word = String::new();
4090 for ch in snapshot
4091 .reversed_chars_at(position)
4092 .skip(chars.len() + 1)
4093 .take(100)
4094 {
4095 if ch.is_whitespace() {
4096 break;
4097 }
4098 containing_word.push(ch);
4099 }
4100 let containing_word = containing_word.chars().rev().collect::<String>();
4101 if util::word_consists_of_emojis(containing_word.as_str()) {
4102 chars.reverse();
4103 return Some(chars.iter().collect());
4104 }
4105 }
4106
4107 if char.is_whitespace() || !char.is_ascii() {
4108 return None;
4109 }
4110 if char == ':' {
4111 found_colon = true;
4112 } else {
4113 chars.push(char);
4114 }
4115 }
4116 // Found a possible emoji shortcode at the beginning of the buffer
4117 chars.reverse();
4118 Some(chars.iter().collect())
4119 }
4120
4121 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4122 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4123 self.transact(window, cx, |this, window, cx| {
4124 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4125 let selections = this.selections.all::<usize>(cx);
4126 let multi_buffer = this.buffer.read(cx);
4127 let buffer = multi_buffer.snapshot(cx);
4128 selections
4129 .iter()
4130 .map(|selection| {
4131 let start_point = selection.start.to_point(&buffer);
4132 let mut existing_indent =
4133 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4134 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4135 let start = selection.start;
4136 let end = selection.end;
4137 let selection_is_empty = start == end;
4138 let language_scope = buffer.language_scope_at(start);
4139 let (
4140 comment_delimiter,
4141 doc_delimiter,
4142 insert_extra_newline,
4143 indent_on_newline,
4144 indent_on_extra_newline,
4145 ) = if let Some(language) = &language_scope {
4146 let mut insert_extra_newline =
4147 insert_extra_newline_brackets(&buffer, start..end, language)
4148 || insert_extra_newline_tree_sitter(&buffer, start..end);
4149
4150 // Comment extension on newline is allowed only for cursor selections
4151 let comment_delimiter = maybe!({
4152 if !selection_is_empty {
4153 return None;
4154 }
4155
4156 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4157 return None;
4158 }
4159
4160 let delimiters = language.line_comment_prefixes();
4161 let max_len_of_delimiter =
4162 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4163 let (snapshot, range) =
4164 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4165
4166 let num_of_whitespaces = snapshot
4167 .chars_for_range(range.clone())
4168 .take_while(|c| c.is_whitespace())
4169 .count();
4170 let comment_candidate = snapshot
4171 .chars_for_range(range)
4172 .skip(num_of_whitespaces)
4173 .take(max_len_of_delimiter)
4174 .collect::<String>();
4175 let (delimiter, trimmed_len) = delimiters
4176 .iter()
4177 .filter_map(|delimiter| {
4178 let prefix = delimiter.trim_end();
4179 if comment_candidate.starts_with(prefix) {
4180 Some((delimiter, prefix.len()))
4181 } else {
4182 None
4183 }
4184 })
4185 .max_by_key(|(_, len)| *len)?;
4186
4187 let cursor_is_placed_after_comment_marker =
4188 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4189 if cursor_is_placed_after_comment_marker {
4190 Some(delimiter.clone())
4191 } else {
4192 None
4193 }
4194 });
4195
4196 let mut indent_on_newline = IndentSize::spaces(0);
4197 let mut indent_on_extra_newline = IndentSize::spaces(0);
4198
4199 let doc_delimiter = maybe!({
4200 if !selection_is_empty {
4201 return None;
4202 }
4203
4204 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4205 return None;
4206 }
4207
4208 let DocumentationConfig {
4209 start: start_tag,
4210 end: end_tag,
4211 prefix: delimiter,
4212 tab_size: len,
4213 } = language.documentation()?;
4214
4215 let is_within_block_comment = buffer
4216 .language_scope_at(start_point)
4217 .is_some_and(|scope| scope.override_name() == Some("comment"));
4218 if !is_within_block_comment {
4219 return None;
4220 }
4221
4222 let (snapshot, range) =
4223 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4224
4225 let num_of_whitespaces = snapshot
4226 .chars_for_range(range.clone())
4227 .take_while(|c| c.is_whitespace())
4228 .count();
4229
4230 // 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.
4231 let column = start_point.column;
4232 let cursor_is_after_start_tag = {
4233 let start_tag_len = start_tag.len();
4234 let start_tag_line = snapshot
4235 .chars_for_range(range.clone())
4236 .skip(num_of_whitespaces)
4237 .take(start_tag_len)
4238 .collect::<String>();
4239 if start_tag_line.starts_with(start_tag.as_ref()) {
4240 num_of_whitespaces + start_tag_len <= column as usize
4241 } else {
4242 false
4243 }
4244 };
4245
4246 let cursor_is_after_delimiter = {
4247 let delimiter_trim = delimiter.trim_end();
4248 let delimiter_line = snapshot
4249 .chars_for_range(range.clone())
4250 .skip(num_of_whitespaces)
4251 .take(delimiter_trim.len())
4252 .collect::<String>();
4253 if delimiter_line.starts_with(delimiter_trim) {
4254 num_of_whitespaces + delimiter_trim.len() <= column as usize
4255 } else {
4256 false
4257 }
4258 };
4259
4260 let cursor_is_before_end_tag_if_exists = {
4261 let mut char_position = 0u32;
4262 let mut end_tag_offset = None;
4263
4264 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4265 if let Some(byte_pos) = chunk.find(&**end_tag) {
4266 let chars_before_match =
4267 chunk[..byte_pos].chars().count() as u32;
4268 end_tag_offset =
4269 Some(char_position + chars_before_match);
4270 break 'outer;
4271 }
4272 char_position += chunk.chars().count() as u32;
4273 }
4274
4275 if let Some(end_tag_offset) = end_tag_offset {
4276 let cursor_is_before_end_tag = column <= end_tag_offset;
4277 if cursor_is_after_start_tag {
4278 if cursor_is_before_end_tag {
4279 insert_extra_newline = true;
4280 }
4281 let cursor_is_at_start_of_end_tag =
4282 column == end_tag_offset;
4283 if cursor_is_at_start_of_end_tag {
4284 indent_on_extra_newline.len = (*len).into();
4285 }
4286 }
4287 cursor_is_before_end_tag
4288 } else {
4289 true
4290 }
4291 };
4292
4293 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4294 && cursor_is_before_end_tag_if_exists
4295 {
4296 if cursor_is_after_start_tag {
4297 indent_on_newline.len = (*len).into();
4298 }
4299 Some(delimiter.clone())
4300 } else {
4301 None
4302 }
4303 });
4304
4305 (
4306 comment_delimiter,
4307 doc_delimiter,
4308 insert_extra_newline,
4309 indent_on_newline,
4310 indent_on_extra_newline,
4311 )
4312 } else {
4313 (
4314 None,
4315 None,
4316 false,
4317 IndentSize::default(),
4318 IndentSize::default(),
4319 )
4320 };
4321
4322 let prevent_auto_indent = doc_delimiter.is_some();
4323 let delimiter = comment_delimiter.or(doc_delimiter);
4324
4325 let capacity_for_delimiter =
4326 delimiter.as_deref().map(str::len).unwrap_or_default();
4327 let mut new_text = String::with_capacity(
4328 1 + capacity_for_delimiter
4329 + existing_indent.len as usize
4330 + indent_on_newline.len as usize
4331 + indent_on_extra_newline.len as usize,
4332 );
4333 new_text.push('\n');
4334 new_text.extend(existing_indent.chars());
4335 new_text.extend(indent_on_newline.chars());
4336
4337 if let Some(delimiter) = &delimiter {
4338 new_text.push_str(delimiter);
4339 }
4340
4341 if insert_extra_newline {
4342 new_text.push('\n');
4343 new_text.extend(existing_indent.chars());
4344 new_text.extend(indent_on_extra_newline.chars());
4345 }
4346
4347 let anchor = buffer.anchor_after(end);
4348 let new_selection = selection.map(|_| anchor);
4349 (
4350 ((start..end, new_text), prevent_auto_indent),
4351 (insert_extra_newline, new_selection),
4352 )
4353 })
4354 .unzip()
4355 };
4356
4357 let mut auto_indent_edits = Vec::new();
4358 let mut edits = Vec::new();
4359 for (edit, prevent_auto_indent) in edits_with_flags {
4360 if prevent_auto_indent {
4361 edits.push(edit);
4362 } else {
4363 auto_indent_edits.push(edit);
4364 }
4365 }
4366 if !edits.is_empty() {
4367 this.edit(edits, cx);
4368 }
4369 if !auto_indent_edits.is_empty() {
4370 this.edit_with_autoindent(auto_indent_edits, cx);
4371 }
4372
4373 let buffer = this.buffer.read(cx).snapshot(cx);
4374 let new_selections = selection_info
4375 .into_iter()
4376 .map(|(extra_newline_inserted, new_selection)| {
4377 let mut cursor = new_selection.end.to_point(&buffer);
4378 if extra_newline_inserted {
4379 cursor.row -= 1;
4380 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4381 }
4382 new_selection.map(|_| cursor)
4383 })
4384 .collect();
4385
4386 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4387 s.select(new_selections)
4388 });
4389 this.refresh_inline_completion(true, false, window, cx);
4390 });
4391 }
4392
4393 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4394 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4395
4396 let buffer = self.buffer.read(cx);
4397 let snapshot = buffer.snapshot(cx);
4398
4399 let mut edits = Vec::new();
4400 let mut rows = Vec::new();
4401
4402 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4403 let cursor = selection.head();
4404 let row = cursor.row;
4405
4406 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4407
4408 let newline = "\n".to_string();
4409 edits.push((start_of_line..start_of_line, newline));
4410
4411 rows.push(row + rows_inserted as u32);
4412 }
4413
4414 self.transact(window, cx, |editor, window, cx| {
4415 editor.edit(edits, cx);
4416
4417 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4418 let mut index = 0;
4419 s.move_cursors_with(|map, _, _| {
4420 let row = rows[index];
4421 index += 1;
4422
4423 let point = Point::new(row, 0);
4424 let boundary = map.next_line_boundary(point).1;
4425 let clipped = map.clip_point(boundary, Bias::Left);
4426
4427 (clipped, SelectionGoal::None)
4428 });
4429 });
4430
4431 let mut indent_edits = Vec::new();
4432 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4433 for row in rows {
4434 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4435 for (row, indent) in indents {
4436 if indent.len == 0 {
4437 continue;
4438 }
4439
4440 let text = match indent.kind {
4441 IndentKind::Space => " ".repeat(indent.len as usize),
4442 IndentKind::Tab => "\t".repeat(indent.len as usize),
4443 };
4444 let point = Point::new(row.0, 0);
4445 indent_edits.push((point..point, text));
4446 }
4447 }
4448 editor.edit(indent_edits, cx);
4449 });
4450 }
4451
4452 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4453 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4454
4455 let buffer = self.buffer.read(cx);
4456 let snapshot = buffer.snapshot(cx);
4457
4458 let mut edits = Vec::new();
4459 let mut rows = Vec::new();
4460 let mut rows_inserted = 0;
4461
4462 for selection in self.selections.all_adjusted(cx) {
4463 let cursor = selection.head();
4464 let row = cursor.row;
4465
4466 let point = Point::new(row + 1, 0);
4467 let start_of_line = snapshot.clip_point(point, Bias::Left);
4468
4469 let newline = "\n".to_string();
4470 edits.push((start_of_line..start_of_line, newline));
4471
4472 rows_inserted += 1;
4473 rows.push(row + rows_inserted);
4474 }
4475
4476 self.transact(window, cx, |editor, window, cx| {
4477 editor.edit(edits, cx);
4478
4479 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4480 let mut index = 0;
4481 s.move_cursors_with(|map, _, _| {
4482 let row = rows[index];
4483 index += 1;
4484
4485 let point = Point::new(row, 0);
4486 let boundary = map.next_line_boundary(point).1;
4487 let clipped = map.clip_point(boundary, Bias::Left);
4488
4489 (clipped, SelectionGoal::None)
4490 });
4491 });
4492
4493 let mut indent_edits = Vec::new();
4494 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4495 for row in rows {
4496 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4497 for (row, indent) in indents {
4498 if indent.len == 0 {
4499 continue;
4500 }
4501
4502 let text = match indent.kind {
4503 IndentKind::Space => " ".repeat(indent.len as usize),
4504 IndentKind::Tab => "\t".repeat(indent.len as usize),
4505 };
4506 let point = Point::new(row.0, 0);
4507 indent_edits.push((point..point, text));
4508 }
4509 }
4510 editor.edit(indent_edits, cx);
4511 });
4512 }
4513
4514 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4515 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4516 original_indent_columns: Vec::new(),
4517 });
4518 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4519 }
4520
4521 fn insert_with_autoindent_mode(
4522 &mut self,
4523 text: &str,
4524 autoindent_mode: Option<AutoindentMode>,
4525 window: &mut Window,
4526 cx: &mut Context<Self>,
4527 ) {
4528 if self.read_only(cx) {
4529 return;
4530 }
4531
4532 let text: Arc<str> = text.into();
4533 self.transact(window, cx, |this, window, cx| {
4534 let old_selections = this.selections.all_adjusted(cx);
4535 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4536 let anchors = {
4537 let snapshot = buffer.read(cx);
4538 old_selections
4539 .iter()
4540 .map(|s| {
4541 let anchor = snapshot.anchor_after(s.head());
4542 s.map(|_| anchor)
4543 })
4544 .collect::<Vec<_>>()
4545 };
4546 buffer.edit(
4547 old_selections
4548 .iter()
4549 .map(|s| (s.start..s.end, text.clone())),
4550 autoindent_mode,
4551 cx,
4552 );
4553 anchors
4554 });
4555
4556 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4557 s.select_anchors(selection_anchors);
4558 });
4559
4560 cx.notify();
4561 });
4562 }
4563
4564 fn trigger_completion_on_input(
4565 &mut self,
4566 text: &str,
4567 trigger_in_words: bool,
4568 window: &mut Window,
4569 cx: &mut Context<Self>,
4570 ) {
4571 let completions_source = self
4572 .context_menu
4573 .borrow()
4574 .as_ref()
4575 .and_then(|menu| match menu {
4576 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4577 CodeContextMenu::CodeActions(_) => None,
4578 });
4579
4580 match completions_source {
4581 Some(CompletionsMenuSource::Words) => {
4582 self.show_word_completions(&ShowWordCompletions, window, cx)
4583 }
4584 Some(CompletionsMenuSource::Normal)
4585 | Some(CompletionsMenuSource::SnippetChoices)
4586 | None
4587 if self.is_completion_trigger(
4588 text,
4589 trigger_in_words,
4590 completions_source.is_some(),
4591 cx,
4592 ) =>
4593 {
4594 self.show_completions(
4595 &ShowCompletions {
4596 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4597 },
4598 window,
4599 cx,
4600 )
4601 }
4602 _ => {
4603 self.hide_context_menu(window, cx);
4604 }
4605 }
4606 }
4607
4608 fn is_completion_trigger(
4609 &self,
4610 text: &str,
4611 trigger_in_words: bool,
4612 menu_is_open: bool,
4613 cx: &mut Context<Self>,
4614 ) -> bool {
4615 let position = self.selections.newest_anchor().head();
4616 let multibuffer = self.buffer.read(cx);
4617 let Some(buffer) = position
4618 .buffer_id
4619 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4620 else {
4621 return false;
4622 };
4623
4624 if let Some(completion_provider) = &self.completion_provider {
4625 completion_provider.is_completion_trigger(
4626 &buffer,
4627 position.text_anchor,
4628 text,
4629 trigger_in_words,
4630 menu_is_open,
4631 cx,
4632 )
4633 } else {
4634 false
4635 }
4636 }
4637
4638 /// If any empty selections is touching the start of its innermost containing autoclose
4639 /// region, expand it to select the brackets.
4640 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4641 let selections = self.selections.all::<usize>(cx);
4642 let buffer = self.buffer.read(cx).read(cx);
4643 let new_selections = self
4644 .selections_with_autoclose_regions(selections, &buffer)
4645 .map(|(mut selection, region)| {
4646 if !selection.is_empty() {
4647 return selection;
4648 }
4649
4650 if let Some(region) = region {
4651 let mut range = region.range.to_offset(&buffer);
4652 if selection.start == range.start && range.start >= region.pair.start.len() {
4653 range.start -= region.pair.start.len();
4654 if buffer.contains_str_at(range.start, ®ion.pair.start)
4655 && buffer.contains_str_at(range.end, ®ion.pair.end)
4656 {
4657 range.end += region.pair.end.len();
4658 selection.start = range.start;
4659 selection.end = range.end;
4660
4661 return selection;
4662 }
4663 }
4664 }
4665
4666 let always_treat_brackets_as_autoclosed = buffer
4667 .language_settings_at(selection.start, cx)
4668 .always_treat_brackets_as_autoclosed;
4669
4670 if !always_treat_brackets_as_autoclosed {
4671 return selection;
4672 }
4673
4674 if let Some(scope) = buffer.language_scope_at(selection.start) {
4675 for (pair, enabled) in scope.brackets() {
4676 if !enabled || !pair.close {
4677 continue;
4678 }
4679
4680 if buffer.contains_str_at(selection.start, &pair.end) {
4681 let pair_start_len = pair.start.len();
4682 if buffer.contains_str_at(
4683 selection.start.saturating_sub(pair_start_len),
4684 &pair.start,
4685 ) {
4686 selection.start -= pair_start_len;
4687 selection.end += pair.end.len();
4688
4689 return selection;
4690 }
4691 }
4692 }
4693 }
4694
4695 selection
4696 })
4697 .collect();
4698
4699 drop(buffer);
4700 self.change_selections(None, window, cx, |selections| {
4701 selections.select(new_selections)
4702 });
4703 }
4704
4705 /// Iterate the given selections, and for each one, find the smallest surrounding
4706 /// autoclose region. This uses the ordering of the selections and the autoclose
4707 /// regions to avoid repeated comparisons.
4708 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4709 &'a self,
4710 selections: impl IntoIterator<Item = Selection<D>>,
4711 buffer: &'a MultiBufferSnapshot,
4712 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4713 let mut i = 0;
4714 let mut regions = self.autoclose_regions.as_slice();
4715 selections.into_iter().map(move |selection| {
4716 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4717
4718 let mut enclosing = None;
4719 while let Some(pair_state) = regions.get(i) {
4720 if pair_state.range.end.to_offset(buffer) < range.start {
4721 regions = ®ions[i + 1..];
4722 i = 0;
4723 } else if pair_state.range.start.to_offset(buffer) > range.end {
4724 break;
4725 } else {
4726 if pair_state.selection_id == selection.id {
4727 enclosing = Some(pair_state);
4728 }
4729 i += 1;
4730 }
4731 }
4732
4733 (selection, enclosing)
4734 })
4735 }
4736
4737 /// Remove any autoclose regions that no longer contain their selection.
4738 fn invalidate_autoclose_regions(
4739 &mut self,
4740 mut selections: &[Selection<Anchor>],
4741 buffer: &MultiBufferSnapshot,
4742 ) {
4743 self.autoclose_regions.retain(|state| {
4744 let mut i = 0;
4745 while let Some(selection) = selections.get(i) {
4746 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4747 selections = &selections[1..];
4748 continue;
4749 }
4750 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4751 break;
4752 }
4753 if selection.id == state.selection_id {
4754 return true;
4755 } else {
4756 i += 1;
4757 }
4758 }
4759 false
4760 });
4761 }
4762
4763 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4764 let offset = position.to_offset(buffer);
4765 let (word_range, kind) = buffer.surrounding_word(offset, true);
4766 if offset > word_range.start && kind == Some(CharKind::Word) {
4767 Some(
4768 buffer
4769 .text_for_range(word_range.start..offset)
4770 .collect::<String>(),
4771 )
4772 } else {
4773 None
4774 }
4775 }
4776
4777 pub fn toggle_inline_values(
4778 &mut self,
4779 _: &ToggleInlineValues,
4780 _: &mut Window,
4781 cx: &mut Context<Self>,
4782 ) {
4783 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4784
4785 self.refresh_inline_values(cx);
4786 }
4787
4788 pub fn toggle_inlay_hints(
4789 &mut self,
4790 _: &ToggleInlayHints,
4791 _: &mut Window,
4792 cx: &mut Context<Self>,
4793 ) {
4794 self.refresh_inlay_hints(
4795 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4796 cx,
4797 );
4798 }
4799
4800 pub fn inlay_hints_enabled(&self) -> bool {
4801 self.inlay_hint_cache.enabled
4802 }
4803
4804 pub fn inline_values_enabled(&self) -> bool {
4805 self.inline_value_cache.enabled
4806 }
4807
4808 #[cfg(any(test, feature = "test-support"))]
4809 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4810 self.display_map
4811 .read(cx)
4812 .current_inlays()
4813 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4814 .cloned()
4815 .collect()
4816 }
4817
4818 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4819 if self.semantics_provider.is_none() || !self.mode.is_full() {
4820 return;
4821 }
4822
4823 let reason_description = reason.description();
4824 let ignore_debounce = matches!(
4825 reason,
4826 InlayHintRefreshReason::SettingsChange(_)
4827 | InlayHintRefreshReason::Toggle(_)
4828 | InlayHintRefreshReason::ExcerptsRemoved(_)
4829 | InlayHintRefreshReason::ModifiersChanged(_)
4830 );
4831 let (invalidate_cache, required_languages) = match reason {
4832 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4833 match self.inlay_hint_cache.modifiers_override(enabled) {
4834 Some(enabled) => {
4835 if enabled {
4836 (InvalidationStrategy::RefreshRequested, None)
4837 } else {
4838 self.splice_inlays(
4839 &self
4840 .visible_inlay_hints(cx)
4841 .iter()
4842 .map(|inlay| inlay.id)
4843 .collect::<Vec<InlayId>>(),
4844 Vec::new(),
4845 cx,
4846 );
4847 return;
4848 }
4849 }
4850 None => return,
4851 }
4852 }
4853 InlayHintRefreshReason::Toggle(enabled) => {
4854 if self.inlay_hint_cache.toggle(enabled) {
4855 if enabled {
4856 (InvalidationStrategy::RefreshRequested, None)
4857 } else {
4858 self.splice_inlays(
4859 &self
4860 .visible_inlay_hints(cx)
4861 .iter()
4862 .map(|inlay| inlay.id)
4863 .collect::<Vec<InlayId>>(),
4864 Vec::new(),
4865 cx,
4866 );
4867 return;
4868 }
4869 } else {
4870 return;
4871 }
4872 }
4873 InlayHintRefreshReason::SettingsChange(new_settings) => {
4874 match self.inlay_hint_cache.update_settings(
4875 &self.buffer,
4876 new_settings,
4877 self.visible_inlay_hints(cx),
4878 cx,
4879 ) {
4880 ControlFlow::Break(Some(InlaySplice {
4881 to_remove,
4882 to_insert,
4883 })) => {
4884 self.splice_inlays(&to_remove, to_insert, cx);
4885 return;
4886 }
4887 ControlFlow::Break(None) => return,
4888 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4889 }
4890 }
4891 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4892 if let Some(InlaySplice {
4893 to_remove,
4894 to_insert,
4895 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4896 {
4897 self.splice_inlays(&to_remove, to_insert, cx);
4898 }
4899 self.display_map.update(cx, |display_map, _| {
4900 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4901 });
4902 return;
4903 }
4904 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4905 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4906 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4907 }
4908 InlayHintRefreshReason::RefreshRequested => {
4909 (InvalidationStrategy::RefreshRequested, None)
4910 }
4911 };
4912
4913 if let Some(InlaySplice {
4914 to_remove,
4915 to_insert,
4916 }) = self.inlay_hint_cache.spawn_hint_refresh(
4917 reason_description,
4918 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4919 invalidate_cache,
4920 ignore_debounce,
4921 cx,
4922 ) {
4923 self.splice_inlays(&to_remove, to_insert, cx);
4924 }
4925 }
4926
4927 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4928 self.display_map
4929 .read(cx)
4930 .current_inlays()
4931 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4932 .cloned()
4933 .collect()
4934 }
4935
4936 pub fn excerpts_for_inlay_hints_query(
4937 &self,
4938 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4939 cx: &mut Context<Editor>,
4940 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4941 let Some(project) = self.project.as_ref() else {
4942 return HashMap::default();
4943 };
4944 let project = project.read(cx);
4945 let multi_buffer = self.buffer().read(cx);
4946 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4947 let multi_buffer_visible_start = self
4948 .scroll_manager
4949 .anchor()
4950 .anchor
4951 .to_point(&multi_buffer_snapshot);
4952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4953 multi_buffer_visible_start
4954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4955 Bias::Left,
4956 );
4957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4958 multi_buffer_snapshot
4959 .range_to_buffer_ranges(multi_buffer_visible_range)
4960 .into_iter()
4961 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4962 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4963 let buffer_file = project::File::from_dyn(buffer.file())?;
4964 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4965 let worktree_entry = buffer_worktree
4966 .read(cx)
4967 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4968 if worktree_entry.is_ignored {
4969 return None;
4970 }
4971
4972 let language = buffer.language()?;
4973 if let Some(restrict_to_languages) = restrict_to_languages {
4974 if !restrict_to_languages.contains(language) {
4975 return None;
4976 }
4977 }
4978 Some((
4979 excerpt_id,
4980 (
4981 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4982 buffer.version().clone(),
4983 excerpt_visible_range,
4984 ),
4985 ))
4986 })
4987 .collect()
4988 }
4989
4990 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4991 TextLayoutDetails {
4992 text_system: window.text_system().clone(),
4993 editor_style: self.style.clone().unwrap(),
4994 rem_size: window.rem_size(),
4995 scroll_anchor: self.scroll_manager.anchor(),
4996 visible_rows: self.visible_line_count(),
4997 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4998 }
4999 }
5000
5001 pub fn splice_inlays(
5002 &self,
5003 to_remove: &[InlayId],
5004 to_insert: Vec<Inlay>,
5005 cx: &mut Context<Self>,
5006 ) {
5007 self.display_map.update(cx, |display_map, cx| {
5008 display_map.splice_inlays(to_remove, to_insert, cx)
5009 });
5010 cx.notify();
5011 }
5012
5013 fn trigger_on_type_formatting(
5014 &self,
5015 input: String,
5016 window: &mut Window,
5017 cx: &mut Context<Self>,
5018 ) -> Option<Task<Result<()>>> {
5019 if input.len() != 1 {
5020 return None;
5021 }
5022
5023 let project = self.project.as_ref()?;
5024 let position = self.selections.newest_anchor().head();
5025 let (buffer, buffer_position) = self
5026 .buffer
5027 .read(cx)
5028 .text_anchor_for_position(position, cx)?;
5029
5030 let settings = language_settings::language_settings(
5031 buffer
5032 .read(cx)
5033 .language_at(buffer_position)
5034 .map(|l| l.name()),
5035 buffer.read(cx).file(),
5036 cx,
5037 );
5038 if !settings.use_on_type_format {
5039 return None;
5040 }
5041
5042 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5043 // hence we do LSP request & edit on host side only — add formats to host's history.
5044 let push_to_lsp_host_history = true;
5045 // If this is not the host, append its history with new edits.
5046 let push_to_client_history = project.read(cx).is_via_collab();
5047
5048 let on_type_formatting = project.update(cx, |project, cx| {
5049 project.on_type_format(
5050 buffer.clone(),
5051 buffer_position,
5052 input,
5053 push_to_lsp_host_history,
5054 cx,
5055 )
5056 });
5057 Some(cx.spawn_in(window, async move |editor, cx| {
5058 if let Some(transaction) = on_type_formatting.await? {
5059 if push_to_client_history {
5060 buffer
5061 .update(cx, |buffer, _| {
5062 buffer.push_transaction(transaction, Instant::now());
5063 buffer.finalize_last_transaction();
5064 })
5065 .ok();
5066 }
5067 editor.update(cx, |editor, cx| {
5068 editor.refresh_document_highlights(cx);
5069 })?;
5070 }
5071 Ok(())
5072 }))
5073 }
5074
5075 pub fn show_word_completions(
5076 &mut self,
5077 _: &ShowWordCompletions,
5078 window: &mut Window,
5079 cx: &mut Context<Self>,
5080 ) {
5081 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5082 }
5083
5084 pub fn show_completions(
5085 &mut self,
5086 options: &ShowCompletions,
5087 window: &mut Window,
5088 cx: &mut Context<Self>,
5089 ) {
5090 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5091 }
5092
5093 fn open_or_update_completions_menu(
5094 &mut self,
5095 requested_source: Option<CompletionsMenuSource>,
5096 trigger: Option<&str>,
5097 window: &mut Window,
5098 cx: &mut Context<Self>,
5099 ) {
5100 if self.pending_rename.is_some() {
5101 return;
5102 }
5103
5104 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5105
5106 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5107 // inserted and selected. To handle that case, the start of the selection is used so that
5108 // the menu starts with all choices.
5109 let position = self
5110 .selections
5111 .newest_anchor()
5112 .start
5113 .bias_right(&multibuffer_snapshot);
5114 if position.diff_base_anchor.is_some() {
5115 return;
5116 }
5117 let (buffer, buffer_position) =
5118 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5119 output
5120 } else {
5121 return;
5122 };
5123 let buffer_snapshot = buffer.read(cx).snapshot();
5124
5125 let query: Option<Arc<String>> =
5126 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5127
5128 drop(multibuffer_snapshot);
5129
5130 let provider = match requested_source {
5131 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5132 Some(CompletionsMenuSource::Words) => None,
5133 Some(CompletionsMenuSource::SnippetChoices) => {
5134 log::error!("bug: SnippetChoices requested_source is not handled");
5135 None
5136 }
5137 };
5138
5139 let sort_completions = provider
5140 .as_ref()
5141 .map_or(false, |provider| provider.sort_completions());
5142
5143 let filter_completions = provider
5144 .as_ref()
5145 .map_or(true, |provider| provider.filter_completions());
5146
5147 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5148 if filter_completions {
5149 menu.filter(query.clone(), provider.clone(), window, cx);
5150 }
5151 // When `is_incomplete` is false, no need to re-query completions when the current query
5152 // is a suffix of the initial query.
5153 if !menu.is_incomplete {
5154 // If the new query is a suffix of the old query (typing more characters) and
5155 // the previous result was complete, the existing completions can be filtered.
5156 //
5157 // Note that this is always true for snippet completions.
5158 let query_matches = match (&menu.initial_query, &query) {
5159 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5160 (None, _) => true,
5161 _ => false,
5162 };
5163 if query_matches {
5164 let position_matches = if menu.initial_position == position {
5165 true
5166 } else {
5167 let snapshot = self.buffer.read(cx).read(cx);
5168 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5169 };
5170 if position_matches {
5171 return;
5172 }
5173 }
5174 }
5175 };
5176
5177 let trigger_kind = match trigger {
5178 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5179 CompletionTriggerKind::TRIGGER_CHARACTER
5180 }
5181 _ => CompletionTriggerKind::INVOKED,
5182 };
5183 let completion_context = CompletionContext {
5184 trigger_character: trigger.and_then(|trigger| {
5185 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5186 Some(String::from(trigger))
5187 } else {
5188 None
5189 }
5190 }),
5191 trigger_kind,
5192 };
5193
5194 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5195 buffer_snapshot.surrounding_word(buffer_position)
5196 {
5197 let word_to_exclude = buffer_snapshot
5198 .text_for_range(word_range.clone())
5199 .collect::<String>();
5200 (
5201 buffer_snapshot.anchor_before(word_range.start)
5202 ..buffer_snapshot.anchor_after(buffer_position),
5203 Some(word_to_exclude),
5204 )
5205 } else {
5206 (buffer_position..buffer_position, None)
5207 };
5208
5209 let language = buffer_snapshot
5210 .language_at(buffer_position)
5211 .map(|language| language.name());
5212
5213 let completion_settings =
5214 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5215
5216 let show_completion_documentation = buffer_snapshot
5217 .settings_at(buffer_position, cx)
5218 .show_completion_documentation;
5219
5220 // The document can be large, so stay in reasonable bounds when searching for words,
5221 // otherwise completion pop-up might be slow to appear.
5222 const WORD_LOOKUP_ROWS: u32 = 5_000;
5223 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5224 let min_word_search = buffer_snapshot.clip_point(
5225 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5226 Bias::Left,
5227 );
5228 let max_word_search = buffer_snapshot.clip_point(
5229 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5230 Bias::Right,
5231 );
5232 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5233 ..buffer_snapshot.point_to_offset(max_word_search);
5234
5235 let skip_digits = query
5236 .as_ref()
5237 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5238
5239 let (mut words, provider_responses) = match &provider {
5240 Some(provider) => {
5241 let provider_responses = provider.completions(
5242 position.excerpt_id,
5243 &buffer,
5244 buffer_position,
5245 completion_context,
5246 window,
5247 cx,
5248 );
5249
5250 let words = match completion_settings.words {
5251 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5252 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5253 .background_spawn(async move {
5254 buffer_snapshot.words_in_range(WordsQuery {
5255 fuzzy_contents: None,
5256 range: word_search_range,
5257 skip_digits,
5258 })
5259 }),
5260 };
5261
5262 (words, provider_responses)
5263 }
5264 None => (
5265 cx.background_spawn(async move {
5266 buffer_snapshot.words_in_range(WordsQuery {
5267 fuzzy_contents: None,
5268 range: word_search_range,
5269 skip_digits,
5270 })
5271 }),
5272 Task::ready(Ok(Vec::new())),
5273 ),
5274 };
5275
5276 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5277
5278 let id = post_inc(&mut self.next_completion_id);
5279 let task = cx.spawn_in(window, async move |editor, cx| {
5280 let Ok(()) = editor.update(cx, |this, _| {
5281 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5282 }) else {
5283 return;
5284 };
5285
5286 // TODO: Ideally completions from different sources would be selectively re-queried, so
5287 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5288 let mut completions = Vec::new();
5289 let mut is_incomplete = false;
5290 if let Some(provider_responses) = provider_responses.await.log_err() {
5291 if !provider_responses.is_empty() {
5292 for response in provider_responses {
5293 completions.extend(response.completions);
5294 is_incomplete = is_incomplete || response.is_incomplete;
5295 }
5296 if completion_settings.words == WordsCompletionMode::Fallback {
5297 words = Task::ready(BTreeMap::default());
5298 }
5299 }
5300 }
5301
5302 let mut words = words.await;
5303 if let Some(word_to_exclude) = &word_to_exclude {
5304 words.remove(word_to_exclude);
5305 }
5306 for lsp_completion in &completions {
5307 words.remove(&lsp_completion.new_text);
5308 }
5309 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5310 replace_range: word_replace_range.clone(),
5311 new_text: word.clone(),
5312 label: CodeLabel::plain(word, None),
5313 icon_path: None,
5314 documentation: None,
5315 source: CompletionSource::BufferWord {
5316 word_range,
5317 resolved: false,
5318 },
5319 insert_text_mode: Some(InsertTextMode::AS_IS),
5320 confirm: None,
5321 }));
5322
5323 let menu = if completions.is_empty() {
5324 None
5325 } else {
5326 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5327 let languages = editor
5328 .workspace
5329 .as_ref()
5330 .and_then(|(workspace, _)| workspace.upgrade())
5331 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5332 let menu = CompletionsMenu::new(
5333 id,
5334 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5335 sort_completions,
5336 show_completion_documentation,
5337 position,
5338 query.clone(),
5339 is_incomplete,
5340 buffer.clone(),
5341 completions.into(),
5342 snippet_sort_order,
5343 languages,
5344 language,
5345 cx,
5346 );
5347
5348 let query = if filter_completions { query } else { None };
5349 let matches_task = if let Some(query) = query {
5350 menu.do_async_filtering(query, cx)
5351 } else {
5352 Task::ready(menu.unfiltered_matches())
5353 };
5354 (menu, matches_task)
5355 }) else {
5356 return;
5357 };
5358
5359 let matches = matches_task.await;
5360
5361 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5362 // Newer menu already set, so exit.
5363 match editor.context_menu.borrow().as_ref() {
5364 Some(CodeContextMenu::Completions(prev_menu)) => {
5365 if prev_menu.id > id {
5366 return;
5367 }
5368 }
5369 _ => {}
5370 };
5371
5372 // Only valid to take prev_menu because it the new menu is immediately set
5373 // below, or the menu is hidden.
5374 match editor.context_menu.borrow_mut().take() {
5375 Some(CodeContextMenu::Completions(prev_menu)) => {
5376 let position_matches =
5377 if prev_menu.initial_position == menu.initial_position {
5378 true
5379 } else {
5380 let snapshot = editor.buffer.read(cx).read(cx);
5381 prev_menu.initial_position.to_offset(&snapshot)
5382 == menu.initial_position.to_offset(&snapshot)
5383 };
5384 if position_matches {
5385 // Preserve markdown cache before `set_filter_results` because it will
5386 // try to populate the documentation cache.
5387 menu.preserve_markdown_cache(prev_menu);
5388 }
5389 }
5390 _ => {}
5391 };
5392
5393 menu.set_filter_results(matches, provider, window, cx);
5394 }) else {
5395 return;
5396 };
5397
5398 menu.visible().then_some(menu)
5399 };
5400
5401 editor
5402 .update_in(cx, |editor, window, cx| {
5403 if editor.focus_handle.is_focused(window) {
5404 if let Some(menu) = menu {
5405 *editor.context_menu.borrow_mut() =
5406 Some(CodeContextMenu::Completions(menu));
5407
5408 crate::hover_popover::hide_hover(editor, cx);
5409 if editor.show_edit_predictions_in_menu() {
5410 editor.update_visible_inline_completion(window, cx);
5411 } else {
5412 editor.discard_inline_completion(false, cx);
5413 }
5414
5415 cx.notify();
5416 return;
5417 }
5418 }
5419
5420 if editor.completion_tasks.len() <= 1 {
5421 // If there are no more completion tasks and the last menu was empty, we should hide it.
5422 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5423 // If it was already hidden and we don't show inline completions in the menu, we should
5424 // also show the inline-completion when available.
5425 if was_hidden && editor.show_edit_predictions_in_menu() {
5426 editor.update_visible_inline_completion(window, cx);
5427 }
5428 }
5429 })
5430 .ok();
5431 });
5432
5433 self.completion_tasks.push((id, task));
5434 }
5435
5436 #[cfg(feature = "test-support")]
5437 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5438 let menu = self.context_menu.borrow();
5439 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5440 let completions = menu.completions.borrow();
5441 Some(completions.to_vec())
5442 } else {
5443 None
5444 }
5445 }
5446
5447 pub fn with_completions_menu_matching_id<R>(
5448 &self,
5449 id: CompletionId,
5450 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5451 ) -> R {
5452 let mut context_menu = self.context_menu.borrow_mut();
5453 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5454 return f(None);
5455 };
5456 if completions_menu.id != id {
5457 return f(None);
5458 }
5459 f(Some(completions_menu))
5460 }
5461
5462 pub fn confirm_completion(
5463 &mut self,
5464 action: &ConfirmCompletion,
5465 window: &mut Window,
5466 cx: &mut Context<Self>,
5467 ) -> Option<Task<Result<()>>> {
5468 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5469 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5470 }
5471
5472 pub fn confirm_completion_insert(
5473 &mut self,
5474 _: &ConfirmCompletionInsert,
5475 window: &mut Window,
5476 cx: &mut Context<Self>,
5477 ) -> Option<Task<Result<()>>> {
5478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5479 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5480 }
5481
5482 pub fn confirm_completion_replace(
5483 &mut self,
5484 _: &ConfirmCompletionReplace,
5485 window: &mut Window,
5486 cx: &mut Context<Self>,
5487 ) -> Option<Task<Result<()>>> {
5488 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5489 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5490 }
5491
5492 pub fn compose_completion(
5493 &mut self,
5494 action: &ComposeCompletion,
5495 window: &mut Window,
5496 cx: &mut Context<Self>,
5497 ) -> Option<Task<Result<()>>> {
5498 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5499 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5500 }
5501
5502 fn do_completion(
5503 &mut self,
5504 item_ix: Option<usize>,
5505 intent: CompletionIntent,
5506 window: &mut Window,
5507 cx: &mut Context<Editor>,
5508 ) -> Option<Task<Result<()>>> {
5509 use language::ToOffset as _;
5510
5511 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5512 else {
5513 return None;
5514 };
5515
5516 let candidate_id = {
5517 let entries = completions_menu.entries.borrow();
5518 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5519 if self.show_edit_predictions_in_menu() {
5520 self.discard_inline_completion(true, cx);
5521 }
5522 mat.candidate_id
5523 };
5524
5525 let completion = completions_menu
5526 .completions
5527 .borrow()
5528 .get(candidate_id)?
5529 .clone();
5530 cx.stop_propagation();
5531
5532 let buffer_handle = completions_menu.buffer.clone();
5533
5534 let CompletionEdit {
5535 new_text,
5536 snippet,
5537 replace_range,
5538 } = process_completion_for_edit(
5539 &completion,
5540 intent,
5541 &buffer_handle,
5542 &completions_menu.initial_position.text_anchor,
5543 cx,
5544 );
5545
5546 let buffer = buffer_handle.read(cx);
5547 let snapshot = self.buffer.read(cx).snapshot(cx);
5548 let newest_anchor = self.selections.newest_anchor();
5549 let replace_range_multibuffer = {
5550 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5551 let multibuffer_anchor = snapshot
5552 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5553 .unwrap()
5554 ..snapshot
5555 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5556 .unwrap();
5557 multibuffer_anchor.start.to_offset(&snapshot)
5558 ..multibuffer_anchor.end.to_offset(&snapshot)
5559 };
5560 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5561 return None;
5562 }
5563
5564 let old_text = buffer
5565 .text_for_range(replace_range.clone())
5566 .collect::<String>();
5567 let lookbehind = newest_anchor
5568 .start
5569 .text_anchor
5570 .to_offset(buffer)
5571 .saturating_sub(replace_range.start);
5572 let lookahead = replace_range
5573 .end
5574 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5575 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5576 let suffix = &old_text[lookbehind.min(old_text.len())..];
5577
5578 let selections = self.selections.all::<usize>(cx);
5579 let mut ranges = Vec::new();
5580 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5581
5582 for selection in &selections {
5583 let range = if selection.id == newest_anchor.id {
5584 replace_range_multibuffer.clone()
5585 } else {
5586 let mut range = selection.range();
5587
5588 // if prefix is present, don't duplicate it
5589 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5590 range.start = range.start.saturating_sub(lookbehind);
5591
5592 // if suffix is also present, mimic the newest cursor and replace it
5593 if selection.id != newest_anchor.id
5594 && snapshot.contains_str_at(range.end, suffix)
5595 {
5596 range.end += lookahead;
5597 }
5598 }
5599 range
5600 };
5601
5602 ranges.push(range.clone());
5603
5604 if !self.linked_edit_ranges.is_empty() {
5605 let start_anchor = snapshot.anchor_before(range.start);
5606 let end_anchor = snapshot.anchor_after(range.end);
5607 if let Some(ranges) = self
5608 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5609 {
5610 for (buffer, edits) in ranges {
5611 linked_edits
5612 .entry(buffer.clone())
5613 .or_default()
5614 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5615 }
5616 }
5617 }
5618 }
5619
5620 let common_prefix_len = old_text
5621 .chars()
5622 .zip(new_text.chars())
5623 .take_while(|(a, b)| a == b)
5624 .map(|(a, _)| a.len_utf8())
5625 .sum::<usize>();
5626
5627 cx.emit(EditorEvent::InputHandled {
5628 utf16_range_to_replace: None,
5629 text: new_text[common_prefix_len..].into(),
5630 });
5631
5632 self.transact(window, cx, |this, window, cx| {
5633 if let Some(mut snippet) = snippet {
5634 snippet.text = new_text.to_string();
5635 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5636 } else {
5637 this.buffer.update(cx, |buffer, cx| {
5638 let auto_indent = match completion.insert_text_mode {
5639 Some(InsertTextMode::AS_IS) => None,
5640 _ => this.autoindent_mode.clone(),
5641 };
5642 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5643 buffer.edit(edits, auto_indent, cx);
5644 });
5645 }
5646 for (buffer, edits) in linked_edits {
5647 buffer.update(cx, |buffer, cx| {
5648 let snapshot = buffer.snapshot();
5649 let edits = edits
5650 .into_iter()
5651 .map(|(range, text)| {
5652 use text::ToPoint as TP;
5653 let end_point = TP::to_point(&range.end, &snapshot);
5654 let start_point = TP::to_point(&range.start, &snapshot);
5655 (start_point..end_point, text)
5656 })
5657 .sorted_by_key(|(range, _)| range.start);
5658 buffer.edit(edits, None, cx);
5659 })
5660 }
5661
5662 this.refresh_inline_completion(true, false, window, cx);
5663 });
5664
5665 let show_new_completions_on_confirm = completion
5666 .confirm
5667 .as_ref()
5668 .map_or(false, |confirm| confirm(intent, window, cx));
5669 if show_new_completions_on_confirm {
5670 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5671 }
5672
5673 let provider = self.completion_provider.as_ref()?;
5674 drop(completion);
5675 let apply_edits = provider.apply_additional_edits_for_completion(
5676 buffer_handle,
5677 completions_menu.completions.clone(),
5678 candidate_id,
5679 true,
5680 cx,
5681 );
5682
5683 let editor_settings = EditorSettings::get_global(cx);
5684 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5685 // After the code completion is finished, users often want to know what signatures are needed.
5686 // so we should automatically call signature_help
5687 self.show_signature_help(&ShowSignatureHelp, window, cx);
5688 }
5689
5690 Some(cx.foreground_executor().spawn(async move {
5691 apply_edits.await?;
5692 Ok(())
5693 }))
5694 }
5695
5696 pub fn toggle_code_actions(
5697 &mut self,
5698 action: &ToggleCodeActions,
5699 window: &mut Window,
5700 cx: &mut Context<Self>,
5701 ) {
5702 let quick_launch = action.quick_launch;
5703 let mut context_menu = self.context_menu.borrow_mut();
5704 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5705 if code_actions.deployed_from == action.deployed_from {
5706 // Toggle if we're selecting the same one
5707 *context_menu = None;
5708 cx.notify();
5709 return;
5710 } else {
5711 // Otherwise, clear it and start a new one
5712 *context_menu = None;
5713 cx.notify();
5714 }
5715 }
5716 drop(context_menu);
5717 let snapshot = self.snapshot(window, cx);
5718 let deployed_from = action.deployed_from.clone();
5719 let action = action.clone();
5720 self.completion_tasks.clear();
5721 self.discard_inline_completion(false, cx);
5722
5723 let multibuffer_point = match &action.deployed_from {
5724 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5725 DisplayPoint::new(*row, 0).to_point(&snapshot)
5726 }
5727 _ => self.selections.newest::<Point>(cx).head(),
5728 };
5729 let Some((buffer, buffer_row)) = snapshot
5730 .buffer_snapshot
5731 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5732 .and_then(|(buffer_snapshot, range)| {
5733 self.buffer()
5734 .read(cx)
5735 .buffer(buffer_snapshot.remote_id())
5736 .map(|buffer| (buffer, range.start.row))
5737 })
5738 else {
5739 return;
5740 };
5741 let buffer_id = buffer.read(cx).remote_id();
5742 let tasks = self
5743 .tasks
5744 .get(&(buffer_id, buffer_row))
5745 .map(|t| Arc::new(t.to_owned()));
5746
5747 if !self.focus_handle.is_focused(window) {
5748 return;
5749 }
5750 let project = self.project.clone();
5751
5752 let code_actions_task = match deployed_from {
5753 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5754 _ => self.code_actions(buffer_row, window, cx),
5755 };
5756
5757 let runnable_task = match deployed_from {
5758 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5759 _ => {
5760 let mut task_context_task = Task::ready(None);
5761 if let Some(tasks) = &tasks {
5762 if let Some(project) = project {
5763 task_context_task =
5764 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5765 }
5766 }
5767
5768 cx.spawn_in(window, {
5769 let buffer = buffer.clone();
5770 async move |editor, cx| {
5771 let task_context = task_context_task.await;
5772
5773 let resolved_tasks =
5774 tasks
5775 .zip(task_context.clone())
5776 .map(|(tasks, task_context)| ResolvedTasks {
5777 templates: tasks.resolve(&task_context).collect(),
5778 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5779 multibuffer_point.row,
5780 tasks.column,
5781 )),
5782 });
5783 let debug_scenarios = editor
5784 .update(cx, |editor, cx| {
5785 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5786 })?
5787 .await;
5788 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5789 }
5790 })
5791 }
5792 };
5793
5794 cx.spawn_in(window, async move |editor, cx| {
5795 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5796 let code_actions = code_actions_task.await;
5797 let spawn_straight_away = quick_launch
5798 && resolved_tasks
5799 .as_ref()
5800 .map_or(false, |tasks| tasks.templates.len() == 1)
5801 && code_actions
5802 .as_ref()
5803 .map_or(true, |actions| actions.is_empty())
5804 && debug_scenarios.is_empty();
5805
5806 editor.update_in(cx, |editor, window, cx| {
5807 crate::hover_popover::hide_hover(editor, cx);
5808 *editor.context_menu.borrow_mut() =
5809 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5810 buffer,
5811 actions: CodeActionContents::new(
5812 resolved_tasks,
5813 code_actions,
5814 debug_scenarios,
5815 task_context.unwrap_or_default(),
5816 ),
5817 selected_item: Default::default(),
5818 scroll_handle: UniformListScrollHandle::default(),
5819 deployed_from,
5820 }));
5821 if spawn_straight_away {
5822 if let Some(task) = editor.confirm_code_action(
5823 &ConfirmCodeAction { item_ix: Some(0) },
5824 window,
5825 cx,
5826 ) {
5827 cx.notify();
5828 return task;
5829 }
5830 }
5831
5832 Task::ready(Ok(()))
5833 })
5834 })
5835 .detach_and_log_err(cx);
5836 }
5837
5838 fn debug_scenarios(
5839 &mut self,
5840 resolved_tasks: &Option<ResolvedTasks>,
5841 buffer: &Entity<Buffer>,
5842 cx: &mut App,
5843 ) -> Task<Vec<task::DebugScenario>> {
5844 if cx.has_flag::<DebuggerFeatureFlag>() {
5845 maybe!({
5846 let project = self.project.as_ref()?;
5847 let dap_store = project.read(cx).dap_store();
5848 let mut scenarios = vec![];
5849 let resolved_tasks = resolved_tasks.as_ref()?;
5850 let buffer = buffer.read(cx);
5851 let language = buffer.language()?;
5852 let file = buffer.file();
5853 let debug_adapter = language_settings(language.name().into(), file, cx)
5854 .debuggers
5855 .first()
5856 .map(SharedString::from)
5857 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5858
5859 dap_store.update(cx, |dap_store, cx| {
5860 for (_, task) in &resolved_tasks.templates {
5861 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5862 task.original_task().clone(),
5863 debug_adapter.clone().into(),
5864 task.display_label().to_owned().into(),
5865 cx,
5866 );
5867 scenarios.push(maybe_scenario);
5868 }
5869 });
5870 Some(cx.background_spawn(async move {
5871 let scenarios = futures::future::join_all(scenarios)
5872 .await
5873 .into_iter()
5874 .flatten()
5875 .collect::<Vec<_>>();
5876 scenarios
5877 }))
5878 })
5879 .unwrap_or_else(|| Task::ready(vec![]))
5880 } else {
5881 Task::ready(vec![])
5882 }
5883 }
5884
5885 fn code_actions(
5886 &mut self,
5887 buffer_row: u32,
5888 window: &mut Window,
5889 cx: &mut Context<Self>,
5890 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5891 let mut task = self.code_actions_task.take();
5892 cx.spawn_in(window, async move |editor, cx| {
5893 while let Some(prev_task) = task {
5894 prev_task.await.log_err();
5895 task = editor
5896 .update(cx, |this, _| this.code_actions_task.take())
5897 .ok()?;
5898 }
5899
5900 editor
5901 .update(cx, |editor, cx| {
5902 editor
5903 .available_code_actions
5904 .clone()
5905 .and_then(|(location, code_actions)| {
5906 let snapshot = location.buffer.read(cx).snapshot();
5907 let point_range = location.range.to_point(&snapshot);
5908 let point_range = point_range.start.row..=point_range.end.row;
5909 if point_range.contains(&buffer_row) {
5910 Some(code_actions)
5911 } else {
5912 None
5913 }
5914 })
5915 })
5916 .ok()
5917 .flatten()
5918 })
5919 }
5920
5921 pub fn confirm_code_action(
5922 &mut self,
5923 action: &ConfirmCodeAction,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<Task<Result<()>>> {
5927 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5928
5929 let actions_menu =
5930 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5931 menu
5932 } else {
5933 return None;
5934 };
5935
5936 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5937 let action = actions_menu.actions.get(action_ix)?;
5938 let title = action.label();
5939 let buffer = actions_menu.buffer;
5940 let workspace = self.workspace()?;
5941
5942 match action {
5943 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5944 workspace.update(cx, |workspace, cx| {
5945 workspace.schedule_resolved_task(
5946 task_source_kind,
5947 resolved_task,
5948 false,
5949 window,
5950 cx,
5951 );
5952
5953 Some(Task::ready(Ok(())))
5954 })
5955 }
5956 CodeActionsItem::CodeAction {
5957 excerpt_id,
5958 action,
5959 provider,
5960 } => {
5961 let apply_code_action =
5962 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5963 let workspace = workspace.downgrade();
5964 Some(cx.spawn_in(window, async move |editor, cx| {
5965 let project_transaction = apply_code_action.await?;
5966 Self::open_project_transaction(
5967 &editor,
5968 workspace,
5969 project_transaction,
5970 title,
5971 cx,
5972 )
5973 .await
5974 }))
5975 }
5976 CodeActionsItem::DebugScenario(scenario) => {
5977 let context = actions_menu.actions.context.clone();
5978
5979 workspace.update(cx, |workspace, cx| {
5980 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5981 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5982 });
5983 Some(Task::ready(Ok(())))
5984 }
5985 }
5986 }
5987
5988 pub async fn open_project_transaction(
5989 this: &WeakEntity<Editor>,
5990 workspace: WeakEntity<Workspace>,
5991 transaction: ProjectTransaction,
5992 title: String,
5993 cx: &mut AsyncWindowContext,
5994 ) -> Result<()> {
5995 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5996 cx.update(|_, cx| {
5997 entries.sort_unstable_by_key(|(buffer, _)| {
5998 buffer.read(cx).file().map(|f| f.path().clone())
5999 });
6000 })?;
6001
6002 // If the project transaction's edits are all contained within this editor, then
6003 // avoid opening a new editor to display them.
6004
6005 if let Some((buffer, transaction)) = entries.first() {
6006 if entries.len() == 1 {
6007 let excerpt = this.update(cx, |editor, cx| {
6008 editor
6009 .buffer()
6010 .read(cx)
6011 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6012 })?;
6013 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6014 if excerpted_buffer == *buffer {
6015 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6016 let excerpt_range = excerpt_range.to_offset(buffer);
6017 buffer
6018 .edited_ranges_for_transaction::<usize>(transaction)
6019 .all(|range| {
6020 excerpt_range.start <= range.start
6021 && excerpt_range.end >= range.end
6022 })
6023 })?;
6024
6025 if all_edits_within_excerpt {
6026 return Ok(());
6027 }
6028 }
6029 }
6030 }
6031 } else {
6032 return Ok(());
6033 }
6034
6035 let mut ranges_to_highlight = Vec::new();
6036 let excerpt_buffer = cx.new(|cx| {
6037 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6038 for (buffer_handle, transaction) in &entries {
6039 let edited_ranges = buffer_handle
6040 .read(cx)
6041 .edited_ranges_for_transaction::<Point>(transaction)
6042 .collect::<Vec<_>>();
6043 let (ranges, _) = multibuffer.set_excerpts_for_path(
6044 PathKey::for_buffer(buffer_handle, cx),
6045 buffer_handle.clone(),
6046 edited_ranges,
6047 DEFAULT_MULTIBUFFER_CONTEXT,
6048 cx,
6049 );
6050
6051 ranges_to_highlight.extend(ranges);
6052 }
6053 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6054 multibuffer
6055 })?;
6056
6057 workspace.update_in(cx, |workspace, window, cx| {
6058 let project = workspace.project().clone();
6059 let editor =
6060 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6061 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6062 editor.update(cx, |editor, cx| {
6063 editor.highlight_background::<Self>(
6064 &ranges_to_highlight,
6065 |theme| theme.colors().editor_highlighted_line_background,
6066 cx,
6067 );
6068 });
6069 })?;
6070
6071 Ok(())
6072 }
6073
6074 pub fn clear_code_action_providers(&mut self) {
6075 self.code_action_providers.clear();
6076 self.available_code_actions.take();
6077 }
6078
6079 pub fn add_code_action_provider(
6080 &mut self,
6081 provider: Rc<dyn CodeActionProvider>,
6082 window: &mut Window,
6083 cx: &mut Context<Self>,
6084 ) {
6085 if self
6086 .code_action_providers
6087 .iter()
6088 .any(|existing_provider| existing_provider.id() == provider.id())
6089 {
6090 return;
6091 }
6092
6093 self.code_action_providers.push(provider);
6094 self.refresh_code_actions(window, cx);
6095 }
6096
6097 pub fn remove_code_action_provider(
6098 &mut self,
6099 id: Arc<str>,
6100 window: &mut Window,
6101 cx: &mut Context<Self>,
6102 ) {
6103 self.code_action_providers
6104 .retain(|provider| provider.id() != id);
6105 self.refresh_code_actions(window, cx);
6106 }
6107
6108 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6109 !self.code_action_providers.is_empty()
6110 && EditorSettings::get_global(cx).toolbar.code_actions
6111 }
6112
6113 pub fn has_available_code_actions(&self) -> bool {
6114 self.available_code_actions
6115 .as_ref()
6116 .is_some_and(|(_, actions)| !actions.is_empty())
6117 }
6118
6119 fn render_inline_code_actions(
6120 &self,
6121 icon_size: ui::IconSize,
6122 display_row: DisplayRow,
6123 is_active: bool,
6124 cx: &mut Context<Self>,
6125 ) -> AnyElement {
6126 let show_tooltip = !self.context_menu_visible();
6127 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6128 .icon_size(icon_size)
6129 .shape(ui::IconButtonShape::Square)
6130 .style(ButtonStyle::Transparent)
6131 .icon_color(ui::Color::Hidden)
6132 .toggle_state(is_active)
6133 .when(show_tooltip, |this| {
6134 this.tooltip({
6135 let focus_handle = self.focus_handle.clone();
6136 move |window, cx| {
6137 Tooltip::for_action_in(
6138 "Toggle Code Actions",
6139 &ToggleCodeActions {
6140 deployed_from: None,
6141 quick_launch: false,
6142 },
6143 &focus_handle,
6144 window,
6145 cx,
6146 )
6147 }
6148 })
6149 })
6150 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6151 window.focus(&editor.focus_handle(cx));
6152 editor.toggle_code_actions(
6153 &crate::actions::ToggleCodeActions {
6154 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6155 display_row,
6156 )),
6157 quick_launch: false,
6158 },
6159 window,
6160 cx,
6161 );
6162 }))
6163 .into_any_element()
6164 }
6165
6166 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6167 &self.context_menu
6168 }
6169
6170 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6171 let newest_selection = self.selections.newest_anchor().clone();
6172 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6173 let buffer = self.buffer.read(cx);
6174 if newest_selection.head().diff_base_anchor.is_some() {
6175 return None;
6176 }
6177 let (start_buffer, start) =
6178 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6179 let (end_buffer, end) =
6180 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6181 if start_buffer != end_buffer {
6182 return None;
6183 }
6184
6185 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6186 cx.background_executor()
6187 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6188 .await;
6189
6190 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6191 let providers = this.code_action_providers.clone();
6192 let tasks = this
6193 .code_action_providers
6194 .iter()
6195 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6196 .collect::<Vec<_>>();
6197 (providers, tasks)
6198 })?;
6199
6200 let mut actions = Vec::new();
6201 for (provider, provider_actions) in
6202 providers.into_iter().zip(future::join_all(tasks).await)
6203 {
6204 if let Some(provider_actions) = provider_actions.log_err() {
6205 actions.extend(provider_actions.into_iter().map(|action| {
6206 AvailableCodeAction {
6207 excerpt_id: newest_selection.start.excerpt_id,
6208 action,
6209 provider: provider.clone(),
6210 }
6211 }));
6212 }
6213 }
6214
6215 this.update(cx, |this, cx| {
6216 this.available_code_actions = if actions.is_empty() {
6217 None
6218 } else {
6219 Some((
6220 Location {
6221 buffer: start_buffer,
6222 range: start..end,
6223 },
6224 actions.into(),
6225 ))
6226 };
6227 cx.notify();
6228 })
6229 }));
6230 None
6231 }
6232
6233 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6234 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6235 self.show_git_blame_inline = false;
6236
6237 self.show_git_blame_inline_delay_task =
6238 Some(cx.spawn_in(window, async move |this, cx| {
6239 cx.background_executor().timer(delay).await;
6240
6241 this.update(cx, |this, cx| {
6242 this.show_git_blame_inline = true;
6243 cx.notify();
6244 })
6245 .log_err();
6246 }));
6247 }
6248 }
6249
6250 fn show_blame_popover(
6251 &mut self,
6252 blame_entry: &BlameEntry,
6253 position: gpui::Point<Pixels>,
6254 cx: &mut Context<Self>,
6255 ) {
6256 if let Some(state) = &mut self.inline_blame_popover {
6257 state.hide_task.take();
6258 cx.notify();
6259 } else {
6260 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6261 let show_task = cx.spawn(async move |editor, cx| {
6262 cx.background_executor()
6263 .timer(std::time::Duration::from_millis(delay))
6264 .await;
6265 editor
6266 .update(cx, |editor, cx| {
6267 if let Some(state) = &mut editor.inline_blame_popover {
6268 state.show_task = None;
6269 cx.notify();
6270 }
6271 })
6272 .ok();
6273 });
6274 let Some(blame) = self.blame.as_ref() else {
6275 return;
6276 };
6277 let blame = blame.read(cx);
6278 let details = blame.details_for_entry(&blame_entry);
6279 let markdown = cx.new(|cx| {
6280 Markdown::new(
6281 details
6282 .as_ref()
6283 .map(|message| message.message.clone())
6284 .unwrap_or_default(),
6285 None,
6286 None,
6287 cx,
6288 )
6289 });
6290 self.inline_blame_popover = Some(InlineBlamePopover {
6291 position,
6292 show_task: Some(show_task),
6293 hide_task: None,
6294 popover_bounds: None,
6295 popover_state: InlineBlamePopoverState {
6296 scroll_handle: ScrollHandle::new(),
6297 commit_message: details,
6298 markdown,
6299 },
6300 });
6301 }
6302 }
6303
6304 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6305 if let Some(state) = &mut self.inline_blame_popover {
6306 if state.show_task.is_some() {
6307 self.inline_blame_popover.take();
6308 cx.notify();
6309 } else {
6310 let hide_task = cx.spawn(async move |editor, cx| {
6311 cx.background_executor()
6312 .timer(std::time::Duration::from_millis(100))
6313 .await;
6314 editor
6315 .update(cx, |editor, cx| {
6316 editor.inline_blame_popover.take();
6317 cx.notify();
6318 })
6319 .ok();
6320 });
6321 state.hide_task = Some(hide_task);
6322 }
6323 }
6324 }
6325
6326 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6327 if self.pending_rename.is_some() {
6328 return None;
6329 }
6330
6331 let provider = self.semantics_provider.clone()?;
6332 let buffer = self.buffer.read(cx);
6333 let newest_selection = self.selections.newest_anchor().clone();
6334 let cursor_position = newest_selection.head();
6335 let (cursor_buffer, cursor_buffer_position) =
6336 buffer.text_anchor_for_position(cursor_position, cx)?;
6337 let (tail_buffer, tail_buffer_position) =
6338 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6339 if cursor_buffer != tail_buffer {
6340 return None;
6341 }
6342
6343 let snapshot = cursor_buffer.read(cx).snapshot();
6344 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6345 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6346 if start_word_range != end_word_range {
6347 self.document_highlights_task.take();
6348 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6349 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6350 return None;
6351 }
6352
6353 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6354 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6355 cx.background_executor()
6356 .timer(Duration::from_millis(debounce))
6357 .await;
6358
6359 let highlights = if let Some(highlights) = cx
6360 .update(|cx| {
6361 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6362 })
6363 .ok()
6364 .flatten()
6365 {
6366 highlights.await.log_err()
6367 } else {
6368 None
6369 };
6370
6371 if let Some(highlights) = highlights {
6372 this.update(cx, |this, cx| {
6373 if this.pending_rename.is_some() {
6374 return;
6375 }
6376
6377 let buffer_id = cursor_position.buffer_id;
6378 let buffer = this.buffer.read(cx);
6379 if !buffer
6380 .text_anchor_for_position(cursor_position, cx)
6381 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6382 {
6383 return;
6384 }
6385
6386 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6387 let mut write_ranges = Vec::new();
6388 let mut read_ranges = Vec::new();
6389 for highlight in highlights {
6390 for (excerpt_id, excerpt_range) in
6391 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6392 {
6393 let start = highlight
6394 .range
6395 .start
6396 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6397 let end = highlight
6398 .range
6399 .end
6400 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6401 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6402 continue;
6403 }
6404
6405 let range = Anchor {
6406 buffer_id,
6407 excerpt_id,
6408 text_anchor: start,
6409 diff_base_anchor: None,
6410 }..Anchor {
6411 buffer_id,
6412 excerpt_id,
6413 text_anchor: end,
6414 diff_base_anchor: None,
6415 };
6416 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6417 write_ranges.push(range);
6418 } else {
6419 read_ranges.push(range);
6420 }
6421 }
6422 }
6423
6424 this.highlight_background::<DocumentHighlightRead>(
6425 &read_ranges,
6426 |theme| theme.colors().editor_document_highlight_read_background,
6427 cx,
6428 );
6429 this.highlight_background::<DocumentHighlightWrite>(
6430 &write_ranges,
6431 |theme| theme.colors().editor_document_highlight_write_background,
6432 cx,
6433 );
6434 cx.notify();
6435 })
6436 .log_err();
6437 }
6438 }));
6439 None
6440 }
6441
6442 fn prepare_highlight_query_from_selection(
6443 &mut self,
6444 cx: &mut Context<Editor>,
6445 ) -> Option<(String, Range<Anchor>)> {
6446 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6447 return None;
6448 }
6449 if !EditorSettings::get_global(cx).selection_highlight {
6450 return None;
6451 }
6452 if self.selections.count() != 1 || self.selections.line_mode {
6453 return None;
6454 }
6455 let selection = self.selections.newest::<Point>(cx);
6456 if selection.is_empty() || selection.start.row != selection.end.row {
6457 return None;
6458 }
6459 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6460 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6461 let query = multi_buffer_snapshot
6462 .text_for_range(selection_anchor_range.clone())
6463 .collect::<String>();
6464 if query.trim().is_empty() {
6465 return None;
6466 }
6467 Some((query, selection_anchor_range))
6468 }
6469
6470 fn update_selection_occurrence_highlights(
6471 &mut self,
6472 query_text: String,
6473 query_range: Range<Anchor>,
6474 multi_buffer_range_to_query: Range<Point>,
6475 use_debounce: bool,
6476 window: &mut Window,
6477 cx: &mut Context<Editor>,
6478 ) -> Task<()> {
6479 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6480 cx.spawn_in(window, async move |editor, cx| {
6481 if use_debounce {
6482 cx.background_executor()
6483 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6484 .await;
6485 }
6486 let match_task = cx.background_spawn(async move {
6487 let buffer_ranges = multi_buffer_snapshot
6488 .range_to_buffer_ranges(multi_buffer_range_to_query)
6489 .into_iter()
6490 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6491 let mut match_ranges = Vec::new();
6492 let Ok(regex) = project::search::SearchQuery::text(
6493 query_text.clone(),
6494 false,
6495 false,
6496 false,
6497 Default::default(),
6498 Default::default(),
6499 false,
6500 None,
6501 ) else {
6502 return Vec::default();
6503 };
6504 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6505 match_ranges.extend(
6506 regex
6507 .search(&buffer_snapshot, Some(search_range.clone()))
6508 .await
6509 .into_iter()
6510 .filter_map(|match_range| {
6511 let match_start = buffer_snapshot
6512 .anchor_after(search_range.start + match_range.start);
6513 let match_end = buffer_snapshot
6514 .anchor_before(search_range.start + match_range.end);
6515 let match_anchor_range = Anchor::range_in_buffer(
6516 excerpt_id,
6517 buffer_snapshot.remote_id(),
6518 match_start..match_end,
6519 );
6520 (match_anchor_range != query_range).then_some(match_anchor_range)
6521 }),
6522 );
6523 }
6524 match_ranges
6525 });
6526 let match_ranges = match_task.await;
6527 editor
6528 .update_in(cx, |editor, _, cx| {
6529 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6530 if !match_ranges.is_empty() {
6531 editor.highlight_background::<SelectedTextHighlight>(
6532 &match_ranges,
6533 |theme| theme.colors().editor_document_highlight_bracket_background,
6534 cx,
6535 )
6536 }
6537 })
6538 .log_err();
6539 })
6540 }
6541
6542 fn refresh_selected_text_highlights(
6543 &mut self,
6544 on_buffer_edit: bool,
6545 window: &mut Window,
6546 cx: &mut Context<Editor>,
6547 ) {
6548 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6549 else {
6550 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6551 self.quick_selection_highlight_task.take();
6552 self.debounced_selection_highlight_task.take();
6553 return;
6554 };
6555 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6556 if on_buffer_edit
6557 || self
6558 .quick_selection_highlight_task
6559 .as_ref()
6560 .map_or(true, |(prev_anchor_range, _)| {
6561 prev_anchor_range != &query_range
6562 })
6563 {
6564 let multi_buffer_visible_start = self
6565 .scroll_manager
6566 .anchor()
6567 .anchor
6568 .to_point(&multi_buffer_snapshot);
6569 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6570 multi_buffer_visible_start
6571 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6572 Bias::Left,
6573 );
6574 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6575 self.quick_selection_highlight_task = Some((
6576 query_range.clone(),
6577 self.update_selection_occurrence_highlights(
6578 query_text.clone(),
6579 query_range.clone(),
6580 multi_buffer_visible_range,
6581 false,
6582 window,
6583 cx,
6584 ),
6585 ));
6586 }
6587 if on_buffer_edit
6588 || self
6589 .debounced_selection_highlight_task
6590 .as_ref()
6591 .map_or(true, |(prev_anchor_range, _)| {
6592 prev_anchor_range != &query_range
6593 })
6594 {
6595 let multi_buffer_start = multi_buffer_snapshot
6596 .anchor_before(0)
6597 .to_point(&multi_buffer_snapshot);
6598 let multi_buffer_end = multi_buffer_snapshot
6599 .anchor_after(multi_buffer_snapshot.len())
6600 .to_point(&multi_buffer_snapshot);
6601 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6602 self.debounced_selection_highlight_task = Some((
6603 query_range.clone(),
6604 self.update_selection_occurrence_highlights(
6605 query_text,
6606 query_range,
6607 multi_buffer_full_range,
6608 true,
6609 window,
6610 cx,
6611 ),
6612 ));
6613 }
6614 }
6615
6616 pub fn refresh_inline_completion(
6617 &mut self,
6618 debounce: bool,
6619 user_requested: bool,
6620 window: &mut Window,
6621 cx: &mut Context<Self>,
6622 ) -> Option<()> {
6623 let provider = self.edit_prediction_provider()?;
6624 let cursor = self.selections.newest_anchor().head();
6625 let (buffer, cursor_buffer_position) =
6626 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6627
6628 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6629 self.discard_inline_completion(false, cx);
6630 return None;
6631 }
6632
6633 if !user_requested
6634 && (!self.should_show_edit_predictions()
6635 || !self.is_focused(window)
6636 || buffer.read(cx).is_empty())
6637 {
6638 self.discard_inline_completion(false, cx);
6639 return None;
6640 }
6641
6642 self.update_visible_inline_completion(window, cx);
6643 provider.refresh(
6644 self.project.clone(),
6645 buffer,
6646 cursor_buffer_position,
6647 debounce,
6648 cx,
6649 );
6650 Some(())
6651 }
6652
6653 fn show_edit_predictions_in_menu(&self) -> bool {
6654 match self.edit_prediction_settings {
6655 EditPredictionSettings::Disabled => false,
6656 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6657 }
6658 }
6659
6660 pub fn edit_predictions_enabled(&self) -> bool {
6661 match self.edit_prediction_settings {
6662 EditPredictionSettings::Disabled => false,
6663 EditPredictionSettings::Enabled { .. } => true,
6664 }
6665 }
6666
6667 fn edit_prediction_requires_modifier(&self) -> bool {
6668 match self.edit_prediction_settings {
6669 EditPredictionSettings::Disabled => false,
6670 EditPredictionSettings::Enabled {
6671 preview_requires_modifier,
6672 ..
6673 } => preview_requires_modifier,
6674 }
6675 }
6676
6677 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6678 if self.edit_prediction_provider.is_none() {
6679 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6680 } else {
6681 let selection = self.selections.newest_anchor();
6682 let cursor = selection.head();
6683
6684 if let Some((buffer, cursor_buffer_position)) =
6685 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6686 {
6687 self.edit_prediction_settings =
6688 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6689 }
6690 }
6691 }
6692
6693 fn edit_prediction_settings_at_position(
6694 &self,
6695 buffer: &Entity<Buffer>,
6696 buffer_position: language::Anchor,
6697 cx: &App,
6698 ) -> EditPredictionSettings {
6699 if !self.mode.is_full()
6700 || !self.show_inline_completions_override.unwrap_or(true)
6701 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6702 {
6703 return EditPredictionSettings::Disabled;
6704 }
6705
6706 let buffer = buffer.read(cx);
6707
6708 let file = buffer.file();
6709
6710 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6711 return EditPredictionSettings::Disabled;
6712 };
6713
6714 let by_provider = matches!(
6715 self.menu_inline_completions_policy,
6716 MenuInlineCompletionsPolicy::ByProvider
6717 );
6718
6719 let show_in_menu = by_provider
6720 && self
6721 .edit_prediction_provider
6722 .as_ref()
6723 .map_or(false, |provider| {
6724 provider.provider.show_completions_in_menu()
6725 });
6726
6727 let preview_requires_modifier =
6728 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6729
6730 EditPredictionSettings::Enabled {
6731 show_in_menu,
6732 preview_requires_modifier,
6733 }
6734 }
6735
6736 fn should_show_edit_predictions(&self) -> bool {
6737 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6738 }
6739
6740 pub fn edit_prediction_preview_is_active(&self) -> bool {
6741 matches!(
6742 self.edit_prediction_preview,
6743 EditPredictionPreview::Active { .. }
6744 )
6745 }
6746
6747 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6748 let cursor = self.selections.newest_anchor().head();
6749 if let Some((buffer, cursor_position)) =
6750 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6751 {
6752 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6753 } else {
6754 false
6755 }
6756 }
6757
6758 pub fn supports_minimap(&self, cx: &App) -> bool {
6759 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6760 }
6761
6762 fn edit_predictions_enabled_in_buffer(
6763 &self,
6764 buffer: &Entity<Buffer>,
6765 buffer_position: language::Anchor,
6766 cx: &App,
6767 ) -> bool {
6768 maybe!({
6769 if self.read_only(cx) {
6770 return Some(false);
6771 }
6772 let provider = self.edit_prediction_provider()?;
6773 if !provider.is_enabled(&buffer, buffer_position, cx) {
6774 return Some(false);
6775 }
6776 let buffer = buffer.read(cx);
6777 let Some(file) = buffer.file() else {
6778 return Some(true);
6779 };
6780 let settings = all_language_settings(Some(file), cx);
6781 Some(settings.edit_predictions_enabled_for_file(file, cx))
6782 })
6783 .unwrap_or(false)
6784 }
6785
6786 fn cycle_inline_completion(
6787 &mut self,
6788 direction: Direction,
6789 window: &mut Window,
6790 cx: &mut Context<Self>,
6791 ) -> Option<()> {
6792 let provider = self.edit_prediction_provider()?;
6793 let cursor = self.selections.newest_anchor().head();
6794 let (buffer, cursor_buffer_position) =
6795 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6796 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6797 return None;
6798 }
6799
6800 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6801 self.update_visible_inline_completion(window, cx);
6802
6803 Some(())
6804 }
6805
6806 pub fn show_inline_completion(
6807 &mut self,
6808 _: &ShowEditPrediction,
6809 window: &mut Window,
6810 cx: &mut Context<Self>,
6811 ) {
6812 if !self.has_active_inline_completion() {
6813 self.refresh_inline_completion(false, true, window, cx);
6814 return;
6815 }
6816
6817 self.update_visible_inline_completion(window, cx);
6818 }
6819
6820 pub fn display_cursor_names(
6821 &mut self,
6822 _: &DisplayCursorNames,
6823 window: &mut Window,
6824 cx: &mut Context<Self>,
6825 ) {
6826 self.show_cursor_names(window, cx);
6827 }
6828
6829 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6830 self.show_cursor_names = true;
6831 cx.notify();
6832 cx.spawn_in(window, async move |this, cx| {
6833 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6834 this.update(cx, |this, cx| {
6835 this.show_cursor_names = false;
6836 cx.notify()
6837 })
6838 .ok()
6839 })
6840 .detach();
6841 }
6842
6843 pub fn next_edit_prediction(
6844 &mut self,
6845 _: &NextEditPrediction,
6846 window: &mut Window,
6847 cx: &mut Context<Self>,
6848 ) {
6849 if self.has_active_inline_completion() {
6850 self.cycle_inline_completion(Direction::Next, window, cx);
6851 } else {
6852 let is_copilot_disabled = self
6853 .refresh_inline_completion(false, true, window, cx)
6854 .is_none();
6855 if is_copilot_disabled {
6856 cx.propagate();
6857 }
6858 }
6859 }
6860
6861 pub fn previous_edit_prediction(
6862 &mut self,
6863 _: &PreviousEditPrediction,
6864 window: &mut Window,
6865 cx: &mut Context<Self>,
6866 ) {
6867 if self.has_active_inline_completion() {
6868 self.cycle_inline_completion(Direction::Prev, window, cx);
6869 } else {
6870 let is_copilot_disabled = self
6871 .refresh_inline_completion(false, true, window, cx)
6872 .is_none();
6873 if is_copilot_disabled {
6874 cx.propagate();
6875 }
6876 }
6877 }
6878
6879 pub fn accept_edit_prediction(
6880 &mut self,
6881 _: &AcceptEditPrediction,
6882 window: &mut Window,
6883 cx: &mut Context<Self>,
6884 ) {
6885 if self.show_edit_predictions_in_menu() {
6886 self.hide_context_menu(window, cx);
6887 }
6888
6889 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6890 return;
6891 };
6892
6893 self.report_inline_completion_event(
6894 active_inline_completion.completion_id.clone(),
6895 true,
6896 cx,
6897 );
6898
6899 match &active_inline_completion.completion {
6900 InlineCompletion::Move { target, .. } => {
6901 let target = *target;
6902
6903 if let Some(position_map) = &self.last_position_map {
6904 if position_map
6905 .visible_row_range
6906 .contains(&target.to_display_point(&position_map.snapshot).row())
6907 || !self.edit_prediction_requires_modifier()
6908 {
6909 self.unfold_ranges(&[target..target], true, false, cx);
6910 // Note that this is also done in vim's handler of the Tab action.
6911 self.change_selections(
6912 Some(Autoscroll::newest()),
6913 window,
6914 cx,
6915 |selections| {
6916 selections.select_anchor_ranges([target..target]);
6917 },
6918 );
6919 self.clear_row_highlights::<EditPredictionPreview>();
6920
6921 self.edit_prediction_preview
6922 .set_previous_scroll_position(None);
6923 } else {
6924 self.edit_prediction_preview
6925 .set_previous_scroll_position(Some(
6926 position_map.snapshot.scroll_anchor,
6927 ));
6928
6929 self.highlight_rows::<EditPredictionPreview>(
6930 target..target,
6931 cx.theme().colors().editor_highlighted_line_background,
6932 RowHighlightOptions {
6933 autoscroll: true,
6934 ..Default::default()
6935 },
6936 cx,
6937 );
6938 self.request_autoscroll(Autoscroll::fit(), cx);
6939 }
6940 }
6941 }
6942 InlineCompletion::Edit { edits, .. } => {
6943 if let Some(provider) = self.edit_prediction_provider() {
6944 provider.accept(cx);
6945 }
6946
6947 // Store the transaction ID and selections before applying the edit
6948 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6949
6950 let snapshot = self.buffer.read(cx).snapshot(cx);
6951 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6952
6953 self.buffer.update(cx, |buffer, cx| {
6954 buffer.edit(edits.iter().cloned(), None, cx)
6955 });
6956
6957 self.change_selections(None, window, cx, |s| {
6958 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6959 });
6960
6961 let selections = self.selections.disjoint_anchors();
6962 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6963 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6964 if has_new_transaction {
6965 self.selection_history
6966 .insert_transaction(transaction_id_now, selections);
6967 }
6968 }
6969
6970 self.update_visible_inline_completion(window, cx);
6971 if self.active_inline_completion.is_none() {
6972 self.refresh_inline_completion(true, true, window, cx);
6973 }
6974
6975 cx.notify();
6976 }
6977 }
6978
6979 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6980 }
6981
6982 pub fn accept_partial_inline_completion(
6983 &mut self,
6984 _: &AcceptPartialEditPrediction,
6985 window: &mut Window,
6986 cx: &mut Context<Self>,
6987 ) {
6988 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6989 return;
6990 };
6991 if self.selections.count() != 1 {
6992 return;
6993 }
6994
6995 self.report_inline_completion_event(
6996 active_inline_completion.completion_id.clone(),
6997 true,
6998 cx,
6999 );
7000
7001 match &active_inline_completion.completion {
7002 InlineCompletion::Move { target, .. } => {
7003 let target = *target;
7004 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7005 selections.select_anchor_ranges([target..target]);
7006 });
7007 }
7008 InlineCompletion::Edit { edits, .. } => {
7009 // Find an insertion that starts at the cursor position.
7010 let snapshot = self.buffer.read(cx).snapshot(cx);
7011 let cursor_offset = self.selections.newest::<usize>(cx).head();
7012 let insertion = edits.iter().find_map(|(range, text)| {
7013 let range = range.to_offset(&snapshot);
7014 if range.is_empty() && range.start == cursor_offset {
7015 Some(text)
7016 } else {
7017 None
7018 }
7019 });
7020
7021 if let Some(text) = insertion {
7022 let mut partial_completion = text
7023 .chars()
7024 .by_ref()
7025 .take_while(|c| c.is_alphabetic())
7026 .collect::<String>();
7027 if partial_completion.is_empty() {
7028 partial_completion = text
7029 .chars()
7030 .by_ref()
7031 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7032 .collect::<String>();
7033 }
7034
7035 cx.emit(EditorEvent::InputHandled {
7036 utf16_range_to_replace: None,
7037 text: partial_completion.clone().into(),
7038 });
7039
7040 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7041
7042 self.refresh_inline_completion(true, true, window, cx);
7043 cx.notify();
7044 } else {
7045 self.accept_edit_prediction(&Default::default(), window, cx);
7046 }
7047 }
7048 }
7049 }
7050
7051 fn discard_inline_completion(
7052 &mut self,
7053 should_report_inline_completion_event: bool,
7054 cx: &mut Context<Self>,
7055 ) -> bool {
7056 if should_report_inline_completion_event {
7057 let completion_id = self
7058 .active_inline_completion
7059 .as_ref()
7060 .and_then(|active_completion| active_completion.completion_id.clone());
7061
7062 self.report_inline_completion_event(completion_id, false, cx);
7063 }
7064
7065 if let Some(provider) = self.edit_prediction_provider() {
7066 provider.discard(cx);
7067 }
7068
7069 self.take_active_inline_completion(cx)
7070 }
7071
7072 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7073 let Some(provider) = self.edit_prediction_provider() else {
7074 return;
7075 };
7076
7077 let Some((_, buffer, _)) = self
7078 .buffer
7079 .read(cx)
7080 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7081 else {
7082 return;
7083 };
7084
7085 let extension = buffer
7086 .read(cx)
7087 .file()
7088 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7089
7090 let event_type = match accepted {
7091 true => "Edit Prediction Accepted",
7092 false => "Edit Prediction Discarded",
7093 };
7094 telemetry::event!(
7095 event_type,
7096 provider = provider.name(),
7097 prediction_id = id,
7098 suggestion_accepted = accepted,
7099 file_extension = extension,
7100 );
7101 }
7102
7103 pub fn has_active_inline_completion(&self) -> bool {
7104 self.active_inline_completion.is_some()
7105 }
7106
7107 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7108 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7109 return false;
7110 };
7111
7112 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7113 self.clear_highlights::<InlineCompletionHighlight>(cx);
7114 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7115 true
7116 }
7117
7118 /// Returns true when we're displaying the edit prediction popover below the cursor
7119 /// like we are not previewing and the LSP autocomplete menu is visible
7120 /// or we are in `when_holding_modifier` mode.
7121 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7122 if self.edit_prediction_preview_is_active()
7123 || !self.show_edit_predictions_in_menu()
7124 || !self.edit_predictions_enabled()
7125 {
7126 return false;
7127 }
7128
7129 if self.has_visible_completions_menu() {
7130 return true;
7131 }
7132
7133 has_completion && self.edit_prediction_requires_modifier()
7134 }
7135
7136 fn handle_modifiers_changed(
7137 &mut self,
7138 modifiers: Modifiers,
7139 position_map: &PositionMap,
7140 window: &mut Window,
7141 cx: &mut Context<Self>,
7142 ) {
7143 if self.show_edit_predictions_in_menu() {
7144 self.update_edit_prediction_preview(&modifiers, window, cx);
7145 }
7146
7147 self.update_selection_mode(&modifiers, position_map, window, cx);
7148
7149 let mouse_position = window.mouse_position();
7150 if !position_map.text_hitbox.is_hovered(window) {
7151 return;
7152 }
7153
7154 self.update_hovered_link(
7155 position_map.point_for_position(mouse_position),
7156 &position_map.snapshot,
7157 modifiers,
7158 window,
7159 cx,
7160 )
7161 }
7162
7163 fn multi_cursor_modifier(
7164 cursor_event: bool,
7165 modifiers: &Modifiers,
7166 cx: &mut Context<Self>,
7167 ) -> bool {
7168 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7169 if cursor_event {
7170 match multi_cursor_setting {
7171 MultiCursorModifier::Alt => modifiers.alt,
7172 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7173 }
7174 } else {
7175 match multi_cursor_setting {
7176 MultiCursorModifier::Alt => modifiers.secondary(),
7177 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7178 }
7179 }
7180 }
7181
7182 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7183 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7184 }
7185
7186 fn update_selection_mode(
7187 &mut self,
7188 modifiers: &Modifiers,
7189 position_map: &PositionMap,
7190 window: &mut Window,
7191 cx: &mut Context<Self>,
7192 ) {
7193 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7194 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7195 || self.selections.pending.is_none()
7196 {
7197 return;
7198 }
7199
7200 let mouse_position = window.mouse_position();
7201 let point_for_position = position_map.point_for_position(mouse_position);
7202 let position = point_for_position.previous_valid;
7203
7204 self.select(
7205 SelectPhase::BeginColumnar {
7206 position,
7207 reset: false,
7208 goal_column: point_for_position.exact_unclipped.column(),
7209 },
7210 window,
7211 cx,
7212 );
7213 }
7214
7215 fn update_edit_prediction_preview(
7216 &mut self,
7217 modifiers: &Modifiers,
7218 window: &mut Window,
7219 cx: &mut Context<Self>,
7220 ) {
7221 let mut modifiers_held = false;
7222 if let Some(accept_keystroke) = self
7223 .accept_edit_prediction_keybind(false, window, cx)
7224 .keystroke()
7225 {
7226 modifiers_held = modifiers_held
7227 || (&accept_keystroke.modifiers == modifiers
7228 && accept_keystroke.modifiers.modified());
7229 };
7230 if let Some(accept_partial_keystroke) = self
7231 .accept_edit_prediction_keybind(true, window, cx)
7232 .keystroke()
7233 {
7234 modifiers_held = modifiers_held
7235 || (&accept_partial_keystroke.modifiers == modifiers
7236 && accept_partial_keystroke.modifiers.modified());
7237 }
7238
7239 if modifiers_held {
7240 if matches!(
7241 self.edit_prediction_preview,
7242 EditPredictionPreview::Inactive { .. }
7243 ) {
7244 self.edit_prediction_preview = EditPredictionPreview::Active {
7245 previous_scroll_position: None,
7246 since: Instant::now(),
7247 };
7248
7249 self.update_visible_inline_completion(window, cx);
7250 cx.notify();
7251 }
7252 } else if let EditPredictionPreview::Active {
7253 previous_scroll_position,
7254 since,
7255 } = self.edit_prediction_preview
7256 {
7257 if let (Some(previous_scroll_position), Some(position_map)) =
7258 (previous_scroll_position, self.last_position_map.as_ref())
7259 {
7260 self.set_scroll_position(
7261 previous_scroll_position
7262 .scroll_position(&position_map.snapshot.display_snapshot),
7263 window,
7264 cx,
7265 );
7266 }
7267
7268 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7269 released_too_fast: since.elapsed() < Duration::from_millis(200),
7270 };
7271 self.clear_row_highlights::<EditPredictionPreview>();
7272 self.update_visible_inline_completion(window, cx);
7273 cx.notify();
7274 }
7275 }
7276
7277 fn update_visible_inline_completion(
7278 &mut self,
7279 _window: &mut Window,
7280 cx: &mut Context<Self>,
7281 ) -> Option<()> {
7282 let selection = self.selections.newest_anchor();
7283 let cursor = selection.head();
7284 let multibuffer = self.buffer.read(cx).snapshot(cx);
7285 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7286 let excerpt_id = cursor.excerpt_id;
7287
7288 let show_in_menu = self.show_edit_predictions_in_menu();
7289 let completions_menu_has_precedence = !show_in_menu
7290 && (self.context_menu.borrow().is_some()
7291 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7292
7293 if completions_menu_has_precedence
7294 || !offset_selection.is_empty()
7295 || self
7296 .active_inline_completion
7297 .as_ref()
7298 .map_or(false, |completion| {
7299 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7300 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7301 !invalidation_range.contains(&offset_selection.head())
7302 })
7303 {
7304 self.discard_inline_completion(false, cx);
7305 return None;
7306 }
7307
7308 self.take_active_inline_completion(cx);
7309 let Some(provider) = self.edit_prediction_provider() else {
7310 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7311 return None;
7312 };
7313
7314 let (buffer, cursor_buffer_position) =
7315 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7316
7317 self.edit_prediction_settings =
7318 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7319
7320 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7321
7322 if self.edit_prediction_indent_conflict {
7323 let cursor_point = cursor.to_point(&multibuffer);
7324
7325 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7326
7327 if let Some((_, indent)) = indents.iter().next() {
7328 if indent.len == cursor_point.column {
7329 self.edit_prediction_indent_conflict = false;
7330 }
7331 }
7332 }
7333
7334 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7335 let edits = inline_completion
7336 .edits
7337 .into_iter()
7338 .flat_map(|(range, new_text)| {
7339 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7340 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7341 Some((start..end, new_text))
7342 })
7343 .collect::<Vec<_>>();
7344 if edits.is_empty() {
7345 return None;
7346 }
7347
7348 let first_edit_start = edits.first().unwrap().0.start;
7349 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7350 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7351
7352 let last_edit_end = edits.last().unwrap().0.end;
7353 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7354 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7355
7356 let cursor_row = cursor.to_point(&multibuffer).row;
7357
7358 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7359
7360 let mut inlay_ids = Vec::new();
7361 let invalidation_row_range;
7362 let move_invalidation_row_range = if cursor_row < edit_start_row {
7363 Some(cursor_row..edit_end_row)
7364 } else if cursor_row > edit_end_row {
7365 Some(edit_start_row..cursor_row)
7366 } else {
7367 None
7368 };
7369 let is_move =
7370 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7371 let completion = if is_move {
7372 invalidation_row_range =
7373 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7374 let target = first_edit_start;
7375 InlineCompletion::Move { target, snapshot }
7376 } else {
7377 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7378 && !self.inline_completions_hidden_for_vim_mode;
7379
7380 if show_completions_in_buffer {
7381 if edits
7382 .iter()
7383 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7384 {
7385 let mut inlays = Vec::new();
7386 for (range, new_text) in &edits {
7387 let inlay = Inlay::inline_completion(
7388 post_inc(&mut self.next_inlay_id),
7389 range.start,
7390 new_text.as_str(),
7391 );
7392 inlay_ids.push(inlay.id);
7393 inlays.push(inlay);
7394 }
7395
7396 self.splice_inlays(&[], inlays, cx);
7397 } else {
7398 let background_color = cx.theme().status().deleted_background;
7399 let style = HighlightStyle {
7400 background_color: Some(background_color),
7401 ..Default::default()
7402 };
7403 self.highlight_text::<InlineCompletionHighlight>(
7404 edits
7405 .iter()
7406 .map(|(range, _)| (range.clone(), style))
7407 .collect(),
7408 cx,
7409 );
7410 }
7411 }
7412
7413 invalidation_row_range = edit_start_row..edit_end_row;
7414
7415 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7416 if provider.show_tab_accept_marker() {
7417 EditDisplayMode::TabAccept
7418 } else {
7419 EditDisplayMode::Inline
7420 }
7421 } else {
7422 EditDisplayMode::DiffPopover
7423 };
7424
7425 InlineCompletion::Edit {
7426 edits,
7427 edit_preview: inline_completion.edit_preview,
7428 display_mode,
7429 snapshot,
7430 }
7431 };
7432
7433 let invalidation_range = multibuffer
7434 .anchor_before(Point::new(invalidation_row_range.start, 0))
7435 ..multibuffer.anchor_after(Point::new(
7436 invalidation_row_range.end,
7437 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7438 ));
7439
7440 self.stale_inline_completion_in_menu = None;
7441 self.active_inline_completion = Some(InlineCompletionState {
7442 inlay_ids,
7443 completion,
7444 completion_id: inline_completion.id,
7445 invalidation_range,
7446 });
7447
7448 cx.notify();
7449
7450 Some(())
7451 }
7452
7453 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7454 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7455 }
7456
7457 fn clear_tasks(&mut self) {
7458 self.tasks.clear()
7459 }
7460
7461 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7462 if self.tasks.insert(key, value).is_some() {
7463 // This case should hopefully be rare, but just in case...
7464 log::error!(
7465 "multiple different run targets found on a single line, only the last target will be rendered"
7466 )
7467 }
7468 }
7469
7470 /// Get all display points of breakpoints that will be rendered within editor
7471 ///
7472 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7473 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7474 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7475 fn active_breakpoints(
7476 &self,
7477 range: Range<DisplayRow>,
7478 window: &mut Window,
7479 cx: &mut Context<Self>,
7480 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7481 let mut breakpoint_display_points = HashMap::default();
7482
7483 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7484 return breakpoint_display_points;
7485 };
7486
7487 let snapshot = self.snapshot(window, cx);
7488
7489 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7490 let Some(project) = self.project.as_ref() else {
7491 return breakpoint_display_points;
7492 };
7493
7494 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7495 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7496
7497 for (buffer_snapshot, range, excerpt_id) in
7498 multi_buffer_snapshot.range_to_buffer_ranges(range)
7499 {
7500 let Some(buffer) = project
7501 .read(cx)
7502 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7503 else {
7504 continue;
7505 };
7506 let breakpoints = breakpoint_store.read(cx).breakpoints(
7507 &buffer,
7508 Some(
7509 buffer_snapshot.anchor_before(range.start)
7510 ..buffer_snapshot.anchor_after(range.end),
7511 ),
7512 buffer_snapshot,
7513 cx,
7514 );
7515 for (breakpoint, state) in breakpoints {
7516 let multi_buffer_anchor =
7517 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7518 let position = multi_buffer_anchor
7519 .to_point(&multi_buffer_snapshot)
7520 .to_display_point(&snapshot);
7521
7522 breakpoint_display_points.insert(
7523 position.row(),
7524 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7525 );
7526 }
7527 }
7528
7529 breakpoint_display_points
7530 }
7531
7532 fn breakpoint_context_menu(
7533 &self,
7534 anchor: Anchor,
7535 window: &mut Window,
7536 cx: &mut Context<Self>,
7537 ) -> Entity<ui::ContextMenu> {
7538 let weak_editor = cx.weak_entity();
7539 let focus_handle = self.focus_handle(cx);
7540
7541 let row = self
7542 .buffer
7543 .read(cx)
7544 .snapshot(cx)
7545 .summary_for_anchor::<Point>(&anchor)
7546 .row;
7547
7548 let breakpoint = self
7549 .breakpoint_at_row(row, window, cx)
7550 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7551
7552 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7553 "Edit Log Breakpoint"
7554 } else {
7555 "Set Log Breakpoint"
7556 };
7557
7558 let condition_breakpoint_msg = if breakpoint
7559 .as_ref()
7560 .is_some_and(|bp| bp.1.condition.is_some())
7561 {
7562 "Edit Condition Breakpoint"
7563 } else {
7564 "Set Condition Breakpoint"
7565 };
7566
7567 let hit_condition_breakpoint_msg = if breakpoint
7568 .as_ref()
7569 .is_some_and(|bp| bp.1.hit_condition.is_some())
7570 {
7571 "Edit Hit Condition Breakpoint"
7572 } else {
7573 "Set Hit Condition Breakpoint"
7574 };
7575
7576 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7577 "Unset Breakpoint"
7578 } else {
7579 "Set Breakpoint"
7580 };
7581
7582 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7583
7584 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7585 BreakpointState::Enabled => Some("Disable"),
7586 BreakpointState::Disabled => Some("Enable"),
7587 });
7588
7589 let (anchor, breakpoint) =
7590 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7591
7592 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7593 menu.on_blur_subscription(Subscription::new(|| {}))
7594 .context(focus_handle)
7595 .when(run_to_cursor, |this| {
7596 let weak_editor = weak_editor.clone();
7597 this.entry("Run to cursor", None, move |window, cx| {
7598 weak_editor
7599 .update(cx, |editor, cx| {
7600 editor.change_selections(None, window, cx, |s| {
7601 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7602 });
7603 })
7604 .ok();
7605
7606 window.dispatch_action(Box::new(RunToCursor), cx);
7607 })
7608 .separator()
7609 })
7610 .when_some(toggle_state_msg, |this, msg| {
7611 this.entry(msg, None, {
7612 let weak_editor = weak_editor.clone();
7613 let breakpoint = breakpoint.clone();
7614 move |_window, cx| {
7615 weak_editor
7616 .update(cx, |this, cx| {
7617 this.edit_breakpoint_at_anchor(
7618 anchor,
7619 breakpoint.as_ref().clone(),
7620 BreakpointEditAction::InvertState,
7621 cx,
7622 );
7623 })
7624 .log_err();
7625 }
7626 })
7627 })
7628 .entry(set_breakpoint_msg, None, {
7629 let weak_editor = weak_editor.clone();
7630 let breakpoint = breakpoint.clone();
7631 move |_window, cx| {
7632 weak_editor
7633 .update(cx, |this, cx| {
7634 this.edit_breakpoint_at_anchor(
7635 anchor,
7636 breakpoint.as_ref().clone(),
7637 BreakpointEditAction::Toggle,
7638 cx,
7639 );
7640 })
7641 .log_err();
7642 }
7643 })
7644 .entry(log_breakpoint_msg, None, {
7645 let breakpoint = breakpoint.clone();
7646 let weak_editor = weak_editor.clone();
7647 move |window, cx| {
7648 weak_editor
7649 .update(cx, |this, cx| {
7650 this.add_edit_breakpoint_block(
7651 anchor,
7652 breakpoint.as_ref(),
7653 BreakpointPromptEditAction::Log,
7654 window,
7655 cx,
7656 );
7657 })
7658 .log_err();
7659 }
7660 })
7661 .entry(condition_breakpoint_msg, None, {
7662 let breakpoint = breakpoint.clone();
7663 let weak_editor = weak_editor.clone();
7664 move |window, cx| {
7665 weak_editor
7666 .update(cx, |this, cx| {
7667 this.add_edit_breakpoint_block(
7668 anchor,
7669 breakpoint.as_ref(),
7670 BreakpointPromptEditAction::Condition,
7671 window,
7672 cx,
7673 );
7674 })
7675 .log_err();
7676 }
7677 })
7678 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7679 weak_editor
7680 .update(cx, |this, cx| {
7681 this.add_edit_breakpoint_block(
7682 anchor,
7683 breakpoint.as_ref(),
7684 BreakpointPromptEditAction::HitCondition,
7685 window,
7686 cx,
7687 );
7688 })
7689 .log_err();
7690 })
7691 })
7692 }
7693
7694 fn render_breakpoint(
7695 &self,
7696 position: Anchor,
7697 row: DisplayRow,
7698 breakpoint: &Breakpoint,
7699 state: Option<BreakpointSessionState>,
7700 cx: &mut Context<Self>,
7701 ) -> IconButton {
7702 let is_rejected = state.is_some_and(|s| !s.verified);
7703 // Is it a breakpoint that shows up when hovering over gutter?
7704 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7705 (false, false),
7706 |PhantomBreakpointIndicator {
7707 is_active,
7708 display_row,
7709 collides_with_existing_breakpoint,
7710 }| {
7711 (
7712 is_active && display_row == row,
7713 collides_with_existing_breakpoint,
7714 )
7715 },
7716 );
7717
7718 let (color, icon) = {
7719 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7720 (false, false) => ui::IconName::DebugBreakpoint,
7721 (true, false) => ui::IconName::DebugLogBreakpoint,
7722 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7723 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7724 };
7725
7726 let color = if is_phantom {
7727 Color::Hint
7728 } else if is_rejected {
7729 Color::Disabled
7730 } else {
7731 Color::Debugger
7732 };
7733
7734 (color, icon)
7735 };
7736
7737 let breakpoint = Arc::from(breakpoint.clone());
7738
7739 let alt_as_text = gpui::Keystroke {
7740 modifiers: Modifiers::secondary_key(),
7741 ..Default::default()
7742 };
7743 let primary_action_text = if breakpoint.is_disabled() {
7744 "Enable breakpoint"
7745 } else if is_phantom && !collides_with_existing {
7746 "Set breakpoint"
7747 } else {
7748 "Unset breakpoint"
7749 };
7750 let focus_handle = self.focus_handle.clone();
7751
7752 let meta = if is_rejected {
7753 SharedString::from("No executable code is associated with this line.")
7754 } else if collides_with_existing && !breakpoint.is_disabled() {
7755 SharedString::from(format!(
7756 "{alt_as_text}-click to disable,\nright-click for more options."
7757 ))
7758 } else {
7759 SharedString::from("Right-click for more options.")
7760 };
7761 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7762 .icon_size(IconSize::XSmall)
7763 .size(ui::ButtonSize::None)
7764 .when(is_rejected, |this| {
7765 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7766 })
7767 .icon_color(color)
7768 .style(ButtonStyle::Transparent)
7769 .on_click(cx.listener({
7770 let breakpoint = breakpoint.clone();
7771
7772 move |editor, event: &ClickEvent, window, cx| {
7773 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7774 BreakpointEditAction::InvertState
7775 } else {
7776 BreakpointEditAction::Toggle
7777 };
7778
7779 window.focus(&editor.focus_handle(cx));
7780 editor.edit_breakpoint_at_anchor(
7781 position,
7782 breakpoint.as_ref().clone(),
7783 edit_action,
7784 cx,
7785 );
7786 }
7787 }))
7788 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7789 editor.set_breakpoint_context_menu(
7790 row,
7791 Some(position),
7792 event.down.position,
7793 window,
7794 cx,
7795 );
7796 }))
7797 .tooltip(move |window, cx| {
7798 Tooltip::with_meta_in(
7799 primary_action_text,
7800 Some(&ToggleBreakpoint),
7801 meta.clone(),
7802 &focus_handle,
7803 window,
7804 cx,
7805 )
7806 })
7807 }
7808
7809 fn build_tasks_context(
7810 project: &Entity<Project>,
7811 buffer: &Entity<Buffer>,
7812 buffer_row: u32,
7813 tasks: &Arc<RunnableTasks>,
7814 cx: &mut Context<Self>,
7815 ) -> Task<Option<task::TaskContext>> {
7816 let position = Point::new(buffer_row, tasks.column);
7817 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7818 let location = Location {
7819 buffer: buffer.clone(),
7820 range: range_start..range_start,
7821 };
7822 // Fill in the environmental variables from the tree-sitter captures
7823 let mut captured_task_variables = TaskVariables::default();
7824 for (capture_name, value) in tasks.extra_variables.clone() {
7825 captured_task_variables.insert(
7826 task::VariableName::Custom(capture_name.into()),
7827 value.clone(),
7828 );
7829 }
7830 project.update(cx, |project, cx| {
7831 project.task_store().update(cx, |task_store, cx| {
7832 task_store.task_context_for_location(captured_task_variables, location, cx)
7833 })
7834 })
7835 }
7836
7837 pub fn spawn_nearest_task(
7838 &mut self,
7839 action: &SpawnNearestTask,
7840 window: &mut Window,
7841 cx: &mut Context<Self>,
7842 ) {
7843 let Some((workspace, _)) = self.workspace.clone() else {
7844 return;
7845 };
7846 let Some(project) = self.project.clone() else {
7847 return;
7848 };
7849
7850 // Try to find a closest, enclosing node using tree-sitter that has a
7851 // task
7852 let Some((buffer, buffer_row, tasks)) = self
7853 .find_enclosing_node_task(cx)
7854 // Or find the task that's closest in row-distance.
7855 .or_else(|| self.find_closest_task(cx))
7856 else {
7857 return;
7858 };
7859
7860 let reveal_strategy = action.reveal;
7861 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7862 cx.spawn_in(window, async move |_, cx| {
7863 let context = task_context.await?;
7864 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7865
7866 let resolved = &mut resolved_task.resolved;
7867 resolved.reveal = reveal_strategy;
7868
7869 workspace
7870 .update_in(cx, |workspace, window, cx| {
7871 workspace.schedule_resolved_task(
7872 task_source_kind,
7873 resolved_task,
7874 false,
7875 window,
7876 cx,
7877 );
7878 })
7879 .ok()
7880 })
7881 .detach();
7882 }
7883
7884 fn find_closest_task(
7885 &mut self,
7886 cx: &mut Context<Self>,
7887 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7888 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7889
7890 let ((buffer_id, row), tasks) = self
7891 .tasks
7892 .iter()
7893 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7894
7895 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7896 let tasks = Arc::new(tasks.to_owned());
7897 Some((buffer, *row, tasks))
7898 }
7899
7900 fn find_enclosing_node_task(
7901 &mut self,
7902 cx: &mut Context<Self>,
7903 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7904 let snapshot = self.buffer.read(cx).snapshot(cx);
7905 let offset = self.selections.newest::<usize>(cx).head();
7906 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7907 let buffer_id = excerpt.buffer().remote_id();
7908
7909 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7910 let mut cursor = layer.node().walk();
7911
7912 while cursor.goto_first_child_for_byte(offset).is_some() {
7913 if cursor.node().end_byte() == offset {
7914 cursor.goto_next_sibling();
7915 }
7916 }
7917
7918 // Ascend to the smallest ancestor that contains the range and has a task.
7919 loop {
7920 let node = cursor.node();
7921 let node_range = node.byte_range();
7922 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7923
7924 // Check if this node contains our offset
7925 if node_range.start <= offset && node_range.end >= offset {
7926 // If it contains offset, check for task
7927 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7928 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7929 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7930 }
7931 }
7932
7933 if !cursor.goto_parent() {
7934 break;
7935 }
7936 }
7937 None
7938 }
7939
7940 fn render_run_indicator(
7941 &self,
7942 _style: &EditorStyle,
7943 is_active: bool,
7944 row: DisplayRow,
7945 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7946 cx: &mut Context<Self>,
7947 ) -> IconButton {
7948 let color = Color::Muted;
7949 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7950
7951 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7952 .shape(ui::IconButtonShape::Square)
7953 .icon_size(IconSize::XSmall)
7954 .icon_color(color)
7955 .toggle_state(is_active)
7956 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7957 let quick_launch = e.down.button == MouseButton::Left;
7958 window.focus(&editor.focus_handle(cx));
7959 editor.toggle_code_actions(
7960 &ToggleCodeActions {
7961 deployed_from: Some(CodeActionSource::RunMenu(row)),
7962 quick_launch,
7963 },
7964 window,
7965 cx,
7966 );
7967 }))
7968 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7969 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7970 }))
7971 }
7972
7973 pub fn context_menu_visible(&self) -> bool {
7974 !self.edit_prediction_preview_is_active()
7975 && self
7976 .context_menu
7977 .borrow()
7978 .as_ref()
7979 .map_or(false, |menu| menu.visible())
7980 }
7981
7982 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7983 self.context_menu
7984 .borrow()
7985 .as_ref()
7986 .map(|menu| menu.origin())
7987 }
7988
7989 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7990 self.context_menu_options = Some(options);
7991 }
7992
7993 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7994 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7995
7996 fn render_edit_prediction_popover(
7997 &mut self,
7998 text_bounds: &Bounds<Pixels>,
7999 content_origin: gpui::Point<Pixels>,
8000 right_margin: Pixels,
8001 editor_snapshot: &EditorSnapshot,
8002 visible_row_range: Range<DisplayRow>,
8003 scroll_top: f32,
8004 scroll_bottom: f32,
8005 line_layouts: &[LineWithInvisibles],
8006 line_height: Pixels,
8007 scroll_pixel_position: gpui::Point<Pixels>,
8008 newest_selection_head: Option<DisplayPoint>,
8009 editor_width: Pixels,
8010 style: &EditorStyle,
8011 window: &mut Window,
8012 cx: &mut App,
8013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8014 if self.mode().is_minimap() {
8015 return None;
8016 }
8017 let active_inline_completion = self.active_inline_completion.as_ref()?;
8018
8019 if self.edit_prediction_visible_in_cursor_popover(true) {
8020 return None;
8021 }
8022
8023 match &active_inline_completion.completion {
8024 InlineCompletion::Move { target, .. } => {
8025 let target_display_point = target.to_display_point(editor_snapshot);
8026
8027 if self.edit_prediction_requires_modifier() {
8028 if !self.edit_prediction_preview_is_active() {
8029 return None;
8030 }
8031
8032 self.render_edit_prediction_modifier_jump_popover(
8033 text_bounds,
8034 content_origin,
8035 visible_row_range,
8036 line_layouts,
8037 line_height,
8038 scroll_pixel_position,
8039 newest_selection_head,
8040 target_display_point,
8041 window,
8042 cx,
8043 )
8044 } else {
8045 self.render_edit_prediction_eager_jump_popover(
8046 text_bounds,
8047 content_origin,
8048 editor_snapshot,
8049 visible_row_range,
8050 scroll_top,
8051 scroll_bottom,
8052 line_height,
8053 scroll_pixel_position,
8054 target_display_point,
8055 editor_width,
8056 window,
8057 cx,
8058 )
8059 }
8060 }
8061 InlineCompletion::Edit {
8062 display_mode: EditDisplayMode::Inline,
8063 ..
8064 } => None,
8065 InlineCompletion::Edit {
8066 display_mode: EditDisplayMode::TabAccept,
8067 edits,
8068 ..
8069 } => {
8070 let range = &edits.first()?.0;
8071 let target_display_point = range.end.to_display_point(editor_snapshot);
8072
8073 self.render_edit_prediction_end_of_line_popover(
8074 "Accept",
8075 editor_snapshot,
8076 visible_row_range,
8077 target_display_point,
8078 line_height,
8079 scroll_pixel_position,
8080 content_origin,
8081 editor_width,
8082 window,
8083 cx,
8084 )
8085 }
8086 InlineCompletion::Edit {
8087 edits,
8088 edit_preview,
8089 display_mode: EditDisplayMode::DiffPopover,
8090 snapshot,
8091 } => self.render_edit_prediction_diff_popover(
8092 text_bounds,
8093 content_origin,
8094 right_margin,
8095 editor_snapshot,
8096 visible_row_range,
8097 line_layouts,
8098 line_height,
8099 scroll_pixel_position,
8100 newest_selection_head,
8101 editor_width,
8102 style,
8103 edits,
8104 edit_preview,
8105 snapshot,
8106 window,
8107 cx,
8108 ),
8109 }
8110 }
8111
8112 fn render_edit_prediction_modifier_jump_popover(
8113 &mut self,
8114 text_bounds: &Bounds<Pixels>,
8115 content_origin: gpui::Point<Pixels>,
8116 visible_row_range: Range<DisplayRow>,
8117 line_layouts: &[LineWithInvisibles],
8118 line_height: Pixels,
8119 scroll_pixel_position: gpui::Point<Pixels>,
8120 newest_selection_head: Option<DisplayPoint>,
8121 target_display_point: DisplayPoint,
8122 window: &mut Window,
8123 cx: &mut App,
8124 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8125 let scrolled_content_origin =
8126 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8127
8128 const SCROLL_PADDING_Y: Pixels = px(12.);
8129
8130 if target_display_point.row() < visible_row_range.start {
8131 return self.render_edit_prediction_scroll_popover(
8132 |_| SCROLL_PADDING_Y,
8133 IconName::ArrowUp,
8134 visible_row_range,
8135 line_layouts,
8136 newest_selection_head,
8137 scrolled_content_origin,
8138 window,
8139 cx,
8140 );
8141 } else if target_display_point.row() >= visible_row_range.end {
8142 return self.render_edit_prediction_scroll_popover(
8143 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8144 IconName::ArrowDown,
8145 visible_row_range,
8146 line_layouts,
8147 newest_selection_head,
8148 scrolled_content_origin,
8149 window,
8150 cx,
8151 );
8152 }
8153
8154 const POLE_WIDTH: Pixels = px(2.);
8155
8156 let line_layout =
8157 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8158 let target_column = target_display_point.column() as usize;
8159
8160 let target_x = line_layout.x_for_index(target_column);
8161 let target_y =
8162 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8163
8164 let flag_on_right = target_x < text_bounds.size.width / 2.;
8165
8166 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8167 border_color.l += 0.001;
8168
8169 let mut element = v_flex()
8170 .items_end()
8171 .when(flag_on_right, |el| el.items_start())
8172 .child(if flag_on_right {
8173 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8174 .rounded_bl(px(0.))
8175 .rounded_tl(px(0.))
8176 .border_l_2()
8177 .border_color(border_color)
8178 } else {
8179 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8180 .rounded_br(px(0.))
8181 .rounded_tr(px(0.))
8182 .border_r_2()
8183 .border_color(border_color)
8184 })
8185 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8186 .into_any();
8187
8188 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8189
8190 let mut origin = scrolled_content_origin + point(target_x, target_y)
8191 - point(
8192 if flag_on_right {
8193 POLE_WIDTH
8194 } else {
8195 size.width - POLE_WIDTH
8196 },
8197 size.height - line_height,
8198 );
8199
8200 origin.x = origin.x.max(content_origin.x);
8201
8202 element.prepaint_at(origin, window, cx);
8203
8204 Some((element, origin))
8205 }
8206
8207 fn render_edit_prediction_scroll_popover(
8208 &mut self,
8209 to_y: impl Fn(Size<Pixels>) -> Pixels,
8210 scroll_icon: IconName,
8211 visible_row_range: Range<DisplayRow>,
8212 line_layouts: &[LineWithInvisibles],
8213 newest_selection_head: Option<DisplayPoint>,
8214 scrolled_content_origin: gpui::Point<Pixels>,
8215 window: &mut Window,
8216 cx: &mut App,
8217 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8218 let mut element = self
8219 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8220 .into_any();
8221
8222 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8223
8224 let cursor = newest_selection_head?;
8225 let cursor_row_layout =
8226 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8227 let cursor_column = cursor.column() as usize;
8228
8229 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8230
8231 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8232
8233 element.prepaint_at(origin, window, cx);
8234 Some((element, origin))
8235 }
8236
8237 fn render_edit_prediction_eager_jump_popover(
8238 &mut self,
8239 text_bounds: &Bounds<Pixels>,
8240 content_origin: gpui::Point<Pixels>,
8241 editor_snapshot: &EditorSnapshot,
8242 visible_row_range: Range<DisplayRow>,
8243 scroll_top: f32,
8244 scroll_bottom: f32,
8245 line_height: Pixels,
8246 scroll_pixel_position: gpui::Point<Pixels>,
8247 target_display_point: DisplayPoint,
8248 editor_width: Pixels,
8249 window: &mut Window,
8250 cx: &mut App,
8251 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8252 if target_display_point.row().as_f32() < scroll_top {
8253 let mut element = self
8254 .render_edit_prediction_line_popover(
8255 "Jump to Edit",
8256 Some(IconName::ArrowUp),
8257 window,
8258 cx,
8259 )?
8260 .into_any();
8261
8262 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8263 let offset = point(
8264 (text_bounds.size.width - size.width) / 2.,
8265 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8266 );
8267
8268 let origin = text_bounds.origin + offset;
8269 element.prepaint_at(origin, window, cx);
8270 Some((element, origin))
8271 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8272 let mut element = self
8273 .render_edit_prediction_line_popover(
8274 "Jump to Edit",
8275 Some(IconName::ArrowDown),
8276 window,
8277 cx,
8278 )?
8279 .into_any();
8280
8281 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8282 let offset = point(
8283 (text_bounds.size.width - size.width) / 2.,
8284 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8285 );
8286
8287 let origin = text_bounds.origin + offset;
8288 element.prepaint_at(origin, window, cx);
8289 Some((element, origin))
8290 } else {
8291 self.render_edit_prediction_end_of_line_popover(
8292 "Jump to Edit",
8293 editor_snapshot,
8294 visible_row_range,
8295 target_display_point,
8296 line_height,
8297 scroll_pixel_position,
8298 content_origin,
8299 editor_width,
8300 window,
8301 cx,
8302 )
8303 }
8304 }
8305
8306 fn render_edit_prediction_end_of_line_popover(
8307 self: &mut Editor,
8308 label: &'static str,
8309 editor_snapshot: &EditorSnapshot,
8310 visible_row_range: Range<DisplayRow>,
8311 target_display_point: DisplayPoint,
8312 line_height: Pixels,
8313 scroll_pixel_position: gpui::Point<Pixels>,
8314 content_origin: gpui::Point<Pixels>,
8315 editor_width: Pixels,
8316 window: &mut Window,
8317 cx: &mut App,
8318 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8319 let target_line_end = DisplayPoint::new(
8320 target_display_point.row(),
8321 editor_snapshot.line_len(target_display_point.row()),
8322 );
8323
8324 let mut element = self
8325 .render_edit_prediction_line_popover(label, None, window, cx)?
8326 .into_any();
8327
8328 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8329
8330 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8331
8332 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8333 let mut origin = start_point
8334 + line_origin
8335 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8336 origin.x = origin.x.max(content_origin.x);
8337
8338 let max_x = content_origin.x + editor_width - size.width;
8339
8340 if origin.x > max_x {
8341 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8342
8343 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8344 origin.y += offset;
8345 IconName::ArrowUp
8346 } else {
8347 origin.y -= offset;
8348 IconName::ArrowDown
8349 };
8350
8351 element = self
8352 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8353 .into_any();
8354
8355 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8356
8357 origin.x = content_origin.x + editor_width - size.width - px(2.);
8358 }
8359
8360 element.prepaint_at(origin, window, cx);
8361 Some((element, origin))
8362 }
8363
8364 fn render_edit_prediction_diff_popover(
8365 self: &Editor,
8366 text_bounds: &Bounds<Pixels>,
8367 content_origin: gpui::Point<Pixels>,
8368 right_margin: Pixels,
8369 editor_snapshot: &EditorSnapshot,
8370 visible_row_range: Range<DisplayRow>,
8371 line_layouts: &[LineWithInvisibles],
8372 line_height: Pixels,
8373 scroll_pixel_position: gpui::Point<Pixels>,
8374 newest_selection_head: Option<DisplayPoint>,
8375 editor_width: Pixels,
8376 style: &EditorStyle,
8377 edits: &Vec<(Range<Anchor>, String)>,
8378 edit_preview: &Option<language::EditPreview>,
8379 snapshot: &language::BufferSnapshot,
8380 window: &mut Window,
8381 cx: &mut App,
8382 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8383 let edit_start = edits
8384 .first()
8385 .unwrap()
8386 .0
8387 .start
8388 .to_display_point(editor_snapshot);
8389 let edit_end = edits
8390 .last()
8391 .unwrap()
8392 .0
8393 .end
8394 .to_display_point(editor_snapshot);
8395
8396 let is_visible = visible_row_range.contains(&edit_start.row())
8397 || visible_row_range.contains(&edit_end.row());
8398 if !is_visible {
8399 return None;
8400 }
8401
8402 let highlighted_edits =
8403 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8404
8405 let styled_text = highlighted_edits.to_styled_text(&style.text);
8406 let line_count = highlighted_edits.text.lines().count();
8407
8408 const BORDER_WIDTH: Pixels = px(1.);
8409
8410 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8411 let has_keybind = keybind.is_some();
8412
8413 let mut element = h_flex()
8414 .items_start()
8415 .child(
8416 h_flex()
8417 .bg(cx.theme().colors().editor_background)
8418 .border(BORDER_WIDTH)
8419 .shadow_sm()
8420 .border_color(cx.theme().colors().border)
8421 .rounded_l_lg()
8422 .when(line_count > 1, |el| el.rounded_br_lg())
8423 .pr_1()
8424 .child(styled_text),
8425 )
8426 .child(
8427 h_flex()
8428 .h(line_height + BORDER_WIDTH * 2.)
8429 .px_1p5()
8430 .gap_1()
8431 // Workaround: For some reason, there's a gap if we don't do this
8432 .ml(-BORDER_WIDTH)
8433 .shadow(vec![gpui::BoxShadow {
8434 color: gpui::black().opacity(0.05),
8435 offset: point(px(1.), px(1.)),
8436 blur_radius: px(2.),
8437 spread_radius: px(0.),
8438 }])
8439 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8440 .border(BORDER_WIDTH)
8441 .border_color(cx.theme().colors().border)
8442 .rounded_r_lg()
8443 .id("edit_prediction_diff_popover_keybind")
8444 .when(!has_keybind, |el| {
8445 let status_colors = cx.theme().status();
8446
8447 el.bg(status_colors.error_background)
8448 .border_color(status_colors.error.opacity(0.6))
8449 .child(Icon::new(IconName::Info).color(Color::Error))
8450 .cursor_default()
8451 .hoverable_tooltip(move |_window, cx| {
8452 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8453 })
8454 })
8455 .children(keybind),
8456 )
8457 .into_any();
8458
8459 let longest_row =
8460 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8461 let longest_line_width = if visible_row_range.contains(&longest_row) {
8462 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8463 } else {
8464 layout_line(
8465 longest_row,
8466 editor_snapshot,
8467 style,
8468 editor_width,
8469 |_| false,
8470 window,
8471 cx,
8472 )
8473 .width
8474 };
8475
8476 let viewport_bounds =
8477 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8478 right: -right_margin,
8479 ..Default::default()
8480 });
8481
8482 let x_after_longest =
8483 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8484 - scroll_pixel_position.x;
8485
8486 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8487
8488 // Fully visible if it can be displayed within the window (allow overlapping other
8489 // panes). However, this is only allowed if the popover starts within text_bounds.
8490 let can_position_to_the_right = x_after_longest < text_bounds.right()
8491 && x_after_longest + element_bounds.width < viewport_bounds.right();
8492
8493 let mut origin = if can_position_to_the_right {
8494 point(
8495 x_after_longest,
8496 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8497 - scroll_pixel_position.y,
8498 )
8499 } else {
8500 let cursor_row = newest_selection_head.map(|head| head.row());
8501 let above_edit = edit_start
8502 .row()
8503 .0
8504 .checked_sub(line_count as u32)
8505 .map(DisplayRow);
8506 let below_edit = Some(edit_end.row() + 1);
8507 let above_cursor =
8508 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8509 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8510
8511 // Place the edit popover adjacent to the edit if there is a location
8512 // available that is onscreen and does not obscure the cursor. Otherwise,
8513 // place it adjacent to the cursor.
8514 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8515 .into_iter()
8516 .flatten()
8517 .find(|&start_row| {
8518 let end_row = start_row + line_count as u32;
8519 visible_row_range.contains(&start_row)
8520 && visible_row_range.contains(&end_row)
8521 && cursor_row.map_or(true, |cursor_row| {
8522 !((start_row..end_row).contains(&cursor_row))
8523 })
8524 })?;
8525
8526 content_origin
8527 + point(
8528 -scroll_pixel_position.x,
8529 row_target.as_f32() * line_height - scroll_pixel_position.y,
8530 )
8531 };
8532
8533 origin.x -= BORDER_WIDTH;
8534
8535 window.defer_draw(element, origin, 1);
8536
8537 // Do not return an element, since it will already be drawn due to defer_draw.
8538 None
8539 }
8540
8541 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8542 px(30.)
8543 }
8544
8545 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8546 if self.read_only(cx) {
8547 cx.theme().players().read_only()
8548 } else {
8549 self.style.as_ref().unwrap().local_player
8550 }
8551 }
8552
8553 fn render_edit_prediction_accept_keybind(
8554 &self,
8555 window: &mut Window,
8556 cx: &App,
8557 ) -> Option<AnyElement> {
8558 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8559 let accept_keystroke = accept_binding.keystroke()?;
8560
8561 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8562
8563 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8564 Color::Accent
8565 } else {
8566 Color::Muted
8567 };
8568
8569 h_flex()
8570 .px_0p5()
8571 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8572 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8573 .text_size(TextSize::XSmall.rems(cx))
8574 .child(h_flex().children(ui::render_modifiers(
8575 &accept_keystroke.modifiers,
8576 PlatformStyle::platform(),
8577 Some(modifiers_color),
8578 Some(IconSize::XSmall.rems().into()),
8579 true,
8580 )))
8581 .when(is_platform_style_mac, |parent| {
8582 parent.child(accept_keystroke.key.clone())
8583 })
8584 .when(!is_platform_style_mac, |parent| {
8585 parent.child(
8586 Key::new(
8587 util::capitalize(&accept_keystroke.key),
8588 Some(Color::Default),
8589 )
8590 .size(Some(IconSize::XSmall.rems().into())),
8591 )
8592 })
8593 .into_any()
8594 .into()
8595 }
8596
8597 fn render_edit_prediction_line_popover(
8598 &self,
8599 label: impl Into<SharedString>,
8600 icon: Option<IconName>,
8601 window: &mut Window,
8602 cx: &App,
8603 ) -> Option<Stateful<Div>> {
8604 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8605
8606 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8607 let has_keybind = keybind.is_some();
8608
8609 let result = h_flex()
8610 .id("ep-line-popover")
8611 .py_0p5()
8612 .pl_1()
8613 .pr(padding_right)
8614 .gap_1()
8615 .rounded_md()
8616 .border_1()
8617 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8618 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8619 .shadow_sm()
8620 .when(!has_keybind, |el| {
8621 let status_colors = cx.theme().status();
8622
8623 el.bg(status_colors.error_background)
8624 .border_color(status_colors.error.opacity(0.6))
8625 .pl_2()
8626 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8627 .cursor_default()
8628 .hoverable_tooltip(move |_window, cx| {
8629 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8630 })
8631 })
8632 .children(keybind)
8633 .child(
8634 Label::new(label)
8635 .size(LabelSize::Small)
8636 .when(!has_keybind, |el| {
8637 el.color(cx.theme().status().error.into()).strikethrough()
8638 }),
8639 )
8640 .when(!has_keybind, |el| {
8641 el.child(
8642 h_flex().ml_1().child(
8643 Icon::new(IconName::Info)
8644 .size(IconSize::Small)
8645 .color(cx.theme().status().error.into()),
8646 ),
8647 )
8648 })
8649 .when_some(icon, |element, icon| {
8650 element.child(
8651 div()
8652 .mt(px(1.5))
8653 .child(Icon::new(icon).size(IconSize::Small)),
8654 )
8655 });
8656
8657 Some(result)
8658 }
8659
8660 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8661 let accent_color = cx.theme().colors().text_accent;
8662 let editor_bg_color = cx.theme().colors().editor_background;
8663 editor_bg_color.blend(accent_color.opacity(0.1))
8664 }
8665
8666 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8667 let accent_color = cx.theme().colors().text_accent;
8668 let editor_bg_color = cx.theme().colors().editor_background;
8669 editor_bg_color.blend(accent_color.opacity(0.6))
8670 }
8671
8672 fn render_edit_prediction_cursor_popover(
8673 &self,
8674 min_width: Pixels,
8675 max_width: Pixels,
8676 cursor_point: Point,
8677 style: &EditorStyle,
8678 accept_keystroke: Option<&gpui::Keystroke>,
8679 _window: &Window,
8680 cx: &mut Context<Editor>,
8681 ) -> Option<AnyElement> {
8682 let provider = self.edit_prediction_provider.as_ref()?;
8683
8684 if provider.provider.needs_terms_acceptance(cx) {
8685 return Some(
8686 h_flex()
8687 .min_w(min_width)
8688 .flex_1()
8689 .px_2()
8690 .py_1()
8691 .gap_3()
8692 .elevation_2(cx)
8693 .hover(|style| style.bg(cx.theme().colors().element_hover))
8694 .id("accept-terms")
8695 .cursor_pointer()
8696 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8697 .on_click(cx.listener(|this, _event, window, cx| {
8698 cx.stop_propagation();
8699 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8700 window.dispatch_action(
8701 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8702 cx,
8703 );
8704 }))
8705 .child(
8706 h_flex()
8707 .flex_1()
8708 .gap_2()
8709 .child(Icon::new(IconName::ZedPredict))
8710 .child(Label::new("Accept Terms of Service"))
8711 .child(div().w_full())
8712 .child(
8713 Icon::new(IconName::ArrowUpRight)
8714 .color(Color::Muted)
8715 .size(IconSize::Small),
8716 )
8717 .into_any_element(),
8718 )
8719 .into_any(),
8720 );
8721 }
8722
8723 let is_refreshing = provider.provider.is_refreshing(cx);
8724
8725 fn pending_completion_container() -> Div {
8726 h_flex()
8727 .h_full()
8728 .flex_1()
8729 .gap_2()
8730 .child(Icon::new(IconName::ZedPredict))
8731 }
8732
8733 let completion = match &self.active_inline_completion {
8734 Some(prediction) => {
8735 if !self.has_visible_completions_menu() {
8736 const RADIUS: Pixels = px(6.);
8737 const BORDER_WIDTH: Pixels = px(1.);
8738
8739 return Some(
8740 h_flex()
8741 .elevation_2(cx)
8742 .border(BORDER_WIDTH)
8743 .border_color(cx.theme().colors().border)
8744 .when(accept_keystroke.is_none(), |el| {
8745 el.border_color(cx.theme().status().error)
8746 })
8747 .rounded(RADIUS)
8748 .rounded_tl(px(0.))
8749 .overflow_hidden()
8750 .child(div().px_1p5().child(match &prediction.completion {
8751 InlineCompletion::Move { target, snapshot } => {
8752 use text::ToPoint as _;
8753 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8754 {
8755 Icon::new(IconName::ZedPredictDown)
8756 } else {
8757 Icon::new(IconName::ZedPredictUp)
8758 }
8759 }
8760 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8761 }))
8762 .child(
8763 h_flex()
8764 .gap_1()
8765 .py_1()
8766 .px_2()
8767 .rounded_r(RADIUS - BORDER_WIDTH)
8768 .border_l_1()
8769 .border_color(cx.theme().colors().border)
8770 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8771 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8772 el.child(
8773 Label::new("Hold")
8774 .size(LabelSize::Small)
8775 .when(accept_keystroke.is_none(), |el| {
8776 el.strikethrough()
8777 })
8778 .line_height_style(LineHeightStyle::UiLabel),
8779 )
8780 })
8781 .id("edit_prediction_cursor_popover_keybind")
8782 .when(accept_keystroke.is_none(), |el| {
8783 let status_colors = cx.theme().status();
8784
8785 el.bg(status_colors.error_background)
8786 .border_color(status_colors.error.opacity(0.6))
8787 .child(Icon::new(IconName::Info).color(Color::Error))
8788 .cursor_default()
8789 .hoverable_tooltip(move |_window, cx| {
8790 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8791 .into()
8792 })
8793 })
8794 .when_some(
8795 accept_keystroke.as_ref(),
8796 |el, accept_keystroke| {
8797 el.child(h_flex().children(ui::render_modifiers(
8798 &accept_keystroke.modifiers,
8799 PlatformStyle::platform(),
8800 Some(Color::Default),
8801 Some(IconSize::XSmall.rems().into()),
8802 false,
8803 )))
8804 },
8805 ),
8806 )
8807 .into_any(),
8808 );
8809 }
8810
8811 self.render_edit_prediction_cursor_popover_preview(
8812 prediction,
8813 cursor_point,
8814 style,
8815 cx,
8816 )?
8817 }
8818
8819 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8820 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8821 stale_completion,
8822 cursor_point,
8823 style,
8824 cx,
8825 )?,
8826
8827 None => {
8828 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8829 }
8830 },
8831
8832 None => pending_completion_container().child(Label::new("No Prediction")),
8833 };
8834
8835 let completion = if is_refreshing {
8836 completion
8837 .with_animation(
8838 "loading-completion",
8839 Animation::new(Duration::from_secs(2))
8840 .repeat()
8841 .with_easing(pulsating_between(0.4, 0.8)),
8842 |label, delta| label.opacity(delta),
8843 )
8844 .into_any_element()
8845 } else {
8846 completion.into_any_element()
8847 };
8848
8849 let has_completion = self.active_inline_completion.is_some();
8850
8851 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8852 Some(
8853 h_flex()
8854 .min_w(min_width)
8855 .max_w(max_width)
8856 .flex_1()
8857 .elevation_2(cx)
8858 .border_color(cx.theme().colors().border)
8859 .child(
8860 div()
8861 .flex_1()
8862 .py_1()
8863 .px_2()
8864 .overflow_hidden()
8865 .child(completion),
8866 )
8867 .when_some(accept_keystroke, |el, accept_keystroke| {
8868 if !accept_keystroke.modifiers.modified() {
8869 return el;
8870 }
8871
8872 el.child(
8873 h_flex()
8874 .h_full()
8875 .border_l_1()
8876 .rounded_r_lg()
8877 .border_color(cx.theme().colors().border)
8878 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8879 .gap_1()
8880 .py_1()
8881 .px_2()
8882 .child(
8883 h_flex()
8884 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8885 .when(is_platform_style_mac, |parent| parent.gap_1())
8886 .child(h_flex().children(ui::render_modifiers(
8887 &accept_keystroke.modifiers,
8888 PlatformStyle::platform(),
8889 Some(if !has_completion {
8890 Color::Muted
8891 } else {
8892 Color::Default
8893 }),
8894 None,
8895 false,
8896 ))),
8897 )
8898 .child(Label::new("Preview").into_any_element())
8899 .opacity(if has_completion { 1.0 } else { 0.4 }),
8900 )
8901 })
8902 .into_any(),
8903 )
8904 }
8905
8906 fn render_edit_prediction_cursor_popover_preview(
8907 &self,
8908 completion: &InlineCompletionState,
8909 cursor_point: Point,
8910 style: &EditorStyle,
8911 cx: &mut Context<Editor>,
8912 ) -> Option<Div> {
8913 use text::ToPoint as _;
8914
8915 fn render_relative_row_jump(
8916 prefix: impl Into<String>,
8917 current_row: u32,
8918 target_row: u32,
8919 ) -> Div {
8920 let (row_diff, arrow) = if target_row < current_row {
8921 (current_row - target_row, IconName::ArrowUp)
8922 } else {
8923 (target_row - current_row, IconName::ArrowDown)
8924 };
8925
8926 h_flex()
8927 .child(
8928 Label::new(format!("{}{}", prefix.into(), row_diff))
8929 .color(Color::Muted)
8930 .size(LabelSize::Small),
8931 )
8932 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8933 }
8934
8935 match &completion.completion {
8936 InlineCompletion::Move {
8937 target, snapshot, ..
8938 } => Some(
8939 h_flex()
8940 .px_2()
8941 .gap_2()
8942 .flex_1()
8943 .child(
8944 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8945 Icon::new(IconName::ZedPredictDown)
8946 } else {
8947 Icon::new(IconName::ZedPredictUp)
8948 },
8949 )
8950 .child(Label::new("Jump to Edit")),
8951 ),
8952
8953 InlineCompletion::Edit {
8954 edits,
8955 edit_preview,
8956 snapshot,
8957 display_mode: _,
8958 } => {
8959 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8960
8961 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8962 &snapshot,
8963 &edits,
8964 edit_preview.as_ref()?,
8965 true,
8966 cx,
8967 )
8968 .first_line_preview();
8969
8970 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8971 .with_default_highlights(&style.text, highlighted_edits.highlights);
8972
8973 let preview = h_flex()
8974 .gap_1()
8975 .min_w_16()
8976 .child(styled_text)
8977 .when(has_more_lines, |parent| parent.child("…"));
8978
8979 let left = if first_edit_row != cursor_point.row {
8980 render_relative_row_jump("", cursor_point.row, first_edit_row)
8981 .into_any_element()
8982 } else {
8983 Icon::new(IconName::ZedPredict).into_any_element()
8984 };
8985
8986 Some(
8987 h_flex()
8988 .h_full()
8989 .flex_1()
8990 .gap_2()
8991 .pr_1()
8992 .overflow_x_hidden()
8993 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8994 .child(left)
8995 .child(preview),
8996 )
8997 }
8998 }
8999 }
9000
9001 pub fn render_context_menu(
9002 &self,
9003 style: &EditorStyle,
9004 max_height_in_lines: u32,
9005 window: &mut Window,
9006 cx: &mut Context<Editor>,
9007 ) -> Option<AnyElement> {
9008 let menu = self.context_menu.borrow();
9009 let menu = menu.as_ref()?;
9010 if !menu.visible() {
9011 return None;
9012 };
9013 Some(menu.render(style, max_height_in_lines, window, cx))
9014 }
9015
9016 fn render_context_menu_aside(
9017 &mut self,
9018 max_size: Size<Pixels>,
9019 window: &mut Window,
9020 cx: &mut Context<Editor>,
9021 ) -> Option<AnyElement> {
9022 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9023 if menu.visible() {
9024 menu.render_aside(max_size, window, cx)
9025 } else {
9026 None
9027 }
9028 })
9029 }
9030
9031 fn hide_context_menu(
9032 &mut self,
9033 window: &mut Window,
9034 cx: &mut Context<Self>,
9035 ) -> Option<CodeContextMenu> {
9036 cx.notify();
9037 self.completion_tasks.clear();
9038 let context_menu = self.context_menu.borrow_mut().take();
9039 self.stale_inline_completion_in_menu.take();
9040 self.update_visible_inline_completion(window, cx);
9041 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9042 if let Some(completion_provider) = &self.completion_provider {
9043 completion_provider.selection_changed(None, window, cx);
9044 }
9045 }
9046 context_menu
9047 }
9048
9049 fn show_snippet_choices(
9050 &mut self,
9051 choices: &Vec<String>,
9052 selection: Range<Anchor>,
9053 cx: &mut Context<Self>,
9054 ) {
9055 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9056 (Some(a), Some(b)) if a == b => a,
9057 _ => {
9058 log::error!("expected anchor range to have matching buffer IDs");
9059 return;
9060 }
9061 };
9062 let multi_buffer = self.buffer().read(cx);
9063 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9064 return;
9065 };
9066
9067 let id = post_inc(&mut self.next_completion_id);
9068 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9069 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9070 CompletionsMenu::new_snippet_choices(
9071 id,
9072 true,
9073 choices,
9074 selection,
9075 buffer,
9076 snippet_sort_order,
9077 ),
9078 ));
9079 }
9080
9081 pub fn insert_snippet(
9082 &mut self,
9083 insertion_ranges: &[Range<usize>],
9084 snippet: Snippet,
9085 window: &mut Window,
9086 cx: &mut Context<Self>,
9087 ) -> Result<()> {
9088 struct Tabstop<T> {
9089 is_end_tabstop: bool,
9090 ranges: Vec<Range<T>>,
9091 choices: Option<Vec<String>>,
9092 }
9093
9094 let tabstops = self.buffer.update(cx, |buffer, cx| {
9095 let snippet_text: Arc<str> = snippet.text.clone().into();
9096 let edits = insertion_ranges
9097 .iter()
9098 .cloned()
9099 .map(|range| (range, snippet_text.clone()));
9100 let autoindent_mode = AutoindentMode::Block {
9101 original_indent_columns: Vec::new(),
9102 };
9103 buffer.edit(edits, Some(autoindent_mode), cx);
9104
9105 let snapshot = &*buffer.read(cx);
9106 let snippet = &snippet;
9107 snippet
9108 .tabstops
9109 .iter()
9110 .map(|tabstop| {
9111 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9112 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9113 });
9114 let mut tabstop_ranges = tabstop
9115 .ranges
9116 .iter()
9117 .flat_map(|tabstop_range| {
9118 let mut delta = 0_isize;
9119 insertion_ranges.iter().map(move |insertion_range| {
9120 let insertion_start = insertion_range.start as isize + delta;
9121 delta +=
9122 snippet.text.len() as isize - insertion_range.len() as isize;
9123
9124 let start = ((insertion_start + tabstop_range.start) as usize)
9125 .min(snapshot.len());
9126 let end = ((insertion_start + tabstop_range.end) as usize)
9127 .min(snapshot.len());
9128 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9129 })
9130 })
9131 .collect::<Vec<_>>();
9132 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9133
9134 Tabstop {
9135 is_end_tabstop,
9136 ranges: tabstop_ranges,
9137 choices: tabstop.choices.clone(),
9138 }
9139 })
9140 .collect::<Vec<_>>()
9141 });
9142 if let Some(tabstop) = tabstops.first() {
9143 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9144 // Reverse order so that the first range is the newest created selection.
9145 // Completions will use it and autoscroll will prioritize it.
9146 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9147 });
9148
9149 if let Some(choices) = &tabstop.choices {
9150 if let Some(selection) = tabstop.ranges.first() {
9151 self.show_snippet_choices(choices, selection.clone(), cx)
9152 }
9153 }
9154
9155 // If we're already at the last tabstop and it's at the end of the snippet,
9156 // we're done, we don't need to keep the state around.
9157 if !tabstop.is_end_tabstop {
9158 let choices = tabstops
9159 .iter()
9160 .map(|tabstop| tabstop.choices.clone())
9161 .collect();
9162
9163 let ranges = tabstops
9164 .into_iter()
9165 .map(|tabstop| tabstop.ranges)
9166 .collect::<Vec<_>>();
9167
9168 self.snippet_stack.push(SnippetState {
9169 active_index: 0,
9170 ranges,
9171 choices,
9172 });
9173 }
9174
9175 // Check whether the just-entered snippet ends with an auto-closable bracket.
9176 if self.autoclose_regions.is_empty() {
9177 let snapshot = self.buffer.read(cx).snapshot(cx);
9178 for selection in &mut self.selections.all::<Point>(cx) {
9179 let selection_head = selection.head();
9180 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9181 continue;
9182 };
9183
9184 let mut bracket_pair = None;
9185 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9186 let prev_chars = snapshot
9187 .reversed_chars_at(selection_head)
9188 .collect::<String>();
9189 for (pair, enabled) in scope.brackets() {
9190 if enabled
9191 && pair.close
9192 && prev_chars.starts_with(pair.start.as_str())
9193 && next_chars.starts_with(pair.end.as_str())
9194 {
9195 bracket_pair = Some(pair.clone());
9196 break;
9197 }
9198 }
9199 if let Some(pair) = bracket_pair {
9200 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9201 let autoclose_enabled =
9202 self.use_autoclose && snapshot_settings.use_autoclose;
9203 if autoclose_enabled {
9204 let start = snapshot.anchor_after(selection_head);
9205 let end = snapshot.anchor_after(selection_head);
9206 self.autoclose_regions.push(AutocloseRegion {
9207 selection_id: selection.id,
9208 range: start..end,
9209 pair,
9210 });
9211 }
9212 }
9213 }
9214 }
9215 }
9216 Ok(())
9217 }
9218
9219 pub fn move_to_next_snippet_tabstop(
9220 &mut self,
9221 window: &mut Window,
9222 cx: &mut Context<Self>,
9223 ) -> bool {
9224 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9225 }
9226
9227 pub fn move_to_prev_snippet_tabstop(
9228 &mut self,
9229 window: &mut Window,
9230 cx: &mut Context<Self>,
9231 ) -> bool {
9232 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9233 }
9234
9235 pub fn move_to_snippet_tabstop(
9236 &mut self,
9237 bias: Bias,
9238 window: &mut Window,
9239 cx: &mut Context<Self>,
9240 ) -> bool {
9241 if let Some(mut snippet) = self.snippet_stack.pop() {
9242 match bias {
9243 Bias::Left => {
9244 if snippet.active_index > 0 {
9245 snippet.active_index -= 1;
9246 } else {
9247 self.snippet_stack.push(snippet);
9248 return false;
9249 }
9250 }
9251 Bias::Right => {
9252 if snippet.active_index + 1 < snippet.ranges.len() {
9253 snippet.active_index += 1;
9254 } else {
9255 self.snippet_stack.push(snippet);
9256 return false;
9257 }
9258 }
9259 }
9260 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9261 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9262 // Reverse order so that the first range is the newest created selection.
9263 // Completions will use it and autoscroll will prioritize it.
9264 s.select_ranges(current_ranges.iter().rev().cloned())
9265 });
9266
9267 if let Some(choices) = &snippet.choices[snippet.active_index] {
9268 if let Some(selection) = current_ranges.first() {
9269 self.show_snippet_choices(&choices, selection.clone(), cx);
9270 }
9271 }
9272
9273 // If snippet state is not at the last tabstop, push it back on the stack
9274 if snippet.active_index + 1 < snippet.ranges.len() {
9275 self.snippet_stack.push(snippet);
9276 }
9277 return true;
9278 }
9279 }
9280
9281 false
9282 }
9283
9284 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9285 self.transact(window, cx, |this, window, cx| {
9286 this.select_all(&SelectAll, window, cx);
9287 this.insert("", window, cx);
9288 });
9289 }
9290
9291 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9292 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9293 self.transact(window, cx, |this, window, cx| {
9294 this.select_autoclose_pair(window, cx);
9295 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9296 if !this.linked_edit_ranges.is_empty() {
9297 let selections = this.selections.all::<MultiBufferPoint>(cx);
9298 let snapshot = this.buffer.read(cx).snapshot(cx);
9299
9300 for selection in selections.iter() {
9301 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9302 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9303 if selection_start.buffer_id != selection_end.buffer_id {
9304 continue;
9305 }
9306 if let Some(ranges) =
9307 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9308 {
9309 for (buffer, entries) in ranges {
9310 linked_ranges.entry(buffer).or_default().extend(entries);
9311 }
9312 }
9313 }
9314 }
9315
9316 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9317 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9318 for selection in &mut selections {
9319 if selection.is_empty() {
9320 let old_head = selection.head();
9321 let mut new_head =
9322 movement::left(&display_map, old_head.to_display_point(&display_map))
9323 .to_point(&display_map);
9324 if let Some((buffer, line_buffer_range)) = display_map
9325 .buffer_snapshot
9326 .buffer_line_for_row(MultiBufferRow(old_head.row))
9327 {
9328 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9329 let indent_len = match indent_size.kind {
9330 IndentKind::Space => {
9331 buffer.settings_at(line_buffer_range.start, cx).tab_size
9332 }
9333 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9334 };
9335 if old_head.column <= indent_size.len && old_head.column > 0 {
9336 let indent_len = indent_len.get();
9337 new_head = cmp::min(
9338 new_head,
9339 MultiBufferPoint::new(
9340 old_head.row,
9341 ((old_head.column - 1) / indent_len) * indent_len,
9342 ),
9343 );
9344 }
9345 }
9346
9347 selection.set_head(new_head, SelectionGoal::None);
9348 }
9349 }
9350
9351 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9352 s.select(selections)
9353 });
9354 this.insert("", window, cx);
9355 let empty_str: Arc<str> = Arc::from("");
9356 for (buffer, edits) in linked_ranges {
9357 let snapshot = buffer.read(cx).snapshot();
9358 use text::ToPoint as TP;
9359
9360 let edits = edits
9361 .into_iter()
9362 .map(|range| {
9363 let end_point = TP::to_point(&range.end, &snapshot);
9364 let mut start_point = TP::to_point(&range.start, &snapshot);
9365
9366 if end_point == start_point {
9367 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9368 .saturating_sub(1);
9369 start_point =
9370 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9371 };
9372
9373 (start_point..end_point, empty_str.clone())
9374 })
9375 .sorted_by_key(|(range, _)| range.start)
9376 .collect::<Vec<_>>();
9377 buffer.update(cx, |this, cx| {
9378 this.edit(edits, None, cx);
9379 })
9380 }
9381 this.refresh_inline_completion(true, false, window, cx);
9382 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9383 });
9384 }
9385
9386 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9387 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9388 self.transact(window, cx, |this, window, cx| {
9389 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9390 s.move_with(|map, selection| {
9391 if selection.is_empty() {
9392 let cursor = movement::right(map, selection.head());
9393 selection.end = cursor;
9394 selection.reversed = true;
9395 selection.goal = SelectionGoal::None;
9396 }
9397 })
9398 });
9399 this.insert("", window, cx);
9400 this.refresh_inline_completion(true, false, window, cx);
9401 });
9402 }
9403
9404 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9405 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9406 if self.move_to_prev_snippet_tabstop(window, cx) {
9407 return;
9408 }
9409 self.outdent(&Outdent, window, cx);
9410 }
9411
9412 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9413 if self.move_to_next_snippet_tabstop(window, cx) {
9414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9415 return;
9416 }
9417 if self.read_only(cx) {
9418 return;
9419 }
9420 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9421 let mut selections = self.selections.all_adjusted(cx);
9422 let buffer = self.buffer.read(cx);
9423 let snapshot = buffer.snapshot(cx);
9424 let rows_iter = selections.iter().map(|s| s.head().row);
9425 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9426
9427 let has_some_cursor_in_whitespace = selections
9428 .iter()
9429 .filter(|selection| selection.is_empty())
9430 .any(|selection| {
9431 let cursor = selection.head();
9432 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9433 cursor.column < current_indent.len
9434 });
9435
9436 let mut edits = Vec::new();
9437 let mut prev_edited_row = 0;
9438 let mut row_delta = 0;
9439 for selection in &mut selections {
9440 if selection.start.row != prev_edited_row {
9441 row_delta = 0;
9442 }
9443 prev_edited_row = selection.end.row;
9444
9445 // If the selection is non-empty, then increase the indentation of the selected lines.
9446 if !selection.is_empty() {
9447 row_delta =
9448 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9449 continue;
9450 }
9451
9452 let cursor = selection.head();
9453 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9454 if let Some(suggested_indent) =
9455 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9456 {
9457 // Don't do anything if already at suggested indent
9458 // and there is any other cursor which is not
9459 if has_some_cursor_in_whitespace
9460 && cursor.column == current_indent.len
9461 && current_indent.len == suggested_indent.len
9462 {
9463 continue;
9464 }
9465
9466 // Adjust line and move cursor to suggested indent
9467 // if cursor is not at suggested indent
9468 if cursor.column < suggested_indent.len
9469 && cursor.column <= current_indent.len
9470 && current_indent.len <= suggested_indent.len
9471 {
9472 selection.start = Point::new(cursor.row, suggested_indent.len);
9473 selection.end = selection.start;
9474 if row_delta == 0 {
9475 edits.extend(Buffer::edit_for_indent_size_adjustment(
9476 cursor.row,
9477 current_indent,
9478 suggested_indent,
9479 ));
9480 row_delta = suggested_indent.len - current_indent.len;
9481 }
9482 continue;
9483 }
9484
9485 // If current indent is more than suggested indent
9486 // only move cursor to current indent and skip indent
9487 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9488 selection.start = Point::new(cursor.row, current_indent.len);
9489 selection.end = selection.start;
9490 continue;
9491 }
9492 }
9493
9494 // Otherwise, insert a hard or soft tab.
9495 let settings = buffer.language_settings_at(cursor, cx);
9496 let tab_size = if settings.hard_tabs {
9497 IndentSize::tab()
9498 } else {
9499 let tab_size = settings.tab_size.get();
9500 let indent_remainder = snapshot
9501 .text_for_range(Point::new(cursor.row, 0)..cursor)
9502 .flat_map(str::chars)
9503 .fold(row_delta % tab_size, |counter: u32, c| {
9504 if c == '\t' {
9505 0
9506 } else {
9507 (counter + 1) % tab_size
9508 }
9509 });
9510
9511 let chars_to_next_tab_stop = tab_size - indent_remainder;
9512 IndentSize::spaces(chars_to_next_tab_stop)
9513 };
9514 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9515 selection.end = selection.start;
9516 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9517 row_delta += tab_size.len;
9518 }
9519
9520 self.transact(window, cx, |this, window, cx| {
9521 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9522 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9523 s.select(selections)
9524 });
9525 this.refresh_inline_completion(true, false, window, cx);
9526 });
9527 }
9528
9529 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9530 if self.read_only(cx) {
9531 return;
9532 }
9533 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9534 let mut selections = self.selections.all::<Point>(cx);
9535 let mut prev_edited_row = 0;
9536 let mut row_delta = 0;
9537 let mut edits = Vec::new();
9538 let buffer = self.buffer.read(cx);
9539 let snapshot = buffer.snapshot(cx);
9540 for selection in &mut selections {
9541 if selection.start.row != prev_edited_row {
9542 row_delta = 0;
9543 }
9544 prev_edited_row = selection.end.row;
9545
9546 row_delta =
9547 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9548 }
9549
9550 self.transact(window, cx, |this, window, cx| {
9551 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9552 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9553 s.select(selections)
9554 });
9555 });
9556 }
9557
9558 fn indent_selection(
9559 buffer: &MultiBuffer,
9560 snapshot: &MultiBufferSnapshot,
9561 selection: &mut Selection<Point>,
9562 edits: &mut Vec<(Range<Point>, String)>,
9563 delta_for_start_row: u32,
9564 cx: &App,
9565 ) -> u32 {
9566 let settings = buffer.language_settings_at(selection.start, cx);
9567 let tab_size = settings.tab_size.get();
9568 let indent_kind = if settings.hard_tabs {
9569 IndentKind::Tab
9570 } else {
9571 IndentKind::Space
9572 };
9573 let mut start_row = selection.start.row;
9574 let mut end_row = selection.end.row + 1;
9575
9576 // If a selection ends at the beginning of a line, don't indent
9577 // that last line.
9578 if selection.end.column == 0 && selection.end.row > selection.start.row {
9579 end_row -= 1;
9580 }
9581
9582 // Avoid re-indenting a row that has already been indented by a
9583 // previous selection, but still update this selection's column
9584 // to reflect that indentation.
9585 if delta_for_start_row > 0 {
9586 start_row += 1;
9587 selection.start.column += delta_for_start_row;
9588 if selection.end.row == selection.start.row {
9589 selection.end.column += delta_for_start_row;
9590 }
9591 }
9592
9593 let mut delta_for_end_row = 0;
9594 let has_multiple_rows = start_row + 1 != end_row;
9595 for row in start_row..end_row {
9596 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9597 let indent_delta = match (current_indent.kind, indent_kind) {
9598 (IndentKind::Space, IndentKind::Space) => {
9599 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9600 IndentSize::spaces(columns_to_next_tab_stop)
9601 }
9602 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9603 (_, IndentKind::Tab) => IndentSize::tab(),
9604 };
9605
9606 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9607 0
9608 } else {
9609 selection.start.column
9610 };
9611 let row_start = Point::new(row, start);
9612 edits.push((
9613 row_start..row_start,
9614 indent_delta.chars().collect::<String>(),
9615 ));
9616
9617 // Update this selection's endpoints to reflect the indentation.
9618 if row == selection.start.row {
9619 selection.start.column += indent_delta.len;
9620 }
9621 if row == selection.end.row {
9622 selection.end.column += indent_delta.len;
9623 delta_for_end_row = indent_delta.len;
9624 }
9625 }
9626
9627 if selection.start.row == selection.end.row {
9628 delta_for_start_row + delta_for_end_row
9629 } else {
9630 delta_for_end_row
9631 }
9632 }
9633
9634 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9635 if self.read_only(cx) {
9636 return;
9637 }
9638 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9640 let selections = self.selections.all::<Point>(cx);
9641 let mut deletion_ranges = Vec::new();
9642 let mut last_outdent = None;
9643 {
9644 let buffer = self.buffer.read(cx);
9645 let snapshot = buffer.snapshot(cx);
9646 for selection in &selections {
9647 let settings = buffer.language_settings_at(selection.start, cx);
9648 let tab_size = settings.tab_size.get();
9649 let mut rows = selection.spanned_rows(false, &display_map);
9650
9651 // Avoid re-outdenting a row that has already been outdented by a
9652 // previous selection.
9653 if let Some(last_row) = last_outdent {
9654 if last_row == rows.start {
9655 rows.start = rows.start.next_row();
9656 }
9657 }
9658 let has_multiple_rows = rows.len() > 1;
9659 for row in rows.iter_rows() {
9660 let indent_size = snapshot.indent_size_for_line(row);
9661 if indent_size.len > 0 {
9662 let deletion_len = match indent_size.kind {
9663 IndentKind::Space => {
9664 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9665 if columns_to_prev_tab_stop == 0 {
9666 tab_size
9667 } else {
9668 columns_to_prev_tab_stop
9669 }
9670 }
9671 IndentKind::Tab => 1,
9672 };
9673 let start = if has_multiple_rows
9674 || deletion_len > selection.start.column
9675 || indent_size.len < selection.start.column
9676 {
9677 0
9678 } else {
9679 selection.start.column - deletion_len
9680 };
9681 deletion_ranges.push(
9682 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9683 );
9684 last_outdent = Some(row);
9685 }
9686 }
9687 }
9688 }
9689
9690 self.transact(window, cx, |this, window, cx| {
9691 this.buffer.update(cx, |buffer, cx| {
9692 let empty_str: Arc<str> = Arc::default();
9693 buffer.edit(
9694 deletion_ranges
9695 .into_iter()
9696 .map(|range| (range, empty_str.clone())),
9697 None,
9698 cx,
9699 );
9700 });
9701 let selections = this.selections.all::<usize>(cx);
9702 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9703 s.select(selections)
9704 });
9705 });
9706 }
9707
9708 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9709 if self.read_only(cx) {
9710 return;
9711 }
9712 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9713 let selections = self
9714 .selections
9715 .all::<usize>(cx)
9716 .into_iter()
9717 .map(|s| s.range());
9718
9719 self.transact(window, cx, |this, window, cx| {
9720 this.buffer.update(cx, |buffer, cx| {
9721 buffer.autoindent_ranges(selections, cx);
9722 });
9723 let selections = this.selections.all::<usize>(cx);
9724 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9725 s.select(selections)
9726 });
9727 });
9728 }
9729
9730 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9733 let selections = self.selections.all::<Point>(cx);
9734
9735 let mut new_cursors = Vec::new();
9736 let mut edit_ranges = Vec::new();
9737 let mut selections = selections.iter().peekable();
9738 while let Some(selection) = selections.next() {
9739 let mut rows = selection.spanned_rows(false, &display_map);
9740 let goal_display_column = selection.head().to_display_point(&display_map).column();
9741
9742 // Accumulate contiguous regions of rows that we want to delete.
9743 while let Some(next_selection) = selections.peek() {
9744 let next_rows = next_selection.spanned_rows(false, &display_map);
9745 if next_rows.start <= rows.end {
9746 rows.end = next_rows.end;
9747 selections.next().unwrap();
9748 } else {
9749 break;
9750 }
9751 }
9752
9753 let buffer = &display_map.buffer_snapshot;
9754 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9755 let edit_end;
9756 let cursor_buffer_row;
9757 if buffer.max_point().row >= rows.end.0 {
9758 // If there's a line after the range, delete the \n from the end of the row range
9759 // and position the cursor on the next line.
9760 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9761 cursor_buffer_row = rows.end;
9762 } else {
9763 // If there isn't a line after the range, delete the \n from the line before the
9764 // start of the row range and position the cursor there.
9765 edit_start = edit_start.saturating_sub(1);
9766 edit_end = buffer.len();
9767 cursor_buffer_row = rows.start.previous_row();
9768 }
9769
9770 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9771 *cursor.column_mut() =
9772 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9773
9774 new_cursors.push((
9775 selection.id,
9776 buffer.anchor_after(cursor.to_point(&display_map)),
9777 ));
9778 edit_ranges.push(edit_start..edit_end);
9779 }
9780
9781 self.transact(window, cx, |this, window, cx| {
9782 let buffer = this.buffer.update(cx, |buffer, cx| {
9783 let empty_str: Arc<str> = Arc::default();
9784 buffer.edit(
9785 edit_ranges
9786 .into_iter()
9787 .map(|range| (range, empty_str.clone())),
9788 None,
9789 cx,
9790 );
9791 buffer.snapshot(cx)
9792 });
9793 let new_selections = new_cursors
9794 .into_iter()
9795 .map(|(id, cursor)| {
9796 let cursor = cursor.to_point(&buffer);
9797 Selection {
9798 id,
9799 start: cursor,
9800 end: cursor,
9801 reversed: false,
9802 goal: SelectionGoal::None,
9803 }
9804 })
9805 .collect();
9806
9807 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9808 s.select(new_selections);
9809 });
9810 });
9811 }
9812
9813 pub fn join_lines_impl(
9814 &mut self,
9815 insert_whitespace: bool,
9816 window: &mut Window,
9817 cx: &mut Context<Self>,
9818 ) {
9819 if self.read_only(cx) {
9820 return;
9821 }
9822 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9823 for selection in self.selections.all::<Point>(cx) {
9824 let start = MultiBufferRow(selection.start.row);
9825 // Treat single line selections as if they include the next line. Otherwise this action
9826 // would do nothing for single line selections individual cursors.
9827 let end = if selection.start.row == selection.end.row {
9828 MultiBufferRow(selection.start.row + 1)
9829 } else {
9830 MultiBufferRow(selection.end.row)
9831 };
9832
9833 if let Some(last_row_range) = row_ranges.last_mut() {
9834 if start <= last_row_range.end {
9835 last_row_range.end = end;
9836 continue;
9837 }
9838 }
9839 row_ranges.push(start..end);
9840 }
9841
9842 let snapshot = self.buffer.read(cx).snapshot(cx);
9843 let mut cursor_positions = Vec::new();
9844 for row_range in &row_ranges {
9845 let anchor = snapshot.anchor_before(Point::new(
9846 row_range.end.previous_row().0,
9847 snapshot.line_len(row_range.end.previous_row()),
9848 ));
9849 cursor_positions.push(anchor..anchor);
9850 }
9851
9852 self.transact(window, cx, |this, window, cx| {
9853 for row_range in row_ranges.into_iter().rev() {
9854 for row in row_range.iter_rows().rev() {
9855 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9856 let next_line_row = row.next_row();
9857 let indent = snapshot.indent_size_for_line(next_line_row);
9858 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9859
9860 let replace =
9861 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9862 " "
9863 } else {
9864 ""
9865 };
9866
9867 this.buffer.update(cx, |buffer, cx| {
9868 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9869 });
9870 }
9871 }
9872
9873 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9874 s.select_anchor_ranges(cursor_positions)
9875 });
9876 });
9877 }
9878
9879 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9880 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9881 self.join_lines_impl(true, window, cx);
9882 }
9883
9884 pub fn sort_lines_case_sensitive(
9885 &mut self,
9886 _: &SortLinesCaseSensitive,
9887 window: &mut Window,
9888 cx: &mut Context<Self>,
9889 ) {
9890 self.manipulate_lines(window, cx, |lines| lines.sort())
9891 }
9892
9893 pub fn sort_lines_case_insensitive(
9894 &mut self,
9895 _: &SortLinesCaseInsensitive,
9896 window: &mut Window,
9897 cx: &mut Context<Self>,
9898 ) {
9899 self.manipulate_lines(window, cx, |lines| {
9900 lines.sort_by_key(|line| line.to_lowercase())
9901 })
9902 }
9903
9904 pub fn unique_lines_case_insensitive(
9905 &mut self,
9906 _: &UniqueLinesCaseInsensitive,
9907 window: &mut Window,
9908 cx: &mut Context<Self>,
9909 ) {
9910 self.manipulate_lines(window, cx, |lines| {
9911 let mut seen = HashSet::default();
9912 lines.retain(|line| seen.insert(line.to_lowercase()));
9913 })
9914 }
9915
9916 pub fn unique_lines_case_sensitive(
9917 &mut self,
9918 _: &UniqueLinesCaseSensitive,
9919 window: &mut Window,
9920 cx: &mut Context<Self>,
9921 ) {
9922 self.manipulate_lines(window, cx, |lines| {
9923 let mut seen = HashSet::default();
9924 lines.retain(|line| seen.insert(*line));
9925 })
9926 }
9927
9928 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9929 let Some(project) = self.project.clone() else {
9930 return;
9931 };
9932 self.reload(project, window, cx)
9933 .detach_and_notify_err(window, cx);
9934 }
9935
9936 pub fn restore_file(
9937 &mut self,
9938 _: &::git::RestoreFile,
9939 window: &mut Window,
9940 cx: &mut Context<Self>,
9941 ) {
9942 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9943 let mut buffer_ids = HashSet::default();
9944 let snapshot = self.buffer().read(cx).snapshot(cx);
9945 for selection in self.selections.all::<usize>(cx) {
9946 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9947 }
9948
9949 let buffer = self.buffer().read(cx);
9950 let ranges = buffer_ids
9951 .into_iter()
9952 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9953 .collect::<Vec<_>>();
9954
9955 self.restore_hunks_in_ranges(ranges, window, cx);
9956 }
9957
9958 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9959 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9960 let selections = self
9961 .selections
9962 .all(cx)
9963 .into_iter()
9964 .map(|s| s.range())
9965 .collect();
9966 self.restore_hunks_in_ranges(selections, window, cx);
9967 }
9968
9969 pub fn restore_hunks_in_ranges(
9970 &mut self,
9971 ranges: Vec<Range<Point>>,
9972 window: &mut Window,
9973 cx: &mut Context<Editor>,
9974 ) {
9975 let mut revert_changes = HashMap::default();
9976 let chunk_by = self
9977 .snapshot(window, cx)
9978 .hunks_for_ranges(ranges)
9979 .into_iter()
9980 .chunk_by(|hunk| hunk.buffer_id);
9981 for (buffer_id, hunks) in &chunk_by {
9982 let hunks = hunks.collect::<Vec<_>>();
9983 for hunk in &hunks {
9984 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9985 }
9986 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9987 }
9988 drop(chunk_by);
9989 if !revert_changes.is_empty() {
9990 self.transact(window, cx, |editor, window, cx| {
9991 editor.restore(revert_changes, window, cx);
9992 });
9993 }
9994 }
9995
9996 pub fn open_active_item_in_terminal(
9997 &mut self,
9998 _: &OpenInTerminal,
9999 window: &mut Window,
10000 cx: &mut Context<Self>,
10001 ) {
10002 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10003 let project_path = buffer.read(cx).project_path(cx)?;
10004 let project = self.project.as_ref()?.read(cx);
10005 let entry = project.entry_for_path(&project_path, cx)?;
10006 let parent = match &entry.canonical_path {
10007 Some(canonical_path) => canonical_path.to_path_buf(),
10008 None => project.absolute_path(&project_path, cx)?,
10009 }
10010 .parent()?
10011 .to_path_buf();
10012 Some(parent)
10013 }) {
10014 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10015 }
10016 }
10017
10018 fn set_breakpoint_context_menu(
10019 &mut self,
10020 display_row: DisplayRow,
10021 position: Option<Anchor>,
10022 clicked_point: gpui::Point<Pixels>,
10023 window: &mut Window,
10024 cx: &mut Context<Self>,
10025 ) {
10026 if !cx.has_flag::<DebuggerFeatureFlag>() {
10027 return;
10028 }
10029 let source = self
10030 .buffer
10031 .read(cx)
10032 .snapshot(cx)
10033 .anchor_before(Point::new(display_row.0, 0u32));
10034
10035 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10036
10037 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10038 self,
10039 source,
10040 clicked_point,
10041 context_menu,
10042 window,
10043 cx,
10044 );
10045 }
10046
10047 fn add_edit_breakpoint_block(
10048 &mut self,
10049 anchor: Anchor,
10050 breakpoint: &Breakpoint,
10051 edit_action: BreakpointPromptEditAction,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) {
10055 let weak_editor = cx.weak_entity();
10056 let bp_prompt = cx.new(|cx| {
10057 BreakpointPromptEditor::new(
10058 weak_editor,
10059 anchor,
10060 breakpoint.clone(),
10061 edit_action,
10062 window,
10063 cx,
10064 )
10065 });
10066
10067 let height = bp_prompt.update(cx, |this, cx| {
10068 this.prompt
10069 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10070 });
10071 let cloned_prompt = bp_prompt.clone();
10072 let blocks = vec![BlockProperties {
10073 style: BlockStyle::Sticky,
10074 placement: BlockPlacement::Above(anchor),
10075 height: Some(height),
10076 render: Arc::new(move |cx| {
10077 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10078 cloned_prompt.clone().into_any_element()
10079 }),
10080 priority: 0,
10081 render_in_minimap: true,
10082 }];
10083
10084 let focus_handle = bp_prompt.focus_handle(cx);
10085 window.focus(&focus_handle);
10086
10087 let block_ids = self.insert_blocks(blocks, None, cx);
10088 bp_prompt.update(cx, |prompt, _| {
10089 prompt.add_block_ids(block_ids);
10090 });
10091 }
10092
10093 pub(crate) fn breakpoint_at_row(
10094 &self,
10095 row: u32,
10096 window: &mut Window,
10097 cx: &mut Context<Self>,
10098 ) -> Option<(Anchor, Breakpoint)> {
10099 let snapshot = self.snapshot(window, cx);
10100 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10101
10102 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10103 }
10104
10105 pub(crate) fn breakpoint_at_anchor(
10106 &self,
10107 breakpoint_position: Anchor,
10108 snapshot: &EditorSnapshot,
10109 cx: &mut Context<Self>,
10110 ) -> Option<(Anchor, Breakpoint)> {
10111 let project = self.project.clone()?;
10112
10113 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10114 snapshot
10115 .buffer_snapshot
10116 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10117 })?;
10118
10119 let enclosing_excerpt = breakpoint_position.excerpt_id;
10120 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10121 let buffer_snapshot = buffer.read(cx).snapshot();
10122
10123 let row = buffer_snapshot
10124 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10125 .row;
10126
10127 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10128 let anchor_end = snapshot
10129 .buffer_snapshot
10130 .anchor_after(Point::new(row, line_len));
10131
10132 let bp = self
10133 .breakpoint_store
10134 .as_ref()?
10135 .read_with(cx, |breakpoint_store, cx| {
10136 breakpoint_store
10137 .breakpoints(
10138 &buffer,
10139 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10140 &buffer_snapshot,
10141 cx,
10142 )
10143 .next()
10144 .and_then(|(bp, _)| {
10145 let breakpoint_row = buffer_snapshot
10146 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10147 .row;
10148
10149 if breakpoint_row == row {
10150 snapshot
10151 .buffer_snapshot
10152 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10153 .map(|position| (position, bp.bp.clone()))
10154 } else {
10155 None
10156 }
10157 })
10158 });
10159 bp
10160 }
10161
10162 pub fn edit_log_breakpoint(
10163 &mut self,
10164 _: &EditLogBreakpoint,
10165 window: &mut Window,
10166 cx: &mut Context<Self>,
10167 ) {
10168 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10169 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10170 message: None,
10171 state: BreakpointState::Enabled,
10172 condition: None,
10173 hit_condition: None,
10174 });
10175
10176 self.add_edit_breakpoint_block(
10177 anchor,
10178 &breakpoint,
10179 BreakpointPromptEditAction::Log,
10180 window,
10181 cx,
10182 );
10183 }
10184 }
10185
10186 fn breakpoints_at_cursors(
10187 &self,
10188 window: &mut Window,
10189 cx: &mut Context<Self>,
10190 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10191 let snapshot = self.snapshot(window, cx);
10192 let cursors = self
10193 .selections
10194 .disjoint_anchors()
10195 .into_iter()
10196 .map(|selection| {
10197 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10198
10199 let breakpoint_position = self
10200 .breakpoint_at_row(cursor_position.row, window, cx)
10201 .map(|bp| bp.0)
10202 .unwrap_or_else(|| {
10203 snapshot
10204 .display_snapshot
10205 .buffer_snapshot
10206 .anchor_after(Point::new(cursor_position.row, 0))
10207 });
10208
10209 let breakpoint = self
10210 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10211 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10212
10213 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10214 })
10215 // 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.
10216 .collect::<HashMap<Anchor, _>>();
10217
10218 cursors.into_iter().collect()
10219 }
10220
10221 pub fn enable_breakpoint(
10222 &mut self,
10223 _: &crate::actions::EnableBreakpoint,
10224 window: &mut Window,
10225 cx: &mut Context<Self>,
10226 ) {
10227 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10228 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10229 continue;
10230 };
10231 self.edit_breakpoint_at_anchor(
10232 anchor,
10233 breakpoint,
10234 BreakpointEditAction::InvertState,
10235 cx,
10236 );
10237 }
10238 }
10239
10240 pub fn disable_breakpoint(
10241 &mut self,
10242 _: &crate::actions::DisableBreakpoint,
10243 window: &mut Window,
10244 cx: &mut Context<Self>,
10245 ) {
10246 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10247 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10248 continue;
10249 };
10250 self.edit_breakpoint_at_anchor(
10251 anchor,
10252 breakpoint,
10253 BreakpointEditAction::InvertState,
10254 cx,
10255 );
10256 }
10257 }
10258
10259 pub fn toggle_breakpoint(
10260 &mut self,
10261 _: &crate::actions::ToggleBreakpoint,
10262 window: &mut Window,
10263 cx: &mut Context<Self>,
10264 ) {
10265 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10266 if let Some(breakpoint) = breakpoint {
10267 self.edit_breakpoint_at_anchor(
10268 anchor,
10269 breakpoint,
10270 BreakpointEditAction::Toggle,
10271 cx,
10272 );
10273 } else {
10274 self.edit_breakpoint_at_anchor(
10275 anchor,
10276 Breakpoint::new_standard(),
10277 BreakpointEditAction::Toggle,
10278 cx,
10279 );
10280 }
10281 }
10282 }
10283
10284 pub fn edit_breakpoint_at_anchor(
10285 &mut self,
10286 breakpoint_position: Anchor,
10287 breakpoint: Breakpoint,
10288 edit_action: BreakpointEditAction,
10289 cx: &mut Context<Self>,
10290 ) {
10291 let Some(breakpoint_store) = &self.breakpoint_store else {
10292 return;
10293 };
10294
10295 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10296 if breakpoint_position == Anchor::min() {
10297 self.buffer()
10298 .read(cx)
10299 .excerpt_buffer_ids()
10300 .into_iter()
10301 .next()
10302 } else {
10303 None
10304 }
10305 }) else {
10306 return;
10307 };
10308
10309 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10310 return;
10311 };
10312
10313 breakpoint_store.update(cx, |breakpoint_store, cx| {
10314 breakpoint_store.toggle_breakpoint(
10315 buffer,
10316 BreakpointWithPosition {
10317 position: breakpoint_position.text_anchor,
10318 bp: breakpoint,
10319 },
10320 edit_action,
10321 cx,
10322 );
10323 });
10324
10325 cx.notify();
10326 }
10327
10328 #[cfg(any(test, feature = "test-support"))]
10329 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10330 self.breakpoint_store.clone()
10331 }
10332
10333 pub fn prepare_restore_change(
10334 &self,
10335 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10336 hunk: &MultiBufferDiffHunk,
10337 cx: &mut App,
10338 ) -> Option<()> {
10339 if hunk.is_created_file() {
10340 return None;
10341 }
10342 let buffer = self.buffer.read(cx);
10343 let diff = buffer.diff_for(hunk.buffer_id)?;
10344 let buffer = buffer.buffer(hunk.buffer_id)?;
10345 let buffer = buffer.read(cx);
10346 let original_text = diff
10347 .read(cx)
10348 .base_text()
10349 .as_rope()
10350 .slice(hunk.diff_base_byte_range.clone());
10351 let buffer_snapshot = buffer.snapshot();
10352 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10353 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10354 probe
10355 .0
10356 .start
10357 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10358 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10359 }) {
10360 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10361 Some(())
10362 } else {
10363 None
10364 }
10365 }
10366
10367 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10368 self.manipulate_lines(window, cx, |lines| lines.reverse())
10369 }
10370
10371 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10372 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10373 }
10374
10375 fn manipulate_lines<Fn>(
10376 &mut self,
10377 window: &mut Window,
10378 cx: &mut Context<Self>,
10379 mut callback: Fn,
10380 ) where
10381 Fn: FnMut(&mut Vec<&str>),
10382 {
10383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10384
10385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10386 let buffer = self.buffer.read(cx).snapshot(cx);
10387
10388 let mut edits = Vec::new();
10389
10390 let selections = self.selections.all::<Point>(cx);
10391 let mut selections = selections.iter().peekable();
10392 let mut contiguous_row_selections = Vec::new();
10393 let mut new_selections = Vec::new();
10394 let mut added_lines = 0;
10395 let mut removed_lines = 0;
10396
10397 while let Some(selection) = selections.next() {
10398 let (start_row, end_row) = consume_contiguous_rows(
10399 &mut contiguous_row_selections,
10400 selection,
10401 &display_map,
10402 &mut selections,
10403 );
10404
10405 let start_point = Point::new(start_row.0, 0);
10406 let end_point = Point::new(
10407 end_row.previous_row().0,
10408 buffer.line_len(end_row.previous_row()),
10409 );
10410 let text = buffer
10411 .text_for_range(start_point..end_point)
10412 .collect::<String>();
10413
10414 let mut lines = text.split('\n').collect_vec();
10415
10416 let lines_before = lines.len();
10417 callback(&mut lines);
10418 let lines_after = lines.len();
10419
10420 edits.push((start_point..end_point, lines.join("\n")));
10421
10422 // Selections must change based on added and removed line count
10423 let start_row =
10424 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10425 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10426 new_selections.push(Selection {
10427 id: selection.id,
10428 start: start_row,
10429 end: end_row,
10430 goal: SelectionGoal::None,
10431 reversed: selection.reversed,
10432 });
10433
10434 if lines_after > lines_before {
10435 added_lines += lines_after - lines_before;
10436 } else if lines_before > lines_after {
10437 removed_lines += lines_before - lines_after;
10438 }
10439 }
10440
10441 self.transact(window, cx, |this, window, cx| {
10442 let buffer = this.buffer.update(cx, |buffer, cx| {
10443 buffer.edit(edits, None, cx);
10444 buffer.snapshot(cx)
10445 });
10446
10447 // Recalculate offsets on newly edited buffer
10448 let new_selections = new_selections
10449 .iter()
10450 .map(|s| {
10451 let start_point = Point::new(s.start.0, 0);
10452 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10453 Selection {
10454 id: s.id,
10455 start: buffer.point_to_offset(start_point),
10456 end: buffer.point_to_offset(end_point),
10457 goal: s.goal,
10458 reversed: s.reversed,
10459 }
10460 })
10461 .collect();
10462
10463 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10464 s.select(new_selections);
10465 });
10466
10467 this.request_autoscroll(Autoscroll::fit(), cx);
10468 });
10469 }
10470
10471 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10472 self.manipulate_text(window, cx, |text| {
10473 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10474 if has_upper_case_characters {
10475 text.to_lowercase()
10476 } else {
10477 text.to_uppercase()
10478 }
10479 })
10480 }
10481
10482 pub fn convert_to_upper_case(
10483 &mut self,
10484 _: &ConvertToUpperCase,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_text(window, cx, |text| text.to_uppercase())
10489 }
10490
10491 pub fn convert_to_lower_case(
10492 &mut self,
10493 _: &ConvertToLowerCase,
10494 window: &mut Window,
10495 cx: &mut Context<Self>,
10496 ) {
10497 self.manipulate_text(window, cx, |text| text.to_lowercase())
10498 }
10499
10500 pub fn convert_to_title_case(
10501 &mut self,
10502 _: &ConvertToTitleCase,
10503 window: &mut Window,
10504 cx: &mut Context<Self>,
10505 ) {
10506 self.manipulate_text(window, cx, |text| {
10507 text.split('\n')
10508 .map(|line| line.to_case(Case::Title))
10509 .join("\n")
10510 })
10511 }
10512
10513 pub fn convert_to_snake_case(
10514 &mut self,
10515 _: &ConvertToSnakeCase,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10520 }
10521
10522 pub fn convert_to_kebab_case(
10523 &mut self,
10524 _: &ConvertToKebabCase,
10525 window: &mut Window,
10526 cx: &mut Context<Self>,
10527 ) {
10528 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10529 }
10530
10531 pub fn convert_to_upper_camel_case(
10532 &mut self,
10533 _: &ConvertToUpperCamelCase,
10534 window: &mut Window,
10535 cx: &mut Context<Self>,
10536 ) {
10537 self.manipulate_text(window, cx, |text| {
10538 text.split('\n')
10539 .map(|line| line.to_case(Case::UpperCamel))
10540 .join("\n")
10541 })
10542 }
10543
10544 pub fn convert_to_lower_camel_case(
10545 &mut self,
10546 _: &ConvertToLowerCamelCase,
10547 window: &mut Window,
10548 cx: &mut Context<Self>,
10549 ) {
10550 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10551 }
10552
10553 pub fn convert_to_opposite_case(
10554 &mut self,
10555 _: &ConvertToOppositeCase,
10556 window: &mut Window,
10557 cx: &mut Context<Self>,
10558 ) {
10559 self.manipulate_text(window, cx, |text| {
10560 text.chars()
10561 .fold(String::with_capacity(text.len()), |mut t, c| {
10562 if c.is_uppercase() {
10563 t.extend(c.to_lowercase());
10564 } else {
10565 t.extend(c.to_uppercase());
10566 }
10567 t
10568 })
10569 })
10570 }
10571
10572 pub fn convert_to_rot13(
10573 &mut self,
10574 _: &ConvertToRot13,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 self.manipulate_text(window, cx, |text| {
10579 text.chars()
10580 .map(|c| match c {
10581 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10582 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10583 _ => c,
10584 })
10585 .collect()
10586 })
10587 }
10588
10589 pub fn convert_to_rot47(
10590 &mut self,
10591 _: &ConvertToRot47,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) {
10595 self.manipulate_text(window, cx, |text| {
10596 text.chars()
10597 .map(|c| {
10598 let code_point = c as u32;
10599 if code_point >= 33 && code_point <= 126 {
10600 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10601 }
10602 c
10603 })
10604 .collect()
10605 })
10606 }
10607
10608 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10609 where
10610 Fn: FnMut(&str) -> String,
10611 {
10612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10613 let buffer = self.buffer.read(cx).snapshot(cx);
10614
10615 let mut new_selections = Vec::new();
10616 let mut edits = Vec::new();
10617 let mut selection_adjustment = 0i32;
10618
10619 for selection in self.selections.all::<usize>(cx) {
10620 let selection_is_empty = selection.is_empty();
10621
10622 let (start, end) = if selection_is_empty {
10623 let word_range = movement::surrounding_word(
10624 &display_map,
10625 selection.start.to_display_point(&display_map),
10626 );
10627 let start = word_range.start.to_offset(&display_map, Bias::Left);
10628 let end = word_range.end.to_offset(&display_map, Bias::Left);
10629 (start, end)
10630 } else {
10631 (selection.start, selection.end)
10632 };
10633
10634 let text = buffer.text_for_range(start..end).collect::<String>();
10635 let old_length = text.len() as i32;
10636 let text = callback(&text);
10637
10638 new_selections.push(Selection {
10639 start: (start as i32 - selection_adjustment) as usize,
10640 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10641 goal: SelectionGoal::None,
10642 ..selection
10643 });
10644
10645 selection_adjustment += old_length - text.len() as i32;
10646
10647 edits.push((start..end, text));
10648 }
10649
10650 self.transact(window, cx, |this, window, cx| {
10651 this.buffer.update(cx, |buffer, cx| {
10652 buffer.edit(edits, None, cx);
10653 });
10654
10655 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10656 s.select(new_selections);
10657 });
10658
10659 this.request_autoscroll(Autoscroll::fit(), cx);
10660 });
10661 }
10662
10663 pub fn move_selection_on_drop(
10664 &mut self,
10665 selection: &Selection<Anchor>,
10666 target: DisplayPoint,
10667 is_cut: bool,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10672 let buffer = &display_map.buffer_snapshot;
10673 let mut edits = Vec::new();
10674 let insert_point = display_map
10675 .clip_point(target, Bias::Left)
10676 .to_point(&display_map);
10677 let text = buffer
10678 .text_for_range(selection.start..selection.end)
10679 .collect::<String>();
10680 if is_cut {
10681 edits.push(((selection.start..selection.end), String::new()));
10682 }
10683 let insert_anchor = buffer.anchor_before(insert_point);
10684 edits.push(((insert_anchor..insert_anchor), text));
10685 let last_edit_start = insert_anchor.bias_left(buffer);
10686 let last_edit_end = insert_anchor.bias_right(buffer);
10687 self.transact(window, cx, |this, window, cx| {
10688 this.buffer.update(cx, |buffer, cx| {
10689 buffer.edit(edits, None, cx);
10690 });
10691 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10692 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10693 });
10694 });
10695 }
10696
10697 pub fn clear_selection_drag_state(&mut self) {
10698 self.selection_drag_state = SelectionDragState::None;
10699 }
10700
10701 pub fn duplicate(
10702 &mut self,
10703 upwards: bool,
10704 whole_lines: bool,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10709
10710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10711 let buffer = &display_map.buffer_snapshot;
10712 let selections = self.selections.all::<Point>(cx);
10713
10714 let mut edits = Vec::new();
10715 let mut selections_iter = selections.iter().peekable();
10716 while let Some(selection) = selections_iter.next() {
10717 let mut rows = selection.spanned_rows(false, &display_map);
10718 // duplicate line-wise
10719 if whole_lines || selection.start == selection.end {
10720 // Avoid duplicating the same lines twice.
10721 while let Some(next_selection) = selections_iter.peek() {
10722 let next_rows = next_selection.spanned_rows(false, &display_map);
10723 if next_rows.start < rows.end {
10724 rows.end = next_rows.end;
10725 selections_iter.next().unwrap();
10726 } else {
10727 break;
10728 }
10729 }
10730
10731 // Copy the text from the selected row region and splice it either at the start
10732 // or end of the region.
10733 let start = Point::new(rows.start.0, 0);
10734 let end = Point::new(
10735 rows.end.previous_row().0,
10736 buffer.line_len(rows.end.previous_row()),
10737 );
10738 let text = buffer
10739 .text_for_range(start..end)
10740 .chain(Some("\n"))
10741 .collect::<String>();
10742 let insert_location = if upwards {
10743 Point::new(rows.end.0, 0)
10744 } else {
10745 start
10746 };
10747 edits.push((insert_location..insert_location, text));
10748 } else {
10749 // duplicate character-wise
10750 let start = selection.start;
10751 let end = selection.end;
10752 let text = buffer.text_for_range(start..end).collect::<String>();
10753 edits.push((selection.end..selection.end, text));
10754 }
10755 }
10756
10757 self.transact(window, cx, |this, _, cx| {
10758 this.buffer.update(cx, |buffer, cx| {
10759 buffer.edit(edits, None, cx);
10760 });
10761
10762 this.request_autoscroll(Autoscroll::fit(), cx);
10763 });
10764 }
10765
10766 pub fn duplicate_line_up(
10767 &mut self,
10768 _: &DuplicateLineUp,
10769 window: &mut Window,
10770 cx: &mut Context<Self>,
10771 ) {
10772 self.duplicate(true, true, window, cx);
10773 }
10774
10775 pub fn duplicate_line_down(
10776 &mut self,
10777 _: &DuplicateLineDown,
10778 window: &mut Window,
10779 cx: &mut Context<Self>,
10780 ) {
10781 self.duplicate(false, true, window, cx);
10782 }
10783
10784 pub fn duplicate_selection(
10785 &mut self,
10786 _: &DuplicateSelection,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 ) {
10790 self.duplicate(false, false, window, cx);
10791 }
10792
10793 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10795
10796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10797 let buffer = self.buffer.read(cx).snapshot(cx);
10798
10799 let mut edits = Vec::new();
10800 let mut unfold_ranges = Vec::new();
10801 let mut refold_creases = Vec::new();
10802
10803 let selections = self.selections.all::<Point>(cx);
10804 let mut selections = selections.iter().peekable();
10805 let mut contiguous_row_selections = Vec::new();
10806 let mut new_selections = Vec::new();
10807
10808 while let Some(selection) = selections.next() {
10809 // Find all the selections that span a contiguous row range
10810 let (start_row, end_row) = consume_contiguous_rows(
10811 &mut contiguous_row_selections,
10812 selection,
10813 &display_map,
10814 &mut selections,
10815 );
10816
10817 // Move the text spanned by the row range to be before the line preceding the row range
10818 if start_row.0 > 0 {
10819 let range_to_move = Point::new(
10820 start_row.previous_row().0,
10821 buffer.line_len(start_row.previous_row()),
10822 )
10823 ..Point::new(
10824 end_row.previous_row().0,
10825 buffer.line_len(end_row.previous_row()),
10826 );
10827 let insertion_point = display_map
10828 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10829 .0;
10830
10831 // Don't move lines across excerpts
10832 if buffer
10833 .excerpt_containing(insertion_point..range_to_move.end)
10834 .is_some()
10835 {
10836 let text = buffer
10837 .text_for_range(range_to_move.clone())
10838 .flat_map(|s| s.chars())
10839 .skip(1)
10840 .chain(['\n'])
10841 .collect::<String>();
10842
10843 edits.push((
10844 buffer.anchor_after(range_to_move.start)
10845 ..buffer.anchor_before(range_to_move.end),
10846 String::new(),
10847 ));
10848 let insertion_anchor = buffer.anchor_after(insertion_point);
10849 edits.push((insertion_anchor..insertion_anchor, text));
10850
10851 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10852
10853 // Move selections up
10854 new_selections.extend(contiguous_row_selections.drain(..).map(
10855 |mut selection| {
10856 selection.start.row -= row_delta;
10857 selection.end.row -= row_delta;
10858 selection
10859 },
10860 ));
10861
10862 // Move folds up
10863 unfold_ranges.push(range_to_move.clone());
10864 for fold in display_map.folds_in_range(
10865 buffer.anchor_before(range_to_move.start)
10866 ..buffer.anchor_after(range_to_move.end),
10867 ) {
10868 let mut start = fold.range.start.to_point(&buffer);
10869 let mut end = fold.range.end.to_point(&buffer);
10870 start.row -= row_delta;
10871 end.row -= row_delta;
10872 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10873 }
10874 }
10875 }
10876
10877 // If we didn't move line(s), preserve the existing selections
10878 new_selections.append(&mut contiguous_row_selections);
10879 }
10880
10881 self.transact(window, cx, |this, window, cx| {
10882 this.unfold_ranges(&unfold_ranges, true, true, cx);
10883 this.buffer.update(cx, |buffer, cx| {
10884 for (range, text) in edits {
10885 buffer.edit([(range, text)], None, cx);
10886 }
10887 });
10888 this.fold_creases(refold_creases, true, window, cx);
10889 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10890 s.select(new_selections);
10891 })
10892 });
10893 }
10894
10895 pub fn move_line_down(
10896 &mut self,
10897 _: &MoveLineDown,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10902
10903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10904 let buffer = self.buffer.read(cx).snapshot(cx);
10905
10906 let mut edits = Vec::new();
10907 let mut unfold_ranges = Vec::new();
10908 let mut refold_creases = Vec::new();
10909
10910 let selections = self.selections.all::<Point>(cx);
10911 let mut selections = selections.iter().peekable();
10912 let mut contiguous_row_selections = Vec::new();
10913 let mut new_selections = Vec::new();
10914
10915 while let Some(selection) = selections.next() {
10916 // Find all the selections that span a contiguous row range
10917 let (start_row, end_row) = consume_contiguous_rows(
10918 &mut contiguous_row_selections,
10919 selection,
10920 &display_map,
10921 &mut selections,
10922 );
10923
10924 // Move the text spanned by the row range to be after the last line of the row range
10925 if end_row.0 <= buffer.max_point().row {
10926 let range_to_move =
10927 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10928 let insertion_point = display_map
10929 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10930 .0;
10931
10932 // Don't move lines across excerpt boundaries
10933 if buffer
10934 .excerpt_containing(range_to_move.start..insertion_point)
10935 .is_some()
10936 {
10937 let mut text = String::from("\n");
10938 text.extend(buffer.text_for_range(range_to_move.clone()));
10939 text.pop(); // Drop trailing newline
10940 edits.push((
10941 buffer.anchor_after(range_to_move.start)
10942 ..buffer.anchor_before(range_to_move.end),
10943 String::new(),
10944 ));
10945 let insertion_anchor = buffer.anchor_after(insertion_point);
10946 edits.push((insertion_anchor..insertion_anchor, text));
10947
10948 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10949
10950 // Move selections down
10951 new_selections.extend(contiguous_row_selections.drain(..).map(
10952 |mut selection| {
10953 selection.start.row += row_delta;
10954 selection.end.row += row_delta;
10955 selection
10956 },
10957 ));
10958
10959 // Move folds down
10960 unfold_ranges.push(range_to_move.clone());
10961 for fold in display_map.folds_in_range(
10962 buffer.anchor_before(range_to_move.start)
10963 ..buffer.anchor_after(range_to_move.end),
10964 ) {
10965 let mut start = fold.range.start.to_point(&buffer);
10966 let mut end = fold.range.end.to_point(&buffer);
10967 start.row += row_delta;
10968 end.row += row_delta;
10969 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10970 }
10971 }
10972 }
10973
10974 // If we didn't move line(s), preserve the existing selections
10975 new_selections.append(&mut contiguous_row_selections);
10976 }
10977
10978 self.transact(window, cx, |this, window, cx| {
10979 this.unfold_ranges(&unfold_ranges, true, true, cx);
10980 this.buffer.update(cx, |buffer, cx| {
10981 for (range, text) in edits {
10982 buffer.edit([(range, text)], None, cx);
10983 }
10984 });
10985 this.fold_creases(refold_creases, true, window, cx);
10986 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10987 s.select(new_selections)
10988 });
10989 });
10990 }
10991
10992 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10994 let text_layout_details = &self.text_layout_details(window);
10995 self.transact(window, cx, |this, window, cx| {
10996 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10997 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10998 s.move_with(|display_map, selection| {
10999 if !selection.is_empty() {
11000 return;
11001 }
11002
11003 let mut head = selection.head();
11004 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11005 if head.column() == display_map.line_len(head.row()) {
11006 transpose_offset = display_map
11007 .buffer_snapshot
11008 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11009 }
11010
11011 if transpose_offset == 0 {
11012 return;
11013 }
11014
11015 *head.column_mut() += 1;
11016 head = display_map.clip_point(head, Bias::Right);
11017 let goal = SelectionGoal::HorizontalPosition(
11018 display_map
11019 .x_for_display_point(head, text_layout_details)
11020 .into(),
11021 );
11022 selection.collapse_to(head, goal);
11023
11024 let transpose_start = display_map
11025 .buffer_snapshot
11026 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11027 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11028 let transpose_end = display_map
11029 .buffer_snapshot
11030 .clip_offset(transpose_offset + 1, Bias::Right);
11031 if let Some(ch) =
11032 display_map.buffer_snapshot.chars_at(transpose_start).next()
11033 {
11034 edits.push((transpose_start..transpose_offset, String::new()));
11035 edits.push((transpose_end..transpose_end, ch.to_string()));
11036 }
11037 }
11038 });
11039 edits
11040 });
11041 this.buffer
11042 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11043 let selections = this.selections.all::<usize>(cx);
11044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11045 s.select(selections);
11046 });
11047 });
11048 }
11049
11050 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11051 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11052 self.rewrap_impl(RewrapOptions::default(), cx)
11053 }
11054
11055 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11056 let buffer = self.buffer.read(cx).snapshot(cx);
11057 let selections = self.selections.all::<Point>(cx);
11058
11059 // Shrink and split selections to respect paragraph boundaries.
11060 let ranges = selections.into_iter().flat_map(|selection| {
11061 let language_settings = buffer.language_settings_at(selection.head(), cx);
11062 let language_scope = buffer.language_scope_at(selection.head());
11063
11064 let Some(start_row) = (selection.start.row..=selection.end.row)
11065 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11066 else {
11067 return vec![];
11068 };
11069 let Some(end_row) = (selection.start.row..=selection.end.row)
11070 .rev()
11071 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11072 else {
11073 return vec![];
11074 };
11075
11076 let mut row = start_row;
11077 let mut ranges = Vec::new();
11078 while let Some(blank_row) =
11079 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11080 {
11081 let next_paragraph_start = (blank_row + 1..=end_row)
11082 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11083 .unwrap();
11084 ranges.push((
11085 language_settings.clone(),
11086 language_scope.clone(),
11087 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11088 ));
11089 row = next_paragraph_start;
11090 }
11091 ranges.push((
11092 language_settings.clone(),
11093 language_scope.clone(),
11094 Point::new(row, 0)..Point::new(end_row, 0),
11095 ));
11096
11097 ranges
11098 });
11099
11100 let mut edits = Vec::new();
11101 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11102
11103 for (language_settings, language_scope, range) in ranges {
11104 let mut start_row = range.start.row;
11105 let mut end_row = range.end.row;
11106
11107 // Skip selections that overlap with a range that has already been rewrapped.
11108 let selection_range = start_row..end_row;
11109 if rewrapped_row_ranges
11110 .iter()
11111 .any(|range| range.overlaps(&selection_range))
11112 {
11113 continue;
11114 }
11115
11116 let tab_size = language_settings.tab_size;
11117
11118 // Since not all lines in the selection may be at the same indent
11119 // level, choose the indent size that is the most common between all
11120 // of the lines.
11121 //
11122 // If there is a tie, we use the deepest indent.
11123 let (indent_size, indent_end) = {
11124 let mut indent_size_occurrences = HashMap::default();
11125 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11126
11127 for row in start_row..=end_row {
11128 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11129 rows_by_indent_size.entry(indent).or_default().push(row);
11130 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11131 }
11132
11133 let indent_size = indent_size_occurrences
11134 .into_iter()
11135 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11136 .map(|(indent, _)| indent)
11137 .unwrap_or_default();
11138 let row = rows_by_indent_size[&indent_size][0];
11139 let indent_end = Point::new(row, indent_size.len);
11140
11141 (indent_size, indent_end)
11142 };
11143
11144 let mut line_prefix = indent_size.chars().collect::<String>();
11145
11146 let mut inside_comment = false;
11147 if let Some(comment_prefix) = language_scope.and_then(|language| {
11148 language
11149 .line_comment_prefixes()
11150 .iter()
11151 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11152 .cloned()
11153 }) {
11154 line_prefix.push_str(&comment_prefix);
11155 inside_comment = true;
11156 }
11157
11158 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11159 RewrapBehavior::InComments => inside_comment,
11160 RewrapBehavior::InSelections => !range.is_empty(),
11161 RewrapBehavior::Anywhere => true,
11162 };
11163
11164 let should_rewrap = options.override_language_settings
11165 || allow_rewrap_based_on_language
11166 || self.hard_wrap.is_some();
11167 if !should_rewrap {
11168 continue;
11169 }
11170
11171 if range.is_empty() {
11172 'expand_upwards: while start_row > 0 {
11173 let prev_row = start_row - 1;
11174 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11175 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11176 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11177 {
11178 start_row = prev_row;
11179 } else {
11180 break 'expand_upwards;
11181 }
11182 }
11183
11184 'expand_downwards: while end_row < buffer.max_point().row {
11185 let next_row = end_row + 1;
11186 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11187 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11188 && !buffer.is_line_blank(MultiBufferRow(next_row))
11189 {
11190 end_row = next_row;
11191 } else {
11192 break 'expand_downwards;
11193 }
11194 }
11195 }
11196
11197 let start = Point::new(start_row, 0);
11198 let start_offset = start.to_offset(&buffer);
11199 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11200 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11201 let Some(lines_without_prefixes) = selection_text
11202 .lines()
11203 .map(|line| {
11204 line.strip_prefix(&line_prefix)
11205 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11206 .with_context(|| {
11207 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11208 })
11209 })
11210 .collect::<Result<Vec<_>, _>>()
11211 .log_err()
11212 else {
11213 continue;
11214 };
11215
11216 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11217 buffer
11218 .language_settings_at(Point::new(start_row, 0), cx)
11219 .preferred_line_length as usize
11220 });
11221 let wrapped_text = wrap_with_prefix(
11222 line_prefix,
11223 lines_without_prefixes.join("\n"),
11224 wrap_column,
11225 tab_size,
11226 options.preserve_existing_whitespace,
11227 );
11228
11229 // TODO: should always use char-based diff while still supporting cursor behavior that
11230 // matches vim.
11231 let mut diff_options = DiffOptions::default();
11232 if options.override_language_settings {
11233 diff_options.max_word_diff_len = 0;
11234 diff_options.max_word_diff_line_count = 0;
11235 } else {
11236 diff_options.max_word_diff_len = usize::MAX;
11237 diff_options.max_word_diff_line_count = usize::MAX;
11238 }
11239
11240 for (old_range, new_text) in
11241 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11242 {
11243 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11244 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11245 edits.push((edit_start..edit_end, new_text));
11246 }
11247
11248 rewrapped_row_ranges.push(start_row..=end_row);
11249 }
11250
11251 self.buffer
11252 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11253 }
11254
11255 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11256 let mut text = String::new();
11257 let buffer = self.buffer.read(cx).snapshot(cx);
11258 let mut selections = self.selections.all::<Point>(cx);
11259 let mut clipboard_selections = Vec::with_capacity(selections.len());
11260 {
11261 let max_point = buffer.max_point();
11262 let mut is_first = true;
11263 for selection in &mut selections {
11264 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11265 if is_entire_line {
11266 selection.start = Point::new(selection.start.row, 0);
11267 if !selection.is_empty() && selection.end.column == 0 {
11268 selection.end = cmp::min(max_point, selection.end);
11269 } else {
11270 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11271 }
11272 selection.goal = SelectionGoal::None;
11273 }
11274 if is_first {
11275 is_first = false;
11276 } else {
11277 text += "\n";
11278 }
11279 let mut len = 0;
11280 for chunk in buffer.text_for_range(selection.start..selection.end) {
11281 text.push_str(chunk);
11282 len += chunk.len();
11283 }
11284 clipboard_selections.push(ClipboardSelection {
11285 len,
11286 is_entire_line,
11287 first_line_indent: buffer
11288 .indent_size_for_line(MultiBufferRow(selection.start.row))
11289 .len,
11290 });
11291 }
11292 }
11293
11294 self.transact(window, cx, |this, window, cx| {
11295 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11296 s.select(selections);
11297 });
11298 this.insert("", window, cx);
11299 });
11300 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11301 }
11302
11303 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11304 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11305 let item = self.cut_common(window, cx);
11306 cx.write_to_clipboard(item);
11307 }
11308
11309 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11310 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11311 self.change_selections(None, window, cx, |s| {
11312 s.move_with(|snapshot, sel| {
11313 if sel.is_empty() {
11314 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11315 }
11316 });
11317 });
11318 let item = self.cut_common(window, cx);
11319 cx.set_global(KillRing(item))
11320 }
11321
11322 pub fn kill_ring_yank(
11323 &mut self,
11324 _: &KillRingYank,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11329 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11330 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11331 (kill_ring.text().to_string(), kill_ring.metadata_json())
11332 } else {
11333 return;
11334 }
11335 } else {
11336 return;
11337 };
11338 self.do_paste(&text, metadata, false, window, cx);
11339 }
11340
11341 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11342 self.do_copy(true, cx);
11343 }
11344
11345 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11346 self.do_copy(false, cx);
11347 }
11348
11349 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11350 let selections = self.selections.all::<Point>(cx);
11351 let buffer = self.buffer.read(cx).read(cx);
11352 let mut text = String::new();
11353
11354 let mut clipboard_selections = Vec::with_capacity(selections.len());
11355 {
11356 let max_point = buffer.max_point();
11357 let mut is_first = true;
11358 for selection in &selections {
11359 let mut start = selection.start;
11360 let mut end = selection.end;
11361 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11362 if is_entire_line {
11363 start = Point::new(start.row, 0);
11364 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11365 }
11366
11367 let mut trimmed_selections = Vec::new();
11368 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11369 let row = MultiBufferRow(start.row);
11370 let first_indent = buffer.indent_size_for_line(row);
11371 if first_indent.len == 0 || start.column > first_indent.len {
11372 trimmed_selections.push(start..end);
11373 } else {
11374 trimmed_selections.push(
11375 Point::new(row.0, first_indent.len)
11376 ..Point::new(row.0, buffer.line_len(row)),
11377 );
11378 for row in start.row + 1..=end.row {
11379 let mut line_len = buffer.line_len(MultiBufferRow(row));
11380 if row == end.row {
11381 line_len = end.column;
11382 }
11383 if line_len == 0 {
11384 trimmed_selections
11385 .push(Point::new(row, 0)..Point::new(row, line_len));
11386 continue;
11387 }
11388 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11389 if row_indent_size.len >= first_indent.len {
11390 trimmed_selections.push(
11391 Point::new(row, first_indent.len)..Point::new(row, line_len),
11392 );
11393 } else {
11394 trimmed_selections.clear();
11395 trimmed_selections.push(start..end);
11396 break;
11397 }
11398 }
11399 }
11400 } else {
11401 trimmed_selections.push(start..end);
11402 }
11403
11404 for trimmed_range in trimmed_selections {
11405 if is_first {
11406 is_first = false;
11407 } else {
11408 text += "\n";
11409 }
11410 let mut len = 0;
11411 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11412 text.push_str(chunk);
11413 len += chunk.len();
11414 }
11415 clipboard_selections.push(ClipboardSelection {
11416 len,
11417 is_entire_line,
11418 first_line_indent: buffer
11419 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11420 .len,
11421 });
11422 }
11423 }
11424 }
11425
11426 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11427 text,
11428 clipboard_selections,
11429 ));
11430 }
11431
11432 pub fn do_paste(
11433 &mut self,
11434 text: &String,
11435 clipboard_selections: Option<Vec<ClipboardSelection>>,
11436 handle_entire_lines: bool,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 if self.read_only(cx) {
11441 return;
11442 }
11443
11444 let clipboard_text = Cow::Borrowed(text);
11445
11446 self.transact(window, cx, |this, window, cx| {
11447 if let Some(mut clipboard_selections) = clipboard_selections {
11448 let old_selections = this.selections.all::<usize>(cx);
11449 let all_selections_were_entire_line =
11450 clipboard_selections.iter().all(|s| s.is_entire_line);
11451 let first_selection_indent_column =
11452 clipboard_selections.first().map(|s| s.first_line_indent);
11453 if clipboard_selections.len() != old_selections.len() {
11454 clipboard_selections.drain(..);
11455 }
11456 let cursor_offset = this.selections.last::<usize>(cx).head();
11457 let mut auto_indent_on_paste = true;
11458
11459 this.buffer.update(cx, |buffer, cx| {
11460 let snapshot = buffer.read(cx);
11461 auto_indent_on_paste = snapshot
11462 .language_settings_at(cursor_offset, cx)
11463 .auto_indent_on_paste;
11464
11465 let mut start_offset = 0;
11466 let mut edits = Vec::new();
11467 let mut original_indent_columns = Vec::new();
11468 for (ix, selection) in old_selections.iter().enumerate() {
11469 let to_insert;
11470 let entire_line;
11471 let original_indent_column;
11472 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11473 let end_offset = start_offset + clipboard_selection.len;
11474 to_insert = &clipboard_text[start_offset..end_offset];
11475 entire_line = clipboard_selection.is_entire_line;
11476 start_offset = end_offset + 1;
11477 original_indent_column = Some(clipboard_selection.first_line_indent);
11478 } else {
11479 to_insert = clipboard_text.as_str();
11480 entire_line = all_selections_were_entire_line;
11481 original_indent_column = first_selection_indent_column
11482 }
11483
11484 // If the corresponding selection was empty when this slice of the
11485 // clipboard text was written, then the entire line containing the
11486 // selection was copied. If this selection is also currently empty,
11487 // then paste the line before the current line of the buffer.
11488 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11489 let column = selection.start.to_point(&snapshot).column as usize;
11490 let line_start = selection.start - column;
11491 line_start..line_start
11492 } else {
11493 selection.range()
11494 };
11495
11496 edits.push((range, to_insert));
11497 original_indent_columns.push(original_indent_column);
11498 }
11499 drop(snapshot);
11500
11501 buffer.edit(
11502 edits,
11503 if auto_indent_on_paste {
11504 Some(AutoindentMode::Block {
11505 original_indent_columns,
11506 })
11507 } else {
11508 None
11509 },
11510 cx,
11511 );
11512 });
11513
11514 let selections = this.selections.all::<usize>(cx);
11515 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11516 s.select(selections)
11517 });
11518 } else {
11519 this.insert(&clipboard_text, window, cx);
11520 }
11521 });
11522 }
11523
11524 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11526 if let Some(item) = cx.read_from_clipboard() {
11527 let entries = item.entries();
11528
11529 match entries.first() {
11530 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11531 // of all the pasted entries.
11532 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11533 .do_paste(
11534 clipboard_string.text(),
11535 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11536 true,
11537 window,
11538 cx,
11539 ),
11540 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11541 }
11542 }
11543 }
11544
11545 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11546 if self.read_only(cx) {
11547 return;
11548 }
11549
11550 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11551
11552 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11553 if let Some((selections, _)) =
11554 self.selection_history.transaction(transaction_id).cloned()
11555 {
11556 self.change_selections(None, window, cx, |s| {
11557 s.select_anchors(selections.to_vec());
11558 });
11559 } else {
11560 log::error!(
11561 "No entry in selection_history found for undo. \
11562 This may correspond to a bug where undo does not update the selection. \
11563 If this is occurring, please add details to \
11564 https://github.com/zed-industries/zed/issues/22692"
11565 );
11566 }
11567 self.request_autoscroll(Autoscroll::fit(), cx);
11568 self.unmark_text(window, cx);
11569 self.refresh_inline_completion(true, false, window, cx);
11570 cx.emit(EditorEvent::Edited { transaction_id });
11571 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11572 }
11573 }
11574
11575 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11576 if self.read_only(cx) {
11577 return;
11578 }
11579
11580 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11581
11582 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11583 if let Some((_, Some(selections))) =
11584 self.selection_history.transaction(transaction_id).cloned()
11585 {
11586 self.change_selections(None, window, cx, |s| {
11587 s.select_anchors(selections.to_vec());
11588 });
11589 } else {
11590 log::error!(
11591 "No entry in selection_history found for redo. \
11592 This may correspond to a bug where undo does not update the selection. \
11593 If this is occurring, please add details to \
11594 https://github.com/zed-industries/zed/issues/22692"
11595 );
11596 }
11597 self.request_autoscroll(Autoscroll::fit(), cx);
11598 self.unmark_text(window, cx);
11599 self.refresh_inline_completion(true, false, window, cx);
11600 cx.emit(EditorEvent::Edited { transaction_id });
11601 }
11602 }
11603
11604 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11605 self.buffer
11606 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11607 }
11608
11609 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11610 self.buffer
11611 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11612 }
11613
11614 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11615 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11616 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11617 s.move_with(|map, selection| {
11618 let cursor = if selection.is_empty() {
11619 movement::left(map, selection.start)
11620 } else {
11621 selection.start
11622 };
11623 selection.collapse_to(cursor, SelectionGoal::None);
11624 });
11625 })
11626 }
11627
11628 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11631 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11632 })
11633 }
11634
11635 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11636 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11638 s.move_with(|map, selection| {
11639 let cursor = if selection.is_empty() {
11640 movement::right(map, selection.end)
11641 } else {
11642 selection.end
11643 };
11644 selection.collapse_to(cursor, SelectionGoal::None)
11645 });
11646 })
11647 }
11648
11649 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11652 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11653 })
11654 }
11655
11656 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11657 if self.take_rename(true, window, cx).is_some() {
11658 return;
11659 }
11660
11661 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11662 cx.propagate();
11663 return;
11664 }
11665
11666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11667
11668 let text_layout_details = &self.text_layout_details(window);
11669 let selection_count = self.selections.count();
11670 let first_selection = self.selections.first_anchor();
11671
11672 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11673 s.move_with(|map, selection| {
11674 if !selection.is_empty() {
11675 selection.goal = SelectionGoal::None;
11676 }
11677 let (cursor, goal) = movement::up(
11678 map,
11679 selection.start,
11680 selection.goal,
11681 false,
11682 text_layout_details,
11683 );
11684 selection.collapse_to(cursor, goal);
11685 });
11686 });
11687
11688 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11689 {
11690 cx.propagate();
11691 }
11692 }
11693
11694 pub fn move_up_by_lines(
11695 &mut self,
11696 action: &MoveUpByLines,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 if self.take_rename(true, window, cx).is_some() {
11701 return;
11702 }
11703
11704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11710
11711 let text_layout_details = &self.text_layout_details(window);
11712
11713 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11714 s.move_with(|map, selection| {
11715 if !selection.is_empty() {
11716 selection.goal = SelectionGoal::None;
11717 }
11718 let (cursor, goal) = movement::up_by_rows(
11719 map,
11720 selection.start,
11721 action.lines,
11722 selection.goal,
11723 false,
11724 text_layout_details,
11725 );
11726 selection.collapse_to(cursor, goal);
11727 });
11728 })
11729 }
11730
11731 pub fn move_down_by_lines(
11732 &mut self,
11733 action: &MoveDownByLines,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 if self.take_rename(true, window, cx).is_some() {
11738 return;
11739 }
11740
11741 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11742 cx.propagate();
11743 return;
11744 }
11745
11746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11747
11748 let text_layout_details = &self.text_layout_details(window);
11749
11750 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11751 s.move_with(|map, selection| {
11752 if !selection.is_empty() {
11753 selection.goal = SelectionGoal::None;
11754 }
11755 let (cursor, goal) = movement::down_by_rows(
11756 map,
11757 selection.start,
11758 action.lines,
11759 selection.goal,
11760 false,
11761 text_layout_details,
11762 );
11763 selection.collapse_to(cursor, goal);
11764 });
11765 })
11766 }
11767
11768 pub fn select_down_by_lines(
11769 &mut self,
11770 action: &SelectDownByLines,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11775 let text_layout_details = &self.text_layout_details(window);
11776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11777 s.move_heads_with(|map, head, goal| {
11778 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11779 })
11780 })
11781 }
11782
11783 pub fn select_up_by_lines(
11784 &mut self,
11785 action: &SelectUpByLines,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11790 let text_layout_details = &self.text_layout_details(window);
11791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11792 s.move_heads_with(|map, head, goal| {
11793 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11794 })
11795 })
11796 }
11797
11798 pub fn select_page_up(
11799 &mut self,
11800 _: &SelectPageUp,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 let Some(row_count) = self.visible_row_count() else {
11805 return;
11806 };
11807
11808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11809
11810 let text_layout_details = &self.text_layout_details(window);
11811
11812 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11813 s.move_heads_with(|map, head, goal| {
11814 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11815 })
11816 })
11817 }
11818
11819 pub fn move_page_up(
11820 &mut self,
11821 action: &MovePageUp,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if self.take_rename(true, window, cx).is_some() {
11826 return;
11827 }
11828
11829 if self
11830 .context_menu
11831 .borrow_mut()
11832 .as_mut()
11833 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11834 .unwrap_or(false)
11835 {
11836 return;
11837 }
11838
11839 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11840 cx.propagate();
11841 return;
11842 }
11843
11844 let Some(row_count) = self.visible_row_count() else {
11845 return;
11846 };
11847
11848 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11849
11850 let autoscroll = if action.center_cursor {
11851 Autoscroll::center()
11852 } else {
11853 Autoscroll::fit()
11854 };
11855
11856 let text_layout_details = &self.text_layout_details(window);
11857
11858 self.change_selections(Some(autoscroll), window, cx, |s| {
11859 s.move_with(|map, selection| {
11860 if !selection.is_empty() {
11861 selection.goal = SelectionGoal::None;
11862 }
11863 let (cursor, goal) = movement::up_by_rows(
11864 map,
11865 selection.end,
11866 row_count,
11867 selection.goal,
11868 false,
11869 text_layout_details,
11870 );
11871 selection.collapse_to(cursor, goal);
11872 });
11873 });
11874 }
11875
11876 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11878 let text_layout_details = &self.text_layout_details(window);
11879 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11880 s.move_heads_with(|map, head, goal| {
11881 movement::up(map, head, goal, false, text_layout_details)
11882 })
11883 })
11884 }
11885
11886 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11887 self.take_rename(true, window, cx);
11888
11889 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11890 cx.propagate();
11891 return;
11892 }
11893
11894 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11895
11896 let text_layout_details = &self.text_layout_details(window);
11897 let selection_count = self.selections.count();
11898 let first_selection = self.selections.first_anchor();
11899
11900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11901 s.move_with(|map, selection| {
11902 if !selection.is_empty() {
11903 selection.goal = SelectionGoal::None;
11904 }
11905 let (cursor, goal) = movement::down(
11906 map,
11907 selection.end,
11908 selection.goal,
11909 false,
11910 text_layout_details,
11911 );
11912 selection.collapse_to(cursor, goal);
11913 });
11914 });
11915
11916 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11917 {
11918 cx.propagate();
11919 }
11920 }
11921
11922 pub fn select_page_down(
11923 &mut self,
11924 _: &SelectPageDown,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 ) {
11928 let Some(row_count) = self.visible_row_count() else {
11929 return;
11930 };
11931
11932 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11933
11934 let text_layout_details = &self.text_layout_details(window);
11935
11936 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11937 s.move_heads_with(|map, head, goal| {
11938 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11939 })
11940 })
11941 }
11942
11943 pub fn move_page_down(
11944 &mut self,
11945 action: &MovePageDown,
11946 window: &mut Window,
11947 cx: &mut Context<Self>,
11948 ) {
11949 if self.take_rename(true, window, cx).is_some() {
11950 return;
11951 }
11952
11953 if self
11954 .context_menu
11955 .borrow_mut()
11956 .as_mut()
11957 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11958 .unwrap_or(false)
11959 {
11960 return;
11961 }
11962
11963 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11964 cx.propagate();
11965 return;
11966 }
11967
11968 let Some(row_count) = self.visible_row_count() else {
11969 return;
11970 };
11971
11972 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11973
11974 let autoscroll = if action.center_cursor {
11975 Autoscroll::center()
11976 } else {
11977 Autoscroll::fit()
11978 };
11979
11980 let text_layout_details = &self.text_layout_details(window);
11981 self.change_selections(Some(autoscroll), window, cx, |s| {
11982 s.move_with(|map, selection| {
11983 if !selection.is_empty() {
11984 selection.goal = SelectionGoal::None;
11985 }
11986 let (cursor, goal) = movement::down_by_rows(
11987 map,
11988 selection.end,
11989 row_count,
11990 selection.goal,
11991 false,
11992 text_layout_details,
11993 );
11994 selection.collapse_to(cursor, goal);
11995 });
11996 });
11997 }
11998
11999 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12000 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12001 let text_layout_details = &self.text_layout_details(window);
12002 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12003 s.move_heads_with(|map, head, goal| {
12004 movement::down(map, head, goal, false, text_layout_details)
12005 })
12006 });
12007 }
12008
12009 pub fn context_menu_first(
12010 &mut self,
12011 _: &ContextMenuFirst,
12012 window: &mut Window,
12013 cx: &mut Context<Self>,
12014 ) {
12015 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12016 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12017 }
12018 }
12019
12020 pub fn context_menu_prev(
12021 &mut self,
12022 _: &ContextMenuPrevious,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12027 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12028 }
12029 }
12030
12031 pub fn context_menu_next(
12032 &mut self,
12033 _: &ContextMenuNext,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12038 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12039 }
12040 }
12041
12042 pub fn context_menu_last(
12043 &mut self,
12044 _: &ContextMenuLast,
12045 window: &mut Window,
12046 cx: &mut Context<Self>,
12047 ) {
12048 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12049 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12050 }
12051 }
12052
12053 pub fn move_to_previous_word_start(
12054 &mut self,
12055 _: &MoveToPreviousWordStart,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 ) {
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_cursors_with(|map, head, _| {
12062 (
12063 movement::previous_word_start(map, head),
12064 SelectionGoal::None,
12065 )
12066 });
12067 })
12068 }
12069
12070 pub fn move_to_previous_subword_start(
12071 &mut self,
12072 _: &MoveToPreviousSubwordStart,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12078 s.move_cursors_with(|map, head, _| {
12079 (
12080 movement::previous_subword_start(map, head),
12081 SelectionGoal::None,
12082 )
12083 });
12084 })
12085 }
12086
12087 pub fn select_to_previous_word_start(
12088 &mut self,
12089 _: &SelectToPreviousWordStart,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12094 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12095 s.move_heads_with(|map, head, _| {
12096 (
12097 movement::previous_word_start(map, head),
12098 SelectionGoal::None,
12099 )
12100 });
12101 })
12102 }
12103
12104 pub fn select_to_previous_subword_start(
12105 &mut self,
12106 _: &SelectToPreviousSubwordStart,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.move_heads_with(|map, head, _| {
12113 (
12114 movement::previous_subword_start(map, head),
12115 SelectionGoal::None,
12116 )
12117 });
12118 })
12119 }
12120
12121 pub fn delete_to_previous_word_start(
12122 &mut self,
12123 action: &DeleteToPreviousWordStart,
12124 window: &mut Window,
12125 cx: &mut Context<Self>,
12126 ) {
12127 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12128 self.transact(window, cx, |this, window, cx| {
12129 this.select_autoclose_pair(window, cx);
12130 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12131 s.move_with(|map, selection| {
12132 if selection.is_empty() {
12133 let cursor = if action.ignore_newlines {
12134 movement::previous_word_start(map, selection.head())
12135 } else {
12136 movement::previous_word_start_or_newline(map, selection.head())
12137 };
12138 selection.set_head(cursor, SelectionGoal::None);
12139 }
12140 });
12141 });
12142 this.insert("", window, cx);
12143 });
12144 }
12145
12146 pub fn delete_to_previous_subword_start(
12147 &mut self,
12148 _: &DeleteToPreviousSubwordStart,
12149 window: &mut Window,
12150 cx: &mut Context<Self>,
12151 ) {
12152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12153 self.transact(window, cx, |this, window, cx| {
12154 this.select_autoclose_pair(window, cx);
12155 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12156 s.move_with(|map, selection| {
12157 if selection.is_empty() {
12158 let cursor = movement::previous_subword_start(map, selection.head());
12159 selection.set_head(cursor, SelectionGoal::None);
12160 }
12161 });
12162 });
12163 this.insert("", window, cx);
12164 });
12165 }
12166
12167 pub fn move_to_next_word_end(
12168 &mut self,
12169 _: &MoveToNextWordEnd,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12175 s.move_cursors_with(|map, head, _| {
12176 (movement::next_word_end(map, head), SelectionGoal::None)
12177 });
12178 })
12179 }
12180
12181 pub fn move_to_next_subword_end(
12182 &mut self,
12183 _: &MoveToNextSubwordEnd,
12184 window: &mut Window,
12185 cx: &mut Context<Self>,
12186 ) {
12187 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12188 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12189 s.move_cursors_with(|map, head, _| {
12190 (movement::next_subword_end(map, head), SelectionGoal::None)
12191 });
12192 })
12193 }
12194
12195 pub fn select_to_next_word_end(
12196 &mut self,
12197 _: &SelectToNextWordEnd,
12198 window: &mut Window,
12199 cx: &mut Context<Self>,
12200 ) {
12201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12203 s.move_heads_with(|map, head, _| {
12204 (movement::next_word_end(map, head), SelectionGoal::None)
12205 });
12206 })
12207 }
12208
12209 pub fn select_to_next_subword_end(
12210 &mut self,
12211 _: &SelectToNextSubwordEnd,
12212 window: &mut Window,
12213 cx: &mut Context<Self>,
12214 ) {
12215 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12216 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12217 s.move_heads_with(|map, head, _| {
12218 (movement::next_subword_end(map, head), SelectionGoal::None)
12219 });
12220 })
12221 }
12222
12223 pub fn delete_to_next_word_end(
12224 &mut self,
12225 action: &DeleteToNextWordEnd,
12226 window: &mut Window,
12227 cx: &mut Context<Self>,
12228 ) {
12229 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12230 self.transact(window, cx, |this, window, cx| {
12231 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12232 s.move_with(|map, selection| {
12233 if selection.is_empty() {
12234 let cursor = if action.ignore_newlines {
12235 movement::next_word_end(map, selection.head())
12236 } else {
12237 movement::next_word_end_or_newline(map, selection.head())
12238 };
12239 selection.set_head(cursor, SelectionGoal::None);
12240 }
12241 });
12242 });
12243 this.insert("", window, cx);
12244 });
12245 }
12246
12247 pub fn delete_to_next_subword_end(
12248 &mut self,
12249 _: &DeleteToNextSubwordEnd,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12254 self.transact(window, cx, |this, window, cx| {
12255 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12256 s.move_with(|map, selection| {
12257 if selection.is_empty() {
12258 let cursor = movement::next_subword_end(map, selection.head());
12259 selection.set_head(cursor, SelectionGoal::None);
12260 }
12261 });
12262 });
12263 this.insert("", window, cx);
12264 });
12265 }
12266
12267 pub fn move_to_beginning_of_line(
12268 &mut self,
12269 action: &MoveToBeginningOfLine,
12270 window: &mut Window,
12271 cx: &mut Context<Self>,
12272 ) {
12273 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12274 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12275 s.move_cursors_with(|map, head, _| {
12276 (
12277 movement::indented_line_beginning(
12278 map,
12279 head,
12280 action.stop_at_soft_wraps,
12281 action.stop_at_indent,
12282 ),
12283 SelectionGoal::None,
12284 )
12285 });
12286 })
12287 }
12288
12289 pub fn select_to_beginning_of_line(
12290 &mut self,
12291 action: &SelectToBeginningOfLine,
12292 window: &mut Window,
12293 cx: &mut Context<Self>,
12294 ) {
12295 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12296 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12297 s.move_heads_with(|map, head, _| {
12298 (
12299 movement::indented_line_beginning(
12300 map,
12301 head,
12302 action.stop_at_soft_wraps,
12303 action.stop_at_indent,
12304 ),
12305 SelectionGoal::None,
12306 )
12307 });
12308 });
12309 }
12310
12311 pub fn delete_to_beginning_of_line(
12312 &mut self,
12313 action: &DeleteToBeginningOfLine,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12318 self.transact(window, cx, |this, window, cx| {
12319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12320 s.move_with(|_, selection| {
12321 selection.reversed = true;
12322 });
12323 });
12324
12325 this.select_to_beginning_of_line(
12326 &SelectToBeginningOfLine {
12327 stop_at_soft_wraps: false,
12328 stop_at_indent: action.stop_at_indent,
12329 },
12330 window,
12331 cx,
12332 );
12333 this.backspace(&Backspace, window, cx);
12334 });
12335 }
12336
12337 pub fn move_to_end_of_line(
12338 &mut self,
12339 action: &MoveToEndOfLine,
12340 window: &mut Window,
12341 cx: &mut Context<Self>,
12342 ) {
12343 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12344 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12345 s.move_cursors_with(|map, head, _| {
12346 (
12347 movement::line_end(map, head, action.stop_at_soft_wraps),
12348 SelectionGoal::None,
12349 )
12350 });
12351 })
12352 }
12353
12354 pub fn select_to_end_of_line(
12355 &mut self,
12356 action: &SelectToEndOfLine,
12357 window: &mut Window,
12358 cx: &mut Context<Self>,
12359 ) {
12360 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12361 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12362 s.move_heads_with(|map, head, _| {
12363 (
12364 movement::line_end(map, head, action.stop_at_soft_wraps),
12365 SelectionGoal::None,
12366 )
12367 });
12368 })
12369 }
12370
12371 pub fn delete_to_end_of_line(
12372 &mut self,
12373 _: &DeleteToEndOfLine,
12374 window: &mut Window,
12375 cx: &mut Context<Self>,
12376 ) {
12377 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12378 self.transact(window, cx, |this, window, cx| {
12379 this.select_to_end_of_line(
12380 &SelectToEndOfLine {
12381 stop_at_soft_wraps: false,
12382 },
12383 window,
12384 cx,
12385 );
12386 this.delete(&Delete, window, cx);
12387 });
12388 }
12389
12390 pub fn cut_to_end_of_line(
12391 &mut self,
12392 _: &CutToEndOfLine,
12393 window: &mut Window,
12394 cx: &mut Context<Self>,
12395 ) {
12396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12397 self.transact(window, cx, |this, window, cx| {
12398 this.select_to_end_of_line(
12399 &SelectToEndOfLine {
12400 stop_at_soft_wraps: false,
12401 },
12402 window,
12403 cx,
12404 );
12405 this.cut(&Cut, window, cx);
12406 });
12407 }
12408
12409 pub fn move_to_start_of_paragraph(
12410 &mut self,
12411 _: &MoveToStartOfParagraph,
12412 window: &mut Window,
12413 cx: &mut Context<Self>,
12414 ) {
12415 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12416 cx.propagate();
12417 return;
12418 }
12419 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12420 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12421 s.move_with(|map, selection| {
12422 selection.collapse_to(
12423 movement::start_of_paragraph(map, selection.head(), 1),
12424 SelectionGoal::None,
12425 )
12426 });
12427 })
12428 }
12429
12430 pub fn move_to_end_of_paragraph(
12431 &mut self,
12432 _: &MoveToEndOfParagraph,
12433 window: &mut Window,
12434 cx: &mut Context<Self>,
12435 ) {
12436 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12437 cx.propagate();
12438 return;
12439 }
12440 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12441 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12442 s.move_with(|map, selection| {
12443 selection.collapse_to(
12444 movement::end_of_paragraph(map, selection.head(), 1),
12445 SelectionGoal::None,
12446 )
12447 });
12448 })
12449 }
12450
12451 pub fn select_to_start_of_paragraph(
12452 &mut self,
12453 _: &SelectToStartOfParagraph,
12454 window: &mut Window,
12455 cx: &mut Context<Self>,
12456 ) {
12457 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12458 cx.propagate();
12459 return;
12460 }
12461 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12462 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12463 s.move_heads_with(|map, head, _| {
12464 (
12465 movement::start_of_paragraph(map, head, 1),
12466 SelectionGoal::None,
12467 )
12468 });
12469 })
12470 }
12471
12472 pub fn select_to_end_of_paragraph(
12473 &mut self,
12474 _: &SelectToEndOfParagraph,
12475 window: &mut Window,
12476 cx: &mut Context<Self>,
12477 ) {
12478 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12479 cx.propagate();
12480 return;
12481 }
12482 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12483 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12484 s.move_heads_with(|map, head, _| {
12485 (
12486 movement::end_of_paragraph(map, head, 1),
12487 SelectionGoal::None,
12488 )
12489 });
12490 })
12491 }
12492
12493 pub fn move_to_start_of_excerpt(
12494 &mut self,
12495 _: &MoveToStartOfExcerpt,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) {
12499 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12500 cx.propagate();
12501 return;
12502 }
12503 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12505 s.move_with(|map, selection| {
12506 selection.collapse_to(
12507 movement::start_of_excerpt(
12508 map,
12509 selection.head(),
12510 workspace::searchable::Direction::Prev,
12511 ),
12512 SelectionGoal::None,
12513 )
12514 });
12515 })
12516 }
12517
12518 pub fn move_to_start_of_next_excerpt(
12519 &mut self,
12520 _: &MoveToStartOfNextExcerpt,
12521 window: &mut Window,
12522 cx: &mut Context<Self>,
12523 ) {
12524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12525 cx.propagate();
12526 return;
12527 }
12528
12529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12530 s.move_with(|map, selection| {
12531 selection.collapse_to(
12532 movement::start_of_excerpt(
12533 map,
12534 selection.head(),
12535 workspace::searchable::Direction::Next,
12536 ),
12537 SelectionGoal::None,
12538 )
12539 });
12540 })
12541 }
12542
12543 pub fn move_to_end_of_excerpt(
12544 &mut self,
12545 _: &MoveToEndOfExcerpt,
12546 window: &mut Window,
12547 cx: &mut Context<Self>,
12548 ) {
12549 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12550 cx.propagate();
12551 return;
12552 }
12553 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12554 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12555 s.move_with(|map, selection| {
12556 selection.collapse_to(
12557 movement::end_of_excerpt(
12558 map,
12559 selection.head(),
12560 workspace::searchable::Direction::Next,
12561 ),
12562 SelectionGoal::None,
12563 )
12564 });
12565 })
12566 }
12567
12568 pub fn move_to_end_of_previous_excerpt(
12569 &mut self,
12570 _: &MoveToEndOfPreviousExcerpt,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12575 cx.propagate();
12576 return;
12577 }
12578 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12580 s.move_with(|map, selection| {
12581 selection.collapse_to(
12582 movement::end_of_excerpt(
12583 map,
12584 selection.head(),
12585 workspace::searchable::Direction::Prev,
12586 ),
12587 SelectionGoal::None,
12588 )
12589 });
12590 })
12591 }
12592
12593 pub fn select_to_start_of_excerpt(
12594 &mut self,
12595 _: &SelectToStartOfExcerpt,
12596 window: &mut Window,
12597 cx: &mut Context<Self>,
12598 ) {
12599 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12600 cx.propagate();
12601 return;
12602 }
12603 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12605 s.move_heads_with(|map, head, _| {
12606 (
12607 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12608 SelectionGoal::None,
12609 )
12610 });
12611 })
12612 }
12613
12614 pub fn select_to_start_of_next_excerpt(
12615 &mut self,
12616 _: &SelectToStartOfNextExcerpt,
12617 window: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12621 cx.propagate();
12622 return;
12623 }
12624 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12626 s.move_heads_with(|map, head, _| {
12627 (
12628 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12629 SelectionGoal::None,
12630 )
12631 });
12632 })
12633 }
12634
12635 pub fn select_to_end_of_excerpt(
12636 &mut self,
12637 _: &SelectToEndOfExcerpt,
12638 window: &mut Window,
12639 cx: &mut Context<Self>,
12640 ) {
12641 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12642 cx.propagate();
12643 return;
12644 }
12645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12647 s.move_heads_with(|map, head, _| {
12648 (
12649 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12650 SelectionGoal::None,
12651 )
12652 });
12653 })
12654 }
12655
12656 pub fn select_to_end_of_previous_excerpt(
12657 &mut self,
12658 _: &SelectToEndOfPreviousExcerpt,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12663 cx.propagate();
12664 return;
12665 }
12666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12668 s.move_heads_with(|map, head, _| {
12669 (
12670 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12671 SelectionGoal::None,
12672 )
12673 });
12674 })
12675 }
12676
12677 pub fn move_to_beginning(
12678 &mut self,
12679 _: &MoveToBeginning,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12684 cx.propagate();
12685 return;
12686 }
12687 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12688 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12689 s.select_ranges(vec![0..0]);
12690 });
12691 }
12692
12693 pub fn select_to_beginning(
12694 &mut self,
12695 _: &SelectToBeginning,
12696 window: &mut Window,
12697 cx: &mut Context<Self>,
12698 ) {
12699 let mut selection = self.selections.last::<Point>(cx);
12700 selection.set_head(Point::zero(), SelectionGoal::None);
12701 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12702 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12703 s.select(vec![selection]);
12704 });
12705 }
12706
12707 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12708 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12709 cx.propagate();
12710 return;
12711 }
12712 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12713 let cursor = self.buffer.read(cx).read(cx).len();
12714 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12715 s.select_ranges(vec![cursor..cursor])
12716 });
12717 }
12718
12719 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12720 self.nav_history = nav_history;
12721 }
12722
12723 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12724 self.nav_history.as_ref()
12725 }
12726
12727 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12728 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12729 }
12730
12731 fn push_to_nav_history(
12732 &mut self,
12733 cursor_anchor: Anchor,
12734 new_position: Option<Point>,
12735 is_deactivate: bool,
12736 cx: &mut Context<Self>,
12737 ) {
12738 if let Some(nav_history) = self.nav_history.as_mut() {
12739 let buffer = self.buffer.read(cx).read(cx);
12740 let cursor_position = cursor_anchor.to_point(&buffer);
12741 let scroll_state = self.scroll_manager.anchor();
12742 let scroll_top_row = scroll_state.top_row(&buffer);
12743 drop(buffer);
12744
12745 if let Some(new_position) = new_position {
12746 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12747 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12748 return;
12749 }
12750 }
12751
12752 nav_history.push(
12753 Some(NavigationData {
12754 cursor_anchor,
12755 cursor_position,
12756 scroll_anchor: scroll_state,
12757 scroll_top_row,
12758 }),
12759 cx,
12760 );
12761 cx.emit(EditorEvent::PushedToNavHistory {
12762 anchor: cursor_anchor,
12763 is_deactivate,
12764 })
12765 }
12766 }
12767
12768 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12769 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12770 let buffer = self.buffer.read(cx).snapshot(cx);
12771 let mut selection = self.selections.first::<usize>(cx);
12772 selection.set_head(buffer.len(), SelectionGoal::None);
12773 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12774 s.select(vec![selection]);
12775 });
12776 }
12777
12778 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12780 let end = self.buffer.read(cx).read(cx).len();
12781 self.change_selections(None, window, cx, |s| {
12782 s.select_ranges(vec![0..end]);
12783 });
12784 }
12785
12786 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12789 let mut selections = self.selections.all::<Point>(cx);
12790 let max_point = display_map.buffer_snapshot.max_point();
12791 for selection in &mut selections {
12792 let rows = selection.spanned_rows(true, &display_map);
12793 selection.start = Point::new(rows.start.0, 0);
12794 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12795 selection.reversed = false;
12796 }
12797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12798 s.select(selections);
12799 });
12800 }
12801
12802 pub fn split_selection_into_lines(
12803 &mut self,
12804 _: &SplitSelectionIntoLines,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 let selections = self
12809 .selections
12810 .all::<Point>(cx)
12811 .into_iter()
12812 .map(|selection| selection.start..selection.end)
12813 .collect::<Vec<_>>();
12814 self.unfold_ranges(&selections, true, true, cx);
12815
12816 let mut new_selection_ranges = Vec::new();
12817 {
12818 let buffer = self.buffer.read(cx).read(cx);
12819 for selection in selections {
12820 for row in selection.start.row..selection.end.row {
12821 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12822 new_selection_ranges.push(cursor..cursor);
12823 }
12824
12825 let is_multiline_selection = selection.start.row != selection.end.row;
12826 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12827 // so this action feels more ergonomic when paired with other selection operations
12828 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12829 if !should_skip_last {
12830 new_selection_ranges.push(selection.end..selection.end);
12831 }
12832 }
12833 }
12834 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12835 s.select_ranges(new_selection_ranges);
12836 });
12837 }
12838
12839 pub fn add_selection_above(
12840 &mut self,
12841 _: &AddSelectionAbove,
12842 window: &mut Window,
12843 cx: &mut Context<Self>,
12844 ) {
12845 self.add_selection(true, window, cx);
12846 }
12847
12848 pub fn add_selection_below(
12849 &mut self,
12850 _: &AddSelectionBelow,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 self.add_selection(false, window, cx);
12855 }
12856
12857 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12858 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12859
12860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12861 let all_selections = self.selections.all::<Point>(cx);
12862 let text_layout_details = self.text_layout_details(window);
12863
12864 let (mut columnar_selections, new_selections_to_columnarize) = {
12865 if let Some(state) = self.add_selections_state.as_ref() {
12866 let columnar_selection_ids: HashSet<_> = state
12867 .groups
12868 .iter()
12869 .flat_map(|group| group.stack.iter())
12870 .copied()
12871 .collect();
12872
12873 all_selections
12874 .into_iter()
12875 .partition(|s| columnar_selection_ids.contains(&s.id))
12876 } else {
12877 (Vec::new(), all_selections)
12878 }
12879 };
12880
12881 let mut state = self
12882 .add_selections_state
12883 .take()
12884 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12885
12886 for selection in new_selections_to_columnarize {
12887 let range = selection.display_range(&display_map).sorted();
12888 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12889 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12890 let positions = start_x.min(end_x)..start_x.max(end_x);
12891 let mut stack = Vec::new();
12892 for row in range.start.row().0..=range.end.row().0 {
12893 if let Some(selection) = self.selections.build_columnar_selection(
12894 &display_map,
12895 DisplayRow(row),
12896 &positions,
12897 selection.reversed,
12898 &text_layout_details,
12899 ) {
12900 stack.push(selection.id);
12901 columnar_selections.push(selection);
12902 }
12903 }
12904 if !stack.is_empty() {
12905 if above {
12906 stack.reverse();
12907 }
12908 state.groups.push(AddSelectionsGroup { above, stack });
12909 }
12910 }
12911
12912 let mut final_selections = Vec::new();
12913 let end_row = if above {
12914 DisplayRow(0)
12915 } else {
12916 display_map.max_point().row()
12917 };
12918
12919 let mut last_added_item_per_group = HashMap::default();
12920 for group in state.groups.iter_mut() {
12921 if let Some(last_id) = group.stack.last() {
12922 last_added_item_per_group.insert(*last_id, group);
12923 }
12924 }
12925
12926 for selection in columnar_selections {
12927 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12928 if above == group.above {
12929 let range = selection.display_range(&display_map).sorted();
12930 debug_assert_eq!(range.start.row(), range.end.row());
12931 let mut row = range.start.row();
12932 let positions =
12933 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12934 px(start)..px(end)
12935 } else {
12936 let start_x =
12937 display_map.x_for_display_point(range.start, &text_layout_details);
12938 let end_x =
12939 display_map.x_for_display_point(range.end, &text_layout_details);
12940 start_x.min(end_x)..start_x.max(end_x)
12941 };
12942
12943 let mut maybe_new_selection = None;
12944 while row != end_row {
12945 if above {
12946 row.0 -= 1;
12947 } else {
12948 row.0 += 1;
12949 }
12950 if let Some(new_selection) = self.selections.build_columnar_selection(
12951 &display_map,
12952 row,
12953 &positions,
12954 selection.reversed,
12955 &text_layout_details,
12956 ) {
12957 maybe_new_selection = Some(new_selection);
12958 break;
12959 }
12960 }
12961
12962 if let Some(new_selection) = maybe_new_selection {
12963 group.stack.push(new_selection.id);
12964 if above {
12965 final_selections.push(new_selection);
12966 final_selections.push(selection);
12967 } else {
12968 final_selections.push(selection);
12969 final_selections.push(new_selection);
12970 }
12971 } else {
12972 final_selections.push(selection);
12973 }
12974 } else {
12975 group.stack.pop();
12976 }
12977 } else {
12978 final_selections.push(selection);
12979 }
12980 }
12981
12982 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12983 s.select(final_selections);
12984 });
12985
12986 let final_selection_ids: HashSet<_> = self
12987 .selections
12988 .all::<Point>(cx)
12989 .iter()
12990 .map(|s| s.id)
12991 .collect();
12992 state.groups.retain_mut(|group| {
12993 // selections might get merged above so we remove invalid items from stacks
12994 group.stack.retain(|id| final_selection_ids.contains(id));
12995
12996 // single selection in stack can be treated as initial state
12997 group.stack.len() > 1
12998 });
12999
13000 if !state.groups.is_empty() {
13001 self.add_selections_state = Some(state);
13002 }
13003 }
13004
13005 fn select_match_ranges(
13006 &mut self,
13007 range: Range<usize>,
13008 reversed: bool,
13009 replace_newest: bool,
13010 auto_scroll: Option<Autoscroll>,
13011 window: &mut Window,
13012 cx: &mut Context<Editor>,
13013 ) {
13014 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13015 self.change_selections(auto_scroll, window, cx, |s| {
13016 if replace_newest {
13017 s.delete(s.newest_anchor().id);
13018 }
13019 if reversed {
13020 s.insert_range(range.end..range.start);
13021 } else {
13022 s.insert_range(range);
13023 }
13024 });
13025 }
13026
13027 pub fn select_next_match_internal(
13028 &mut self,
13029 display_map: &DisplaySnapshot,
13030 replace_newest: bool,
13031 autoscroll: Option<Autoscroll>,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) -> Result<()> {
13035 let buffer = &display_map.buffer_snapshot;
13036 let mut selections = self.selections.all::<usize>(cx);
13037 if let Some(mut select_next_state) = self.select_next_state.take() {
13038 let query = &select_next_state.query;
13039 if !select_next_state.done {
13040 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13041 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13042 let mut next_selected_range = None;
13043
13044 let bytes_after_last_selection =
13045 buffer.bytes_in_range(last_selection.end..buffer.len());
13046 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13047 let query_matches = query
13048 .stream_find_iter(bytes_after_last_selection)
13049 .map(|result| (last_selection.end, result))
13050 .chain(
13051 query
13052 .stream_find_iter(bytes_before_first_selection)
13053 .map(|result| (0, result)),
13054 );
13055
13056 for (start_offset, query_match) in query_matches {
13057 let query_match = query_match.unwrap(); // can only fail due to I/O
13058 let offset_range =
13059 start_offset + query_match.start()..start_offset + query_match.end();
13060 let display_range = offset_range.start.to_display_point(display_map)
13061 ..offset_range.end.to_display_point(display_map);
13062
13063 if !select_next_state.wordwise
13064 || (!movement::is_inside_word(display_map, display_range.start)
13065 && !movement::is_inside_word(display_map, display_range.end))
13066 {
13067 // TODO: This is n^2, because we might check all the selections
13068 if !selections
13069 .iter()
13070 .any(|selection| selection.range().overlaps(&offset_range))
13071 {
13072 next_selected_range = Some(offset_range);
13073 break;
13074 }
13075 }
13076 }
13077
13078 if let Some(next_selected_range) = next_selected_range {
13079 self.select_match_ranges(
13080 next_selected_range,
13081 last_selection.reversed,
13082 replace_newest,
13083 autoscroll,
13084 window,
13085 cx,
13086 );
13087 } else {
13088 select_next_state.done = true;
13089 }
13090 }
13091
13092 self.select_next_state = Some(select_next_state);
13093 } else {
13094 let mut only_carets = true;
13095 let mut same_text_selected = true;
13096 let mut selected_text = None;
13097
13098 let mut selections_iter = selections.iter().peekable();
13099 while let Some(selection) = selections_iter.next() {
13100 if selection.start != selection.end {
13101 only_carets = false;
13102 }
13103
13104 if same_text_selected {
13105 if selected_text.is_none() {
13106 selected_text =
13107 Some(buffer.text_for_range(selection.range()).collect::<String>());
13108 }
13109
13110 if let Some(next_selection) = selections_iter.peek() {
13111 if next_selection.range().len() == selection.range().len() {
13112 let next_selected_text = buffer
13113 .text_for_range(next_selection.range())
13114 .collect::<String>();
13115 if Some(next_selected_text) != selected_text {
13116 same_text_selected = false;
13117 selected_text = None;
13118 }
13119 } else {
13120 same_text_selected = false;
13121 selected_text = None;
13122 }
13123 }
13124 }
13125 }
13126
13127 if only_carets {
13128 for selection in &mut selections {
13129 let word_range = movement::surrounding_word(
13130 display_map,
13131 selection.start.to_display_point(display_map),
13132 );
13133 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13134 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13135 selection.goal = SelectionGoal::None;
13136 selection.reversed = false;
13137 self.select_match_ranges(
13138 selection.start..selection.end,
13139 selection.reversed,
13140 replace_newest,
13141 autoscroll,
13142 window,
13143 cx,
13144 );
13145 }
13146
13147 if selections.len() == 1 {
13148 let selection = selections
13149 .last()
13150 .expect("ensured that there's only one selection");
13151 let query = buffer
13152 .text_for_range(selection.start..selection.end)
13153 .collect::<String>();
13154 let is_empty = query.is_empty();
13155 let select_state = SelectNextState {
13156 query: AhoCorasick::new(&[query])?,
13157 wordwise: true,
13158 done: is_empty,
13159 };
13160 self.select_next_state = Some(select_state);
13161 } else {
13162 self.select_next_state = None;
13163 }
13164 } else if let Some(selected_text) = selected_text {
13165 self.select_next_state = Some(SelectNextState {
13166 query: AhoCorasick::new(&[selected_text])?,
13167 wordwise: false,
13168 done: false,
13169 });
13170 self.select_next_match_internal(
13171 display_map,
13172 replace_newest,
13173 autoscroll,
13174 window,
13175 cx,
13176 )?;
13177 }
13178 }
13179 Ok(())
13180 }
13181
13182 pub fn select_all_matches(
13183 &mut self,
13184 _action: &SelectAllMatches,
13185 window: &mut Window,
13186 cx: &mut Context<Self>,
13187 ) -> Result<()> {
13188 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13189
13190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13191
13192 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13193 let Some(select_next_state) = self.select_next_state.as_mut() else {
13194 return Ok(());
13195 };
13196 if select_next_state.done {
13197 return Ok(());
13198 }
13199
13200 let mut new_selections = Vec::new();
13201
13202 let reversed = self.selections.oldest::<usize>(cx).reversed;
13203 let buffer = &display_map.buffer_snapshot;
13204 let query_matches = select_next_state
13205 .query
13206 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13207
13208 for query_match in query_matches.into_iter() {
13209 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13210 let offset_range = if reversed {
13211 query_match.end()..query_match.start()
13212 } else {
13213 query_match.start()..query_match.end()
13214 };
13215 let display_range = offset_range.start.to_display_point(&display_map)
13216 ..offset_range.end.to_display_point(&display_map);
13217
13218 if !select_next_state.wordwise
13219 || (!movement::is_inside_word(&display_map, display_range.start)
13220 && !movement::is_inside_word(&display_map, display_range.end))
13221 {
13222 new_selections.push(offset_range.start..offset_range.end);
13223 }
13224 }
13225
13226 select_next_state.done = true;
13227 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13228 self.change_selections(None, window, cx, |selections| {
13229 selections.select_ranges(new_selections)
13230 });
13231
13232 Ok(())
13233 }
13234
13235 pub fn select_next(
13236 &mut self,
13237 action: &SelectNext,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) -> Result<()> {
13241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13243 self.select_next_match_internal(
13244 &display_map,
13245 action.replace_newest,
13246 Some(Autoscroll::newest()),
13247 window,
13248 cx,
13249 )?;
13250 Ok(())
13251 }
13252
13253 pub fn select_previous(
13254 &mut self,
13255 action: &SelectPrevious,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) -> Result<()> {
13259 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13261 let buffer = &display_map.buffer_snapshot;
13262 let mut selections = self.selections.all::<usize>(cx);
13263 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13264 let query = &select_prev_state.query;
13265 if !select_prev_state.done {
13266 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13267 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13268 let mut next_selected_range = None;
13269 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13270 let bytes_before_last_selection =
13271 buffer.reversed_bytes_in_range(0..last_selection.start);
13272 let bytes_after_first_selection =
13273 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13274 let query_matches = query
13275 .stream_find_iter(bytes_before_last_selection)
13276 .map(|result| (last_selection.start, result))
13277 .chain(
13278 query
13279 .stream_find_iter(bytes_after_first_selection)
13280 .map(|result| (buffer.len(), result)),
13281 );
13282 for (end_offset, query_match) in query_matches {
13283 let query_match = query_match.unwrap(); // can only fail due to I/O
13284 let offset_range =
13285 end_offset - query_match.end()..end_offset - query_match.start();
13286 let display_range = offset_range.start.to_display_point(&display_map)
13287 ..offset_range.end.to_display_point(&display_map);
13288
13289 if !select_prev_state.wordwise
13290 || (!movement::is_inside_word(&display_map, display_range.start)
13291 && !movement::is_inside_word(&display_map, display_range.end))
13292 {
13293 next_selected_range = Some(offset_range);
13294 break;
13295 }
13296 }
13297
13298 if let Some(next_selected_range) = next_selected_range {
13299 self.select_match_ranges(
13300 next_selected_range,
13301 last_selection.reversed,
13302 action.replace_newest,
13303 Some(Autoscroll::newest()),
13304 window,
13305 cx,
13306 );
13307 } else {
13308 select_prev_state.done = true;
13309 }
13310 }
13311
13312 self.select_prev_state = Some(select_prev_state);
13313 } else {
13314 let mut only_carets = true;
13315 let mut same_text_selected = true;
13316 let mut selected_text = None;
13317
13318 let mut selections_iter = selections.iter().peekable();
13319 while let Some(selection) = selections_iter.next() {
13320 if selection.start != selection.end {
13321 only_carets = false;
13322 }
13323
13324 if same_text_selected {
13325 if selected_text.is_none() {
13326 selected_text =
13327 Some(buffer.text_for_range(selection.range()).collect::<String>());
13328 }
13329
13330 if let Some(next_selection) = selections_iter.peek() {
13331 if next_selection.range().len() == selection.range().len() {
13332 let next_selected_text = buffer
13333 .text_for_range(next_selection.range())
13334 .collect::<String>();
13335 if Some(next_selected_text) != selected_text {
13336 same_text_selected = false;
13337 selected_text = None;
13338 }
13339 } else {
13340 same_text_selected = false;
13341 selected_text = None;
13342 }
13343 }
13344 }
13345 }
13346
13347 if only_carets {
13348 for selection in &mut selections {
13349 let word_range = movement::surrounding_word(
13350 &display_map,
13351 selection.start.to_display_point(&display_map),
13352 );
13353 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13354 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13355 selection.goal = SelectionGoal::None;
13356 selection.reversed = false;
13357 self.select_match_ranges(
13358 selection.start..selection.end,
13359 selection.reversed,
13360 action.replace_newest,
13361 Some(Autoscroll::newest()),
13362 window,
13363 cx,
13364 );
13365 }
13366 if selections.len() == 1 {
13367 let selection = selections
13368 .last()
13369 .expect("ensured that there's only one selection");
13370 let query = buffer
13371 .text_for_range(selection.start..selection.end)
13372 .collect::<String>();
13373 let is_empty = query.is_empty();
13374 let select_state = SelectNextState {
13375 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13376 wordwise: true,
13377 done: is_empty,
13378 };
13379 self.select_prev_state = Some(select_state);
13380 } else {
13381 self.select_prev_state = None;
13382 }
13383 } else if let Some(selected_text) = selected_text {
13384 self.select_prev_state = Some(SelectNextState {
13385 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13386 wordwise: false,
13387 done: false,
13388 });
13389 self.select_previous(action, window, cx)?;
13390 }
13391 }
13392 Ok(())
13393 }
13394
13395 pub fn find_next_match(
13396 &mut self,
13397 _: &FindNextMatch,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) -> Result<()> {
13401 let selections = self.selections.disjoint_anchors();
13402 match selections.first() {
13403 Some(first) if selections.len() >= 2 => {
13404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13405 s.select_ranges([first.range()]);
13406 });
13407 }
13408 _ => self.select_next(
13409 &SelectNext {
13410 replace_newest: true,
13411 },
13412 window,
13413 cx,
13414 )?,
13415 }
13416 Ok(())
13417 }
13418
13419 pub fn find_previous_match(
13420 &mut self,
13421 _: &FindPreviousMatch,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) -> Result<()> {
13425 let selections = self.selections.disjoint_anchors();
13426 match selections.last() {
13427 Some(last) if selections.len() >= 2 => {
13428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13429 s.select_ranges([last.range()]);
13430 });
13431 }
13432 _ => self.select_previous(
13433 &SelectPrevious {
13434 replace_newest: true,
13435 },
13436 window,
13437 cx,
13438 )?,
13439 }
13440 Ok(())
13441 }
13442
13443 pub fn toggle_comments(
13444 &mut self,
13445 action: &ToggleComments,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 if self.read_only(cx) {
13450 return;
13451 }
13452 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13453 let text_layout_details = &self.text_layout_details(window);
13454 self.transact(window, cx, |this, window, cx| {
13455 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13456 let mut edits = Vec::new();
13457 let mut selection_edit_ranges = Vec::new();
13458 let mut last_toggled_row = None;
13459 let snapshot = this.buffer.read(cx).read(cx);
13460 let empty_str: Arc<str> = Arc::default();
13461 let mut suffixes_inserted = Vec::new();
13462 let ignore_indent = action.ignore_indent;
13463
13464 fn comment_prefix_range(
13465 snapshot: &MultiBufferSnapshot,
13466 row: MultiBufferRow,
13467 comment_prefix: &str,
13468 comment_prefix_whitespace: &str,
13469 ignore_indent: bool,
13470 ) -> Range<Point> {
13471 let indent_size = if ignore_indent {
13472 0
13473 } else {
13474 snapshot.indent_size_for_line(row).len
13475 };
13476
13477 let start = Point::new(row.0, indent_size);
13478
13479 let mut line_bytes = snapshot
13480 .bytes_in_range(start..snapshot.max_point())
13481 .flatten()
13482 .copied();
13483
13484 // If this line currently begins with the line comment prefix, then record
13485 // the range containing the prefix.
13486 if line_bytes
13487 .by_ref()
13488 .take(comment_prefix.len())
13489 .eq(comment_prefix.bytes())
13490 {
13491 // Include any whitespace that matches the comment prefix.
13492 let matching_whitespace_len = line_bytes
13493 .zip(comment_prefix_whitespace.bytes())
13494 .take_while(|(a, b)| a == b)
13495 .count() as u32;
13496 let end = Point::new(
13497 start.row,
13498 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13499 );
13500 start..end
13501 } else {
13502 start..start
13503 }
13504 }
13505
13506 fn comment_suffix_range(
13507 snapshot: &MultiBufferSnapshot,
13508 row: MultiBufferRow,
13509 comment_suffix: &str,
13510 comment_suffix_has_leading_space: bool,
13511 ) -> Range<Point> {
13512 let end = Point::new(row.0, snapshot.line_len(row));
13513 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13514
13515 let mut line_end_bytes = snapshot
13516 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13517 .flatten()
13518 .copied();
13519
13520 let leading_space_len = if suffix_start_column > 0
13521 && line_end_bytes.next() == Some(b' ')
13522 && comment_suffix_has_leading_space
13523 {
13524 1
13525 } else {
13526 0
13527 };
13528
13529 // If this line currently begins with the line comment prefix, then record
13530 // the range containing the prefix.
13531 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13532 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13533 start..end
13534 } else {
13535 end..end
13536 }
13537 }
13538
13539 // TODO: Handle selections that cross excerpts
13540 for selection in &mut selections {
13541 let start_column = snapshot
13542 .indent_size_for_line(MultiBufferRow(selection.start.row))
13543 .len;
13544 let language = if let Some(language) =
13545 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13546 {
13547 language
13548 } else {
13549 continue;
13550 };
13551
13552 selection_edit_ranges.clear();
13553
13554 // If multiple selections contain a given row, avoid processing that
13555 // row more than once.
13556 let mut start_row = MultiBufferRow(selection.start.row);
13557 if last_toggled_row == Some(start_row) {
13558 start_row = start_row.next_row();
13559 }
13560 let end_row =
13561 if selection.end.row > selection.start.row && selection.end.column == 0 {
13562 MultiBufferRow(selection.end.row - 1)
13563 } else {
13564 MultiBufferRow(selection.end.row)
13565 };
13566 last_toggled_row = Some(end_row);
13567
13568 if start_row > end_row {
13569 continue;
13570 }
13571
13572 // If the language has line comments, toggle those.
13573 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13574
13575 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13576 if ignore_indent {
13577 full_comment_prefixes = full_comment_prefixes
13578 .into_iter()
13579 .map(|s| Arc::from(s.trim_end()))
13580 .collect();
13581 }
13582
13583 if !full_comment_prefixes.is_empty() {
13584 let first_prefix = full_comment_prefixes
13585 .first()
13586 .expect("prefixes is non-empty");
13587 let prefix_trimmed_lengths = full_comment_prefixes
13588 .iter()
13589 .map(|p| p.trim_end_matches(' ').len())
13590 .collect::<SmallVec<[usize; 4]>>();
13591
13592 let mut all_selection_lines_are_comments = true;
13593
13594 for row in start_row.0..=end_row.0 {
13595 let row = MultiBufferRow(row);
13596 if start_row < end_row && snapshot.is_line_blank(row) {
13597 continue;
13598 }
13599
13600 let prefix_range = full_comment_prefixes
13601 .iter()
13602 .zip(prefix_trimmed_lengths.iter().copied())
13603 .map(|(prefix, trimmed_prefix_len)| {
13604 comment_prefix_range(
13605 snapshot.deref(),
13606 row,
13607 &prefix[..trimmed_prefix_len],
13608 &prefix[trimmed_prefix_len..],
13609 ignore_indent,
13610 )
13611 })
13612 .max_by_key(|range| range.end.column - range.start.column)
13613 .expect("prefixes is non-empty");
13614
13615 if prefix_range.is_empty() {
13616 all_selection_lines_are_comments = false;
13617 }
13618
13619 selection_edit_ranges.push(prefix_range);
13620 }
13621
13622 if all_selection_lines_are_comments {
13623 edits.extend(
13624 selection_edit_ranges
13625 .iter()
13626 .cloned()
13627 .map(|range| (range, empty_str.clone())),
13628 );
13629 } else {
13630 let min_column = selection_edit_ranges
13631 .iter()
13632 .map(|range| range.start.column)
13633 .min()
13634 .unwrap_or(0);
13635 edits.extend(selection_edit_ranges.iter().map(|range| {
13636 let position = Point::new(range.start.row, min_column);
13637 (position..position, first_prefix.clone())
13638 }));
13639 }
13640 } else if let Some((full_comment_prefix, comment_suffix)) =
13641 language.block_comment_delimiters()
13642 {
13643 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13644 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13645 let prefix_range = comment_prefix_range(
13646 snapshot.deref(),
13647 start_row,
13648 comment_prefix,
13649 comment_prefix_whitespace,
13650 ignore_indent,
13651 );
13652 let suffix_range = comment_suffix_range(
13653 snapshot.deref(),
13654 end_row,
13655 comment_suffix.trim_start_matches(' '),
13656 comment_suffix.starts_with(' '),
13657 );
13658
13659 if prefix_range.is_empty() || suffix_range.is_empty() {
13660 edits.push((
13661 prefix_range.start..prefix_range.start,
13662 full_comment_prefix.clone(),
13663 ));
13664 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13665 suffixes_inserted.push((end_row, comment_suffix.len()));
13666 } else {
13667 edits.push((prefix_range, empty_str.clone()));
13668 edits.push((suffix_range, empty_str.clone()));
13669 }
13670 } else {
13671 continue;
13672 }
13673 }
13674
13675 drop(snapshot);
13676 this.buffer.update(cx, |buffer, cx| {
13677 buffer.edit(edits, None, cx);
13678 });
13679
13680 // Adjust selections so that they end before any comment suffixes that
13681 // were inserted.
13682 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13683 let mut selections = this.selections.all::<Point>(cx);
13684 let snapshot = this.buffer.read(cx).read(cx);
13685 for selection in &mut selections {
13686 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13687 match row.cmp(&MultiBufferRow(selection.end.row)) {
13688 Ordering::Less => {
13689 suffixes_inserted.next();
13690 continue;
13691 }
13692 Ordering::Greater => break,
13693 Ordering::Equal => {
13694 if selection.end.column == snapshot.line_len(row) {
13695 if selection.is_empty() {
13696 selection.start.column -= suffix_len as u32;
13697 }
13698 selection.end.column -= suffix_len as u32;
13699 }
13700 break;
13701 }
13702 }
13703 }
13704 }
13705
13706 drop(snapshot);
13707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13708 s.select(selections)
13709 });
13710
13711 let selections = this.selections.all::<Point>(cx);
13712 let selections_on_single_row = selections.windows(2).all(|selections| {
13713 selections[0].start.row == selections[1].start.row
13714 && selections[0].end.row == selections[1].end.row
13715 && selections[0].start.row == selections[0].end.row
13716 });
13717 let selections_selecting = selections
13718 .iter()
13719 .any(|selection| selection.start != selection.end);
13720 let advance_downwards = action.advance_downwards
13721 && selections_on_single_row
13722 && !selections_selecting
13723 && !matches!(this.mode, EditorMode::SingleLine { .. });
13724
13725 if advance_downwards {
13726 let snapshot = this.buffer.read(cx).snapshot(cx);
13727
13728 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13729 s.move_cursors_with(|display_snapshot, display_point, _| {
13730 let mut point = display_point.to_point(display_snapshot);
13731 point.row += 1;
13732 point = snapshot.clip_point(point, Bias::Left);
13733 let display_point = point.to_display_point(display_snapshot);
13734 let goal = SelectionGoal::HorizontalPosition(
13735 display_snapshot
13736 .x_for_display_point(display_point, text_layout_details)
13737 .into(),
13738 );
13739 (display_point, goal)
13740 })
13741 });
13742 }
13743 });
13744 }
13745
13746 pub fn select_enclosing_symbol(
13747 &mut self,
13748 _: &SelectEnclosingSymbol,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13753
13754 let buffer = self.buffer.read(cx).snapshot(cx);
13755 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13756
13757 fn update_selection(
13758 selection: &Selection<usize>,
13759 buffer_snap: &MultiBufferSnapshot,
13760 ) -> Option<Selection<usize>> {
13761 let cursor = selection.head();
13762 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13763 for symbol in symbols.iter().rev() {
13764 let start = symbol.range.start.to_offset(buffer_snap);
13765 let end = symbol.range.end.to_offset(buffer_snap);
13766 let new_range = start..end;
13767 if start < selection.start || end > selection.end {
13768 return Some(Selection {
13769 id: selection.id,
13770 start: new_range.start,
13771 end: new_range.end,
13772 goal: SelectionGoal::None,
13773 reversed: selection.reversed,
13774 });
13775 }
13776 }
13777 None
13778 }
13779
13780 let mut selected_larger_symbol = false;
13781 let new_selections = old_selections
13782 .iter()
13783 .map(|selection| match update_selection(selection, &buffer) {
13784 Some(new_selection) => {
13785 if new_selection.range() != selection.range() {
13786 selected_larger_symbol = true;
13787 }
13788 new_selection
13789 }
13790 None => selection.clone(),
13791 })
13792 .collect::<Vec<_>>();
13793
13794 if selected_larger_symbol {
13795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13796 s.select(new_selections);
13797 });
13798 }
13799 }
13800
13801 pub fn select_larger_syntax_node(
13802 &mut self,
13803 _: &SelectLargerSyntaxNode,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 let Some(visible_row_count) = self.visible_row_count() else {
13808 return;
13809 };
13810 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13811 if old_selections.is_empty() {
13812 return;
13813 }
13814
13815 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13816
13817 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13818 let buffer = self.buffer.read(cx).snapshot(cx);
13819
13820 let mut selected_larger_node = false;
13821 let mut new_selections = old_selections
13822 .iter()
13823 .map(|selection| {
13824 let old_range = selection.start..selection.end;
13825
13826 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13827 // manually select word at selection
13828 if ["string_content", "inline"].contains(&node.kind()) {
13829 let word_range = {
13830 let display_point = buffer
13831 .offset_to_point(old_range.start)
13832 .to_display_point(&display_map);
13833 let Range { start, end } =
13834 movement::surrounding_word(&display_map, display_point);
13835 start.to_point(&display_map).to_offset(&buffer)
13836 ..end.to_point(&display_map).to_offset(&buffer)
13837 };
13838 // ignore if word is already selected
13839 if !word_range.is_empty() && old_range != word_range {
13840 let last_word_range = {
13841 let display_point = buffer
13842 .offset_to_point(old_range.end)
13843 .to_display_point(&display_map);
13844 let Range { start, end } =
13845 movement::surrounding_word(&display_map, display_point);
13846 start.to_point(&display_map).to_offset(&buffer)
13847 ..end.to_point(&display_map).to_offset(&buffer)
13848 };
13849 // only select word if start and end point belongs to same word
13850 if word_range == last_word_range {
13851 selected_larger_node = true;
13852 return Selection {
13853 id: selection.id,
13854 start: word_range.start,
13855 end: word_range.end,
13856 goal: SelectionGoal::None,
13857 reversed: selection.reversed,
13858 };
13859 }
13860 }
13861 }
13862 }
13863
13864 let mut new_range = old_range.clone();
13865 while let Some((_node, containing_range)) =
13866 buffer.syntax_ancestor(new_range.clone())
13867 {
13868 new_range = match containing_range {
13869 MultiOrSingleBufferOffsetRange::Single(_) => break,
13870 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13871 };
13872 if !display_map.intersects_fold(new_range.start)
13873 && !display_map.intersects_fold(new_range.end)
13874 {
13875 break;
13876 }
13877 }
13878
13879 selected_larger_node |= new_range != old_range;
13880 Selection {
13881 id: selection.id,
13882 start: new_range.start,
13883 end: new_range.end,
13884 goal: SelectionGoal::None,
13885 reversed: selection.reversed,
13886 }
13887 })
13888 .collect::<Vec<_>>();
13889
13890 if !selected_larger_node {
13891 return; // don't put this call in the history
13892 }
13893
13894 // scroll based on transformation done to the last selection created by the user
13895 let (last_old, last_new) = old_selections
13896 .last()
13897 .zip(new_selections.last().cloned())
13898 .expect("old_selections isn't empty");
13899
13900 // revert selection
13901 let is_selection_reversed = {
13902 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13903 new_selections.last_mut().expect("checked above").reversed =
13904 should_newest_selection_be_reversed;
13905 should_newest_selection_be_reversed
13906 };
13907
13908 if selected_larger_node {
13909 self.select_syntax_node_history.disable_clearing = true;
13910 self.change_selections(None, window, cx, |s| {
13911 s.select(new_selections.clone());
13912 });
13913 self.select_syntax_node_history.disable_clearing = false;
13914 }
13915
13916 let start_row = last_new.start.to_display_point(&display_map).row().0;
13917 let end_row = last_new.end.to_display_point(&display_map).row().0;
13918 let selection_height = end_row - start_row + 1;
13919 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13920
13921 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13922 let scroll_behavior = if fits_on_the_screen {
13923 self.request_autoscroll(Autoscroll::fit(), cx);
13924 SelectSyntaxNodeScrollBehavior::FitSelection
13925 } else if is_selection_reversed {
13926 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13927 SelectSyntaxNodeScrollBehavior::CursorTop
13928 } else {
13929 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13930 SelectSyntaxNodeScrollBehavior::CursorBottom
13931 };
13932
13933 self.select_syntax_node_history.push((
13934 old_selections,
13935 scroll_behavior,
13936 is_selection_reversed,
13937 ));
13938 }
13939
13940 pub fn select_smaller_syntax_node(
13941 &mut self,
13942 _: &SelectSmallerSyntaxNode,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13947
13948 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13949 self.select_syntax_node_history.pop()
13950 {
13951 if let Some(selection) = selections.last_mut() {
13952 selection.reversed = is_selection_reversed;
13953 }
13954
13955 self.select_syntax_node_history.disable_clearing = true;
13956 self.change_selections(None, window, cx, |s| {
13957 s.select(selections.to_vec());
13958 });
13959 self.select_syntax_node_history.disable_clearing = false;
13960
13961 match scroll_behavior {
13962 SelectSyntaxNodeScrollBehavior::CursorTop => {
13963 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13964 }
13965 SelectSyntaxNodeScrollBehavior::FitSelection => {
13966 self.request_autoscroll(Autoscroll::fit(), cx);
13967 }
13968 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13969 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13970 }
13971 }
13972 }
13973 }
13974
13975 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13976 if !EditorSettings::get_global(cx).gutter.runnables {
13977 self.clear_tasks();
13978 return Task::ready(());
13979 }
13980 let project = self.project.as_ref().map(Entity::downgrade);
13981 let task_sources = self.lsp_task_sources(cx);
13982 let multi_buffer = self.buffer.downgrade();
13983 cx.spawn_in(window, async move |editor, cx| {
13984 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13985 let Some(project) = project.and_then(|p| p.upgrade()) else {
13986 return;
13987 };
13988 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13989 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13990 }) else {
13991 return;
13992 };
13993
13994 let hide_runnables = project
13995 .update(cx, |project, cx| {
13996 // Do not display any test indicators in non-dev server remote projects.
13997 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13998 })
13999 .unwrap_or(true);
14000 if hide_runnables {
14001 return;
14002 }
14003 let new_rows =
14004 cx.background_spawn({
14005 let snapshot = display_snapshot.clone();
14006 async move {
14007 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14008 }
14009 })
14010 .await;
14011 let Ok(lsp_tasks) =
14012 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14013 else {
14014 return;
14015 };
14016 let lsp_tasks = lsp_tasks.await;
14017
14018 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14019 lsp_tasks
14020 .into_iter()
14021 .flat_map(|(kind, tasks)| {
14022 tasks.into_iter().filter_map(move |(location, task)| {
14023 Some((kind.clone(), location?, task))
14024 })
14025 })
14026 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14027 let buffer = location.target.buffer;
14028 let buffer_snapshot = buffer.read(cx).snapshot();
14029 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14030 |(excerpt_id, snapshot, _)| {
14031 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14032 display_snapshot
14033 .buffer_snapshot
14034 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14035 } else {
14036 None
14037 }
14038 },
14039 );
14040 if let Some(offset) = offset {
14041 let task_buffer_range =
14042 location.target.range.to_point(&buffer_snapshot);
14043 let context_buffer_range =
14044 task_buffer_range.to_offset(&buffer_snapshot);
14045 let context_range = BufferOffset(context_buffer_range.start)
14046 ..BufferOffset(context_buffer_range.end);
14047
14048 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14049 .or_insert_with(|| RunnableTasks {
14050 templates: Vec::new(),
14051 offset,
14052 column: task_buffer_range.start.column,
14053 extra_variables: HashMap::default(),
14054 context_range,
14055 })
14056 .templates
14057 .push((kind, task.original_task().clone()));
14058 }
14059
14060 acc
14061 })
14062 }) else {
14063 return;
14064 };
14065
14066 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14067 buffer.language_settings(cx).tasks.prefer_lsp
14068 }) else {
14069 return;
14070 };
14071
14072 let rows = Self::runnable_rows(
14073 project,
14074 display_snapshot,
14075 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14076 new_rows,
14077 cx.clone(),
14078 )
14079 .await;
14080 editor
14081 .update(cx, |editor, _| {
14082 editor.clear_tasks();
14083 for (key, mut value) in rows {
14084 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14085 value.templates.extend(lsp_tasks.templates);
14086 }
14087
14088 editor.insert_tasks(key, value);
14089 }
14090 for (key, value) in lsp_tasks_by_rows {
14091 editor.insert_tasks(key, value);
14092 }
14093 })
14094 .ok();
14095 })
14096 }
14097 fn fetch_runnable_ranges(
14098 snapshot: &DisplaySnapshot,
14099 range: Range<Anchor>,
14100 ) -> Vec<language::RunnableRange> {
14101 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14102 }
14103
14104 fn runnable_rows(
14105 project: Entity<Project>,
14106 snapshot: DisplaySnapshot,
14107 prefer_lsp: bool,
14108 runnable_ranges: Vec<RunnableRange>,
14109 cx: AsyncWindowContext,
14110 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14111 cx.spawn(async move |cx| {
14112 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14113 for mut runnable in runnable_ranges {
14114 let Some(tasks) = cx
14115 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14116 .ok()
14117 else {
14118 continue;
14119 };
14120 let mut tasks = tasks.await;
14121
14122 if prefer_lsp {
14123 tasks.retain(|(task_kind, _)| {
14124 !matches!(task_kind, TaskSourceKind::Language { .. })
14125 });
14126 }
14127 if tasks.is_empty() {
14128 continue;
14129 }
14130
14131 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14132 let Some(row) = snapshot
14133 .buffer_snapshot
14134 .buffer_line_for_row(MultiBufferRow(point.row))
14135 .map(|(_, range)| range.start.row)
14136 else {
14137 continue;
14138 };
14139
14140 let context_range =
14141 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14142 runnable_rows.push((
14143 (runnable.buffer_id, row),
14144 RunnableTasks {
14145 templates: tasks,
14146 offset: snapshot
14147 .buffer_snapshot
14148 .anchor_before(runnable.run_range.start),
14149 context_range,
14150 column: point.column,
14151 extra_variables: runnable.extra_captures,
14152 },
14153 ));
14154 }
14155 runnable_rows
14156 })
14157 }
14158
14159 fn templates_with_tags(
14160 project: &Entity<Project>,
14161 runnable: &mut Runnable,
14162 cx: &mut App,
14163 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14164 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14165 let (worktree_id, file) = project
14166 .buffer_for_id(runnable.buffer, cx)
14167 .and_then(|buffer| buffer.read(cx).file())
14168 .map(|file| (file.worktree_id(cx), file.clone()))
14169 .unzip();
14170
14171 (
14172 project.task_store().read(cx).task_inventory().cloned(),
14173 worktree_id,
14174 file,
14175 )
14176 });
14177
14178 let tags = mem::take(&mut runnable.tags);
14179 let language = runnable.language.clone();
14180 cx.spawn(async move |cx| {
14181 let mut templates_with_tags = Vec::new();
14182 if let Some(inventory) = inventory {
14183 for RunnableTag(tag) in tags {
14184 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14185 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14186 }) else {
14187 return templates_with_tags;
14188 };
14189 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14190 move |(_, template)| {
14191 template.tags.iter().any(|source_tag| source_tag == &tag)
14192 },
14193 ));
14194 }
14195 }
14196 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14197
14198 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14199 // Strongest source wins; if we have worktree tag binding, prefer that to
14200 // global and language bindings;
14201 // if we have a global binding, prefer that to language binding.
14202 let first_mismatch = templates_with_tags
14203 .iter()
14204 .position(|(tag_source, _)| tag_source != leading_tag_source);
14205 if let Some(index) = first_mismatch {
14206 templates_with_tags.truncate(index);
14207 }
14208 }
14209
14210 templates_with_tags
14211 })
14212 }
14213
14214 pub fn move_to_enclosing_bracket(
14215 &mut self,
14216 _: &MoveToEnclosingBracket,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14221 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14222 s.move_offsets_with(|snapshot, selection| {
14223 let Some(enclosing_bracket_ranges) =
14224 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14225 else {
14226 return;
14227 };
14228
14229 let mut best_length = usize::MAX;
14230 let mut best_inside = false;
14231 let mut best_in_bracket_range = false;
14232 let mut best_destination = None;
14233 for (open, close) in enclosing_bracket_ranges {
14234 let close = close.to_inclusive();
14235 let length = close.end() - open.start;
14236 let inside = selection.start >= open.end && selection.end <= *close.start();
14237 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14238 || close.contains(&selection.head());
14239
14240 // If best is next to a bracket and current isn't, skip
14241 if !in_bracket_range && best_in_bracket_range {
14242 continue;
14243 }
14244
14245 // Prefer smaller lengths unless best is inside and current isn't
14246 if length > best_length && (best_inside || !inside) {
14247 continue;
14248 }
14249
14250 best_length = length;
14251 best_inside = inside;
14252 best_in_bracket_range = in_bracket_range;
14253 best_destination = Some(
14254 if close.contains(&selection.start) && close.contains(&selection.end) {
14255 if inside { open.end } else { open.start }
14256 } else if inside {
14257 *close.start()
14258 } else {
14259 *close.end()
14260 },
14261 );
14262 }
14263
14264 if let Some(destination) = best_destination {
14265 selection.collapse_to(destination, SelectionGoal::None);
14266 }
14267 })
14268 });
14269 }
14270
14271 pub fn undo_selection(
14272 &mut self,
14273 _: &UndoSelection,
14274 window: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14278 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14279 self.selection_history.mode = SelectionHistoryMode::Undoing;
14280 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14281 this.end_selection(window, cx);
14282 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14283 s.select_anchors(entry.selections.to_vec())
14284 });
14285 });
14286 self.selection_history.mode = SelectionHistoryMode::Normal;
14287
14288 self.select_next_state = entry.select_next_state;
14289 self.select_prev_state = entry.select_prev_state;
14290 self.add_selections_state = entry.add_selections_state;
14291 }
14292 }
14293
14294 pub fn redo_selection(
14295 &mut self,
14296 _: &RedoSelection,
14297 window: &mut Window,
14298 cx: &mut Context<Self>,
14299 ) {
14300 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14301 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14302 self.selection_history.mode = SelectionHistoryMode::Redoing;
14303 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14304 this.end_selection(window, cx);
14305 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14306 s.select_anchors(entry.selections.to_vec())
14307 });
14308 });
14309 self.selection_history.mode = SelectionHistoryMode::Normal;
14310
14311 self.select_next_state = entry.select_next_state;
14312 self.select_prev_state = entry.select_prev_state;
14313 self.add_selections_state = entry.add_selections_state;
14314 }
14315 }
14316
14317 pub fn expand_excerpts(
14318 &mut self,
14319 action: &ExpandExcerpts,
14320 _: &mut Window,
14321 cx: &mut Context<Self>,
14322 ) {
14323 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14324 }
14325
14326 pub fn expand_excerpts_down(
14327 &mut self,
14328 action: &ExpandExcerptsDown,
14329 _: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) {
14332 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14333 }
14334
14335 pub fn expand_excerpts_up(
14336 &mut self,
14337 action: &ExpandExcerptsUp,
14338 _: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) {
14341 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14342 }
14343
14344 pub fn expand_excerpts_for_direction(
14345 &mut self,
14346 lines: u32,
14347 direction: ExpandExcerptDirection,
14348
14349 cx: &mut Context<Self>,
14350 ) {
14351 let selections = self.selections.disjoint_anchors();
14352
14353 let lines = if lines == 0 {
14354 EditorSettings::get_global(cx).expand_excerpt_lines
14355 } else {
14356 lines
14357 };
14358
14359 self.buffer.update(cx, |buffer, cx| {
14360 let snapshot = buffer.snapshot(cx);
14361 let mut excerpt_ids = selections
14362 .iter()
14363 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14364 .collect::<Vec<_>>();
14365 excerpt_ids.sort();
14366 excerpt_ids.dedup();
14367 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14368 })
14369 }
14370
14371 pub fn expand_excerpt(
14372 &mut self,
14373 excerpt: ExcerptId,
14374 direction: ExpandExcerptDirection,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 let current_scroll_position = self.scroll_position(cx);
14379 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14380 let mut should_scroll_up = false;
14381
14382 if direction == ExpandExcerptDirection::Down {
14383 let multi_buffer = self.buffer.read(cx);
14384 let snapshot = multi_buffer.snapshot(cx);
14385 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14386 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14387 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14388 let buffer_snapshot = buffer.read(cx).snapshot();
14389 let excerpt_end_row =
14390 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14391 let last_row = buffer_snapshot.max_point().row;
14392 let lines_below = last_row.saturating_sub(excerpt_end_row);
14393 should_scroll_up = lines_below >= lines_to_expand;
14394 }
14395 }
14396 }
14397 }
14398
14399 self.buffer.update(cx, |buffer, cx| {
14400 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14401 });
14402
14403 if should_scroll_up {
14404 let new_scroll_position =
14405 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14406 self.set_scroll_position(new_scroll_position, window, cx);
14407 }
14408 }
14409
14410 pub fn go_to_singleton_buffer_point(
14411 &mut self,
14412 point: Point,
14413 window: &mut Window,
14414 cx: &mut Context<Self>,
14415 ) {
14416 self.go_to_singleton_buffer_range(point..point, window, cx);
14417 }
14418
14419 pub fn go_to_singleton_buffer_range(
14420 &mut self,
14421 range: Range<Point>,
14422 window: &mut Window,
14423 cx: &mut Context<Self>,
14424 ) {
14425 let multibuffer = self.buffer().read(cx);
14426 let Some(buffer) = multibuffer.as_singleton() else {
14427 return;
14428 };
14429 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14430 return;
14431 };
14432 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14433 return;
14434 };
14435 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14436 s.select_anchor_ranges([start..end])
14437 });
14438 }
14439
14440 pub fn go_to_diagnostic(
14441 &mut self,
14442 _: &GoToDiagnostic,
14443 window: &mut Window,
14444 cx: &mut Context<Self>,
14445 ) {
14446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14447 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14448 }
14449
14450 pub fn go_to_prev_diagnostic(
14451 &mut self,
14452 _: &GoToPreviousDiagnostic,
14453 window: &mut Window,
14454 cx: &mut Context<Self>,
14455 ) {
14456 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14457 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14458 }
14459
14460 pub fn go_to_diagnostic_impl(
14461 &mut self,
14462 direction: Direction,
14463 window: &mut Window,
14464 cx: &mut Context<Self>,
14465 ) {
14466 let buffer = self.buffer.read(cx).snapshot(cx);
14467 let selection = self.selections.newest::<usize>(cx);
14468
14469 let mut active_group_id = None;
14470 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14471 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14472 active_group_id = Some(active_group.group_id);
14473 }
14474 }
14475
14476 fn filtered(
14477 snapshot: EditorSnapshot,
14478 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14479 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14480 diagnostics
14481 .filter(|entry| entry.range.start != entry.range.end)
14482 .filter(|entry| !entry.diagnostic.is_unnecessary)
14483 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14484 }
14485
14486 let snapshot = self.snapshot(window, cx);
14487 let before = filtered(
14488 snapshot.clone(),
14489 buffer
14490 .diagnostics_in_range(0..selection.start)
14491 .filter(|entry| entry.range.start <= selection.start),
14492 );
14493 let after = filtered(
14494 snapshot,
14495 buffer
14496 .diagnostics_in_range(selection.start..buffer.len())
14497 .filter(|entry| entry.range.start >= selection.start),
14498 );
14499
14500 let mut found: Option<DiagnosticEntry<usize>> = None;
14501 if direction == Direction::Prev {
14502 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14503 {
14504 for diagnostic in prev_diagnostics.into_iter().rev() {
14505 if diagnostic.range.start != selection.start
14506 || active_group_id
14507 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14508 {
14509 found = Some(diagnostic);
14510 break 'outer;
14511 }
14512 }
14513 }
14514 } else {
14515 for diagnostic in after.chain(before) {
14516 if diagnostic.range.start != selection.start
14517 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14518 {
14519 found = Some(diagnostic);
14520 break;
14521 }
14522 }
14523 }
14524 let Some(next_diagnostic) = found else {
14525 return;
14526 };
14527
14528 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14529 return;
14530 };
14531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14532 s.select_ranges(vec![
14533 next_diagnostic.range.start..next_diagnostic.range.start,
14534 ])
14535 });
14536 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14537 self.refresh_inline_completion(false, true, window, cx);
14538 }
14539
14540 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14541 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14542 let snapshot = self.snapshot(window, cx);
14543 let selection = self.selections.newest::<Point>(cx);
14544 self.go_to_hunk_before_or_after_position(
14545 &snapshot,
14546 selection.head(),
14547 Direction::Next,
14548 window,
14549 cx,
14550 );
14551 }
14552
14553 pub fn go_to_hunk_before_or_after_position(
14554 &mut self,
14555 snapshot: &EditorSnapshot,
14556 position: Point,
14557 direction: Direction,
14558 window: &mut Window,
14559 cx: &mut Context<Editor>,
14560 ) {
14561 let row = if direction == Direction::Next {
14562 self.hunk_after_position(snapshot, position)
14563 .map(|hunk| hunk.row_range.start)
14564 } else {
14565 self.hunk_before_position(snapshot, position)
14566 };
14567
14568 if let Some(row) = row {
14569 let destination = Point::new(row.0, 0);
14570 let autoscroll = Autoscroll::center();
14571
14572 self.unfold_ranges(&[destination..destination], false, false, cx);
14573 self.change_selections(Some(autoscroll), window, cx, |s| {
14574 s.select_ranges([destination..destination]);
14575 });
14576 }
14577 }
14578
14579 fn hunk_after_position(
14580 &mut self,
14581 snapshot: &EditorSnapshot,
14582 position: Point,
14583 ) -> Option<MultiBufferDiffHunk> {
14584 snapshot
14585 .buffer_snapshot
14586 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14587 .find(|hunk| hunk.row_range.start.0 > position.row)
14588 .or_else(|| {
14589 snapshot
14590 .buffer_snapshot
14591 .diff_hunks_in_range(Point::zero()..position)
14592 .find(|hunk| hunk.row_range.end.0 < position.row)
14593 })
14594 }
14595
14596 fn go_to_prev_hunk(
14597 &mut self,
14598 _: &GoToPreviousHunk,
14599 window: &mut Window,
14600 cx: &mut Context<Self>,
14601 ) {
14602 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14603 let snapshot = self.snapshot(window, cx);
14604 let selection = self.selections.newest::<Point>(cx);
14605 self.go_to_hunk_before_or_after_position(
14606 &snapshot,
14607 selection.head(),
14608 Direction::Prev,
14609 window,
14610 cx,
14611 );
14612 }
14613
14614 fn hunk_before_position(
14615 &mut self,
14616 snapshot: &EditorSnapshot,
14617 position: Point,
14618 ) -> Option<MultiBufferRow> {
14619 snapshot
14620 .buffer_snapshot
14621 .diff_hunk_before(position)
14622 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14623 }
14624
14625 fn go_to_next_change(
14626 &mut self,
14627 _: &GoToNextChange,
14628 window: &mut Window,
14629 cx: &mut Context<Self>,
14630 ) {
14631 if let Some(selections) = self
14632 .change_list
14633 .next_change(1, Direction::Next)
14634 .map(|s| s.to_vec())
14635 {
14636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14637 let map = s.display_map();
14638 s.select_display_ranges(selections.iter().map(|a| {
14639 let point = a.to_display_point(&map);
14640 point..point
14641 }))
14642 })
14643 }
14644 }
14645
14646 fn go_to_previous_change(
14647 &mut self,
14648 _: &GoToPreviousChange,
14649 window: &mut Window,
14650 cx: &mut Context<Self>,
14651 ) {
14652 if let Some(selections) = self
14653 .change_list
14654 .next_change(1, Direction::Prev)
14655 .map(|s| s.to_vec())
14656 {
14657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14658 let map = s.display_map();
14659 s.select_display_ranges(selections.iter().map(|a| {
14660 let point = a.to_display_point(&map);
14661 point..point
14662 }))
14663 })
14664 }
14665 }
14666
14667 fn go_to_line<T: 'static>(
14668 &mut self,
14669 position: Anchor,
14670 highlight_color: Option<Hsla>,
14671 window: &mut Window,
14672 cx: &mut Context<Self>,
14673 ) {
14674 let snapshot = self.snapshot(window, cx).display_snapshot;
14675 let position = position.to_point(&snapshot.buffer_snapshot);
14676 let start = snapshot
14677 .buffer_snapshot
14678 .clip_point(Point::new(position.row, 0), Bias::Left);
14679 let end = start + Point::new(1, 0);
14680 let start = snapshot.buffer_snapshot.anchor_before(start);
14681 let end = snapshot.buffer_snapshot.anchor_before(end);
14682
14683 self.highlight_rows::<T>(
14684 start..end,
14685 highlight_color
14686 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14687 Default::default(),
14688 cx,
14689 );
14690
14691 if self.buffer.read(cx).is_singleton() {
14692 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14693 }
14694 }
14695
14696 pub fn go_to_definition(
14697 &mut self,
14698 _: &GoToDefinition,
14699 window: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) -> Task<Result<Navigated>> {
14702 let definition =
14703 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14704 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14705 cx.spawn_in(window, async move |editor, cx| {
14706 if definition.await? == Navigated::Yes {
14707 return Ok(Navigated::Yes);
14708 }
14709 match fallback_strategy {
14710 GoToDefinitionFallback::None => Ok(Navigated::No),
14711 GoToDefinitionFallback::FindAllReferences => {
14712 match editor.update_in(cx, |editor, window, cx| {
14713 editor.find_all_references(&FindAllReferences, window, cx)
14714 })? {
14715 Some(references) => references.await,
14716 None => Ok(Navigated::No),
14717 }
14718 }
14719 }
14720 })
14721 }
14722
14723 pub fn go_to_declaration(
14724 &mut self,
14725 _: &GoToDeclaration,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) -> Task<Result<Navigated>> {
14729 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14730 }
14731
14732 pub fn go_to_declaration_split(
14733 &mut self,
14734 _: &GoToDeclaration,
14735 window: &mut Window,
14736 cx: &mut Context<Self>,
14737 ) -> Task<Result<Navigated>> {
14738 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14739 }
14740
14741 pub fn go_to_implementation(
14742 &mut self,
14743 _: &GoToImplementation,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) -> Task<Result<Navigated>> {
14747 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14748 }
14749
14750 pub fn go_to_implementation_split(
14751 &mut self,
14752 _: &GoToImplementationSplit,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) -> Task<Result<Navigated>> {
14756 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14757 }
14758
14759 pub fn go_to_type_definition(
14760 &mut self,
14761 _: &GoToTypeDefinition,
14762 window: &mut Window,
14763 cx: &mut Context<Self>,
14764 ) -> Task<Result<Navigated>> {
14765 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14766 }
14767
14768 pub fn go_to_definition_split(
14769 &mut self,
14770 _: &GoToDefinitionSplit,
14771 window: &mut Window,
14772 cx: &mut Context<Self>,
14773 ) -> Task<Result<Navigated>> {
14774 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14775 }
14776
14777 pub fn go_to_type_definition_split(
14778 &mut self,
14779 _: &GoToTypeDefinitionSplit,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) -> Task<Result<Navigated>> {
14783 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14784 }
14785
14786 fn go_to_definition_of_kind(
14787 &mut self,
14788 kind: GotoDefinitionKind,
14789 split: bool,
14790 window: &mut Window,
14791 cx: &mut Context<Self>,
14792 ) -> Task<Result<Navigated>> {
14793 let Some(provider) = self.semantics_provider.clone() else {
14794 return Task::ready(Ok(Navigated::No));
14795 };
14796 let head = self.selections.newest::<usize>(cx).head();
14797 let buffer = self.buffer.read(cx);
14798 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14799 text_anchor
14800 } else {
14801 return Task::ready(Ok(Navigated::No));
14802 };
14803
14804 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14805 return Task::ready(Ok(Navigated::No));
14806 };
14807
14808 cx.spawn_in(window, async move |editor, cx| {
14809 let definitions = definitions.await?;
14810 let navigated = editor
14811 .update_in(cx, |editor, window, cx| {
14812 editor.navigate_to_hover_links(
14813 Some(kind),
14814 definitions
14815 .into_iter()
14816 .filter(|location| {
14817 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14818 })
14819 .map(HoverLink::Text)
14820 .collect::<Vec<_>>(),
14821 split,
14822 window,
14823 cx,
14824 )
14825 })?
14826 .await?;
14827 anyhow::Ok(navigated)
14828 })
14829 }
14830
14831 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14832 let selection = self.selections.newest_anchor();
14833 let head = selection.head();
14834 let tail = selection.tail();
14835
14836 let Some((buffer, start_position)) =
14837 self.buffer.read(cx).text_anchor_for_position(head, cx)
14838 else {
14839 return;
14840 };
14841
14842 let end_position = if head != tail {
14843 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14844 return;
14845 };
14846 Some(pos)
14847 } else {
14848 None
14849 };
14850
14851 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14852 let url = if let Some(end_pos) = end_position {
14853 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14854 } else {
14855 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14856 };
14857
14858 if let Some(url) = url {
14859 editor.update(cx, |_, cx| {
14860 cx.open_url(&url);
14861 })
14862 } else {
14863 Ok(())
14864 }
14865 });
14866
14867 url_finder.detach();
14868 }
14869
14870 pub fn open_selected_filename(
14871 &mut self,
14872 _: &OpenSelectedFilename,
14873 window: &mut Window,
14874 cx: &mut Context<Self>,
14875 ) {
14876 let Some(workspace) = self.workspace() else {
14877 return;
14878 };
14879
14880 let position = self.selections.newest_anchor().head();
14881
14882 let Some((buffer, buffer_position)) =
14883 self.buffer.read(cx).text_anchor_for_position(position, cx)
14884 else {
14885 return;
14886 };
14887
14888 let project = self.project.clone();
14889
14890 cx.spawn_in(window, async move |_, cx| {
14891 let result = find_file(&buffer, project, buffer_position, cx).await;
14892
14893 if let Some((_, path)) = result {
14894 workspace
14895 .update_in(cx, |workspace, window, cx| {
14896 workspace.open_resolved_path(path, window, cx)
14897 })?
14898 .await?;
14899 }
14900 anyhow::Ok(())
14901 })
14902 .detach();
14903 }
14904
14905 pub(crate) fn navigate_to_hover_links(
14906 &mut self,
14907 kind: Option<GotoDefinitionKind>,
14908 mut definitions: Vec<HoverLink>,
14909 split: bool,
14910 window: &mut Window,
14911 cx: &mut Context<Editor>,
14912 ) -> Task<Result<Navigated>> {
14913 // If there is one definition, just open it directly
14914 if definitions.len() == 1 {
14915 let definition = definitions.pop().unwrap();
14916
14917 enum TargetTaskResult {
14918 Location(Option<Location>),
14919 AlreadyNavigated,
14920 }
14921
14922 let target_task = match definition {
14923 HoverLink::Text(link) => {
14924 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14925 }
14926 HoverLink::InlayHint(lsp_location, server_id) => {
14927 let computation =
14928 self.compute_target_location(lsp_location, server_id, window, cx);
14929 cx.background_spawn(async move {
14930 let location = computation.await?;
14931 Ok(TargetTaskResult::Location(location))
14932 })
14933 }
14934 HoverLink::Url(url) => {
14935 cx.open_url(&url);
14936 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14937 }
14938 HoverLink::File(path) => {
14939 if let Some(workspace) = self.workspace() {
14940 cx.spawn_in(window, async move |_, cx| {
14941 workspace
14942 .update_in(cx, |workspace, window, cx| {
14943 workspace.open_resolved_path(path, window, cx)
14944 })?
14945 .await
14946 .map(|_| TargetTaskResult::AlreadyNavigated)
14947 })
14948 } else {
14949 Task::ready(Ok(TargetTaskResult::Location(None)))
14950 }
14951 }
14952 };
14953 cx.spawn_in(window, async move |editor, cx| {
14954 let target = match target_task.await.context("target resolution task")? {
14955 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14956 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14957 TargetTaskResult::Location(Some(target)) => target,
14958 };
14959
14960 editor.update_in(cx, |editor, window, cx| {
14961 let Some(workspace) = editor.workspace() else {
14962 return Navigated::No;
14963 };
14964 let pane = workspace.read(cx).active_pane().clone();
14965
14966 let range = target.range.to_point(target.buffer.read(cx));
14967 let range = editor.range_for_match(&range);
14968 let range = collapse_multiline_range(range);
14969
14970 if !split
14971 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14972 {
14973 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14974 } else {
14975 window.defer(cx, move |window, cx| {
14976 let target_editor: Entity<Self> =
14977 workspace.update(cx, |workspace, cx| {
14978 let pane = if split {
14979 workspace.adjacent_pane(window, cx)
14980 } else {
14981 workspace.active_pane().clone()
14982 };
14983
14984 workspace.open_project_item(
14985 pane,
14986 target.buffer.clone(),
14987 true,
14988 true,
14989 window,
14990 cx,
14991 )
14992 });
14993 target_editor.update(cx, |target_editor, cx| {
14994 // When selecting a definition in a different buffer, disable the nav history
14995 // to avoid creating a history entry at the previous cursor location.
14996 pane.update(cx, |pane, _| pane.disable_history());
14997 target_editor.go_to_singleton_buffer_range(range, window, cx);
14998 pane.update(cx, |pane, _| pane.enable_history());
14999 });
15000 });
15001 }
15002 Navigated::Yes
15003 })
15004 })
15005 } else if !definitions.is_empty() {
15006 cx.spawn_in(window, async move |editor, cx| {
15007 let (title, location_tasks, workspace) = editor
15008 .update_in(cx, |editor, window, cx| {
15009 let tab_kind = match kind {
15010 Some(GotoDefinitionKind::Implementation) => "Implementations",
15011 _ => "Definitions",
15012 };
15013 let title = definitions
15014 .iter()
15015 .find_map(|definition| match definition {
15016 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15017 let buffer = origin.buffer.read(cx);
15018 format!(
15019 "{} for {}",
15020 tab_kind,
15021 buffer
15022 .text_for_range(origin.range.clone())
15023 .collect::<String>()
15024 )
15025 }),
15026 HoverLink::InlayHint(_, _) => None,
15027 HoverLink::Url(_) => None,
15028 HoverLink::File(_) => None,
15029 })
15030 .unwrap_or(tab_kind.to_string());
15031 let location_tasks = definitions
15032 .into_iter()
15033 .map(|definition| match definition {
15034 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15035 HoverLink::InlayHint(lsp_location, server_id) => editor
15036 .compute_target_location(lsp_location, server_id, window, cx),
15037 HoverLink::Url(_) => Task::ready(Ok(None)),
15038 HoverLink::File(_) => Task::ready(Ok(None)),
15039 })
15040 .collect::<Vec<_>>();
15041 (title, location_tasks, editor.workspace().clone())
15042 })
15043 .context("location tasks preparation")?;
15044
15045 let locations = future::join_all(location_tasks)
15046 .await
15047 .into_iter()
15048 .filter_map(|location| location.transpose())
15049 .collect::<Result<_>>()
15050 .context("location tasks")?;
15051
15052 let Some(workspace) = workspace else {
15053 return Ok(Navigated::No);
15054 };
15055 let opened = workspace
15056 .update_in(cx, |workspace, window, cx| {
15057 Self::open_locations_in_multibuffer(
15058 workspace,
15059 locations,
15060 title,
15061 split,
15062 MultibufferSelectionMode::First,
15063 window,
15064 cx,
15065 )
15066 })
15067 .ok();
15068
15069 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15070 })
15071 } else {
15072 Task::ready(Ok(Navigated::No))
15073 }
15074 }
15075
15076 fn compute_target_location(
15077 &self,
15078 lsp_location: lsp::Location,
15079 server_id: LanguageServerId,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) -> Task<anyhow::Result<Option<Location>>> {
15083 let Some(project) = self.project.clone() else {
15084 return Task::ready(Ok(None));
15085 };
15086
15087 cx.spawn_in(window, async move |editor, cx| {
15088 let location_task = editor.update(cx, |_, cx| {
15089 project.update(cx, |project, cx| {
15090 let language_server_name = project
15091 .language_server_statuses(cx)
15092 .find(|(id, _)| server_id == *id)
15093 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15094 language_server_name.map(|language_server_name| {
15095 project.open_local_buffer_via_lsp(
15096 lsp_location.uri.clone(),
15097 server_id,
15098 language_server_name,
15099 cx,
15100 )
15101 })
15102 })
15103 })?;
15104 let location = match location_task {
15105 Some(task) => Some({
15106 let target_buffer_handle = task.await.context("open local buffer")?;
15107 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15108 let target_start = target_buffer
15109 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15110 let target_end = target_buffer
15111 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15112 target_buffer.anchor_after(target_start)
15113 ..target_buffer.anchor_before(target_end)
15114 })?;
15115 Location {
15116 buffer: target_buffer_handle,
15117 range,
15118 }
15119 }),
15120 None => None,
15121 };
15122 Ok(location)
15123 })
15124 }
15125
15126 pub fn find_all_references(
15127 &mut self,
15128 _: &FindAllReferences,
15129 window: &mut Window,
15130 cx: &mut Context<Self>,
15131 ) -> Option<Task<Result<Navigated>>> {
15132 let selection = self.selections.newest::<usize>(cx);
15133 let multi_buffer = self.buffer.read(cx);
15134 let head = selection.head();
15135
15136 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15137 let head_anchor = multi_buffer_snapshot.anchor_at(
15138 head,
15139 if head < selection.tail() {
15140 Bias::Right
15141 } else {
15142 Bias::Left
15143 },
15144 );
15145
15146 match self
15147 .find_all_references_task_sources
15148 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15149 {
15150 Ok(_) => {
15151 log::info!(
15152 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15153 );
15154 return None;
15155 }
15156 Err(i) => {
15157 self.find_all_references_task_sources.insert(i, head_anchor);
15158 }
15159 }
15160
15161 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15162 let workspace = self.workspace()?;
15163 let project = workspace.read(cx).project().clone();
15164 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15165 Some(cx.spawn_in(window, async move |editor, cx| {
15166 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15167 if let Ok(i) = editor
15168 .find_all_references_task_sources
15169 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15170 {
15171 editor.find_all_references_task_sources.remove(i);
15172 }
15173 });
15174
15175 let locations = references.await?;
15176 if locations.is_empty() {
15177 return anyhow::Ok(Navigated::No);
15178 }
15179
15180 workspace.update_in(cx, |workspace, window, cx| {
15181 let title = locations
15182 .first()
15183 .as_ref()
15184 .map(|location| {
15185 let buffer = location.buffer.read(cx);
15186 format!(
15187 "References to `{}`",
15188 buffer
15189 .text_for_range(location.range.clone())
15190 .collect::<String>()
15191 )
15192 })
15193 .unwrap();
15194 Self::open_locations_in_multibuffer(
15195 workspace,
15196 locations,
15197 title,
15198 false,
15199 MultibufferSelectionMode::First,
15200 window,
15201 cx,
15202 );
15203 Navigated::Yes
15204 })
15205 }))
15206 }
15207
15208 /// Opens a multibuffer with the given project locations in it
15209 pub fn open_locations_in_multibuffer(
15210 workspace: &mut Workspace,
15211 mut locations: Vec<Location>,
15212 title: String,
15213 split: bool,
15214 multibuffer_selection_mode: MultibufferSelectionMode,
15215 window: &mut Window,
15216 cx: &mut Context<Workspace>,
15217 ) {
15218 // If there are multiple definitions, open them in a multibuffer
15219 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15220 let mut locations = locations.into_iter().peekable();
15221 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15222 let capability = workspace.project().read(cx).capability();
15223
15224 let excerpt_buffer = cx.new(|cx| {
15225 let mut multibuffer = MultiBuffer::new(capability);
15226 while let Some(location) = locations.next() {
15227 let buffer = location.buffer.read(cx);
15228 let mut ranges_for_buffer = Vec::new();
15229 let range = location.range.to_point(buffer);
15230 ranges_for_buffer.push(range.clone());
15231
15232 while let Some(next_location) = locations.peek() {
15233 if next_location.buffer == location.buffer {
15234 ranges_for_buffer.push(next_location.range.to_point(buffer));
15235 locations.next();
15236 } else {
15237 break;
15238 }
15239 }
15240
15241 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15242 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15243 PathKey::for_buffer(&location.buffer, cx),
15244 location.buffer.clone(),
15245 ranges_for_buffer,
15246 DEFAULT_MULTIBUFFER_CONTEXT,
15247 cx,
15248 );
15249 ranges.extend(new_ranges)
15250 }
15251
15252 multibuffer.with_title(title)
15253 });
15254
15255 let editor = cx.new(|cx| {
15256 Editor::for_multibuffer(
15257 excerpt_buffer,
15258 Some(workspace.project().clone()),
15259 window,
15260 cx,
15261 )
15262 });
15263 editor.update(cx, |editor, cx| {
15264 match multibuffer_selection_mode {
15265 MultibufferSelectionMode::First => {
15266 if let Some(first_range) = ranges.first() {
15267 editor.change_selections(None, window, cx, |selections| {
15268 selections.clear_disjoint();
15269 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15270 });
15271 }
15272 editor.highlight_background::<Self>(
15273 &ranges,
15274 |theme| theme.colors().editor_highlighted_line_background,
15275 cx,
15276 );
15277 }
15278 MultibufferSelectionMode::All => {
15279 editor.change_selections(None, window, cx, |selections| {
15280 selections.clear_disjoint();
15281 selections.select_anchor_ranges(ranges);
15282 });
15283 }
15284 }
15285 editor.register_buffers_with_language_servers(cx);
15286 });
15287
15288 let item = Box::new(editor);
15289 let item_id = item.item_id();
15290
15291 if split {
15292 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15293 } else {
15294 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15295 let (preview_item_id, preview_item_idx) =
15296 workspace.active_pane().read_with(cx, |pane, _| {
15297 (pane.preview_item_id(), pane.preview_item_idx())
15298 });
15299
15300 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15301
15302 if let Some(preview_item_id) = preview_item_id {
15303 workspace.active_pane().update(cx, |pane, cx| {
15304 pane.remove_item(preview_item_id, false, false, window, cx);
15305 });
15306 }
15307 } else {
15308 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15309 }
15310 }
15311 workspace.active_pane().update(cx, |pane, cx| {
15312 pane.set_preview_item_id(Some(item_id), cx);
15313 });
15314 }
15315
15316 pub fn rename(
15317 &mut self,
15318 _: &Rename,
15319 window: &mut Window,
15320 cx: &mut Context<Self>,
15321 ) -> Option<Task<Result<()>>> {
15322 use language::ToOffset as _;
15323
15324 let provider = self.semantics_provider.clone()?;
15325 let selection = self.selections.newest_anchor().clone();
15326 let (cursor_buffer, cursor_buffer_position) = self
15327 .buffer
15328 .read(cx)
15329 .text_anchor_for_position(selection.head(), cx)?;
15330 let (tail_buffer, cursor_buffer_position_end) = self
15331 .buffer
15332 .read(cx)
15333 .text_anchor_for_position(selection.tail(), cx)?;
15334 if tail_buffer != cursor_buffer {
15335 return None;
15336 }
15337
15338 let snapshot = cursor_buffer.read(cx).snapshot();
15339 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15340 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15341 let prepare_rename = provider
15342 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15343 .unwrap_or_else(|| Task::ready(Ok(None)));
15344 drop(snapshot);
15345
15346 Some(cx.spawn_in(window, async move |this, cx| {
15347 let rename_range = if let Some(range) = prepare_rename.await? {
15348 Some(range)
15349 } else {
15350 this.update(cx, |this, cx| {
15351 let buffer = this.buffer.read(cx).snapshot(cx);
15352 let mut buffer_highlights = this
15353 .document_highlights_for_position(selection.head(), &buffer)
15354 .filter(|highlight| {
15355 highlight.start.excerpt_id == selection.head().excerpt_id
15356 && highlight.end.excerpt_id == selection.head().excerpt_id
15357 });
15358 buffer_highlights
15359 .next()
15360 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15361 })?
15362 };
15363 if let Some(rename_range) = rename_range {
15364 this.update_in(cx, |this, window, cx| {
15365 let snapshot = cursor_buffer.read(cx).snapshot();
15366 let rename_buffer_range = rename_range.to_offset(&snapshot);
15367 let cursor_offset_in_rename_range =
15368 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15369 let cursor_offset_in_rename_range_end =
15370 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15371
15372 this.take_rename(false, window, cx);
15373 let buffer = this.buffer.read(cx).read(cx);
15374 let cursor_offset = selection.head().to_offset(&buffer);
15375 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15376 let rename_end = rename_start + rename_buffer_range.len();
15377 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15378 let mut old_highlight_id = None;
15379 let old_name: Arc<str> = buffer
15380 .chunks(rename_start..rename_end, true)
15381 .map(|chunk| {
15382 if old_highlight_id.is_none() {
15383 old_highlight_id = chunk.syntax_highlight_id;
15384 }
15385 chunk.text
15386 })
15387 .collect::<String>()
15388 .into();
15389
15390 drop(buffer);
15391
15392 // Position the selection in the rename editor so that it matches the current selection.
15393 this.show_local_selections = false;
15394 let rename_editor = cx.new(|cx| {
15395 let mut editor = Editor::single_line(window, cx);
15396 editor.buffer.update(cx, |buffer, cx| {
15397 buffer.edit([(0..0, old_name.clone())], None, cx)
15398 });
15399 let rename_selection_range = match cursor_offset_in_rename_range
15400 .cmp(&cursor_offset_in_rename_range_end)
15401 {
15402 Ordering::Equal => {
15403 editor.select_all(&SelectAll, window, cx);
15404 return editor;
15405 }
15406 Ordering::Less => {
15407 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15408 }
15409 Ordering::Greater => {
15410 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15411 }
15412 };
15413 if rename_selection_range.end > old_name.len() {
15414 editor.select_all(&SelectAll, window, cx);
15415 } else {
15416 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15417 s.select_ranges([rename_selection_range]);
15418 });
15419 }
15420 editor
15421 });
15422 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15423 if e == &EditorEvent::Focused {
15424 cx.emit(EditorEvent::FocusedIn)
15425 }
15426 })
15427 .detach();
15428
15429 let write_highlights = this
15430 .clear_background_highlights::<DocumentHighlightWrite>(cx)
15431 .unwrap_or_default();
15432 let read_highlights = this
15433 .clear_background_highlights::<DocumentHighlightRead>(cx)
15434 .unwrap_or_default();
15435 let ranges = write_highlights
15436 .iter()
15437 .chain(read_highlights.iter())
15438 .cloned()
15439 .map(|highlight| {
15440 (
15441 highlight.range,
15442 HighlightStyle {
15443 fade_out: Some(0.6),
15444 ..Default::default()
15445 },
15446 )
15447 })
15448 .collect();
15449
15450 this.highlight_text::<Rename>(ranges, cx);
15451 let rename_focus_handle = rename_editor.focus_handle(cx);
15452 window.focus(&rename_focus_handle);
15453 let block_id = this.insert_blocks(
15454 [BlockProperties {
15455 style: BlockStyle::Flex,
15456 placement: BlockPlacement::Below(range.start),
15457 height: Some(1),
15458 render: Arc::new({
15459 let rename_editor = rename_editor.clone();
15460 move |cx: &mut BlockContext| {
15461 let mut text_style = cx.editor_style.text.clone();
15462 if let Some(highlight_style) = old_highlight_id
15463 .and_then(|h| h.style(&cx.editor_style.syntax))
15464 {
15465 text_style = text_style.highlight(highlight_style);
15466 }
15467 div()
15468 .block_mouse_except_scroll()
15469 .pl(cx.anchor_x)
15470 .child(EditorElement::new(
15471 &rename_editor,
15472 EditorStyle {
15473 background: cx.theme().system().transparent,
15474 local_player: cx.editor_style.local_player,
15475 text: text_style,
15476 scrollbar_width: cx.editor_style.scrollbar_width,
15477 syntax: cx.editor_style.syntax.clone(),
15478 status: cx.editor_style.status.clone(),
15479 inlay_hints_style: HighlightStyle {
15480 font_weight: Some(FontWeight::BOLD),
15481 ..make_inlay_hints_style(cx.app)
15482 },
15483 inline_completion_styles: make_suggestion_styles(
15484 cx.app,
15485 ),
15486 ..EditorStyle::default()
15487 },
15488 ))
15489 .into_any_element()
15490 }
15491 }),
15492 priority: 0,
15493 render_in_minimap: true,
15494 }],
15495 Some(Autoscroll::fit()),
15496 cx,
15497 )[0];
15498 this.pending_rename = Some(RenameState {
15499 range,
15500 old_name,
15501 editor: rename_editor,
15502 block_id,
15503 });
15504 })?;
15505 }
15506
15507 Ok(())
15508 }))
15509 }
15510
15511 pub fn confirm_rename(
15512 &mut self,
15513 _: &ConfirmRename,
15514 window: &mut Window,
15515 cx: &mut Context<Self>,
15516 ) -> Option<Task<Result<()>>> {
15517 let rename = self.take_rename(false, window, cx)?;
15518 let workspace = self.workspace()?.downgrade();
15519 let (buffer, start) = self
15520 .buffer
15521 .read(cx)
15522 .text_anchor_for_position(rename.range.start, cx)?;
15523 let (end_buffer, _) = self
15524 .buffer
15525 .read(cx)
15526 .text_anchor_for_position(rename.range.end, cx)?;
15527 if buffer != end_buffer {
15528 return None;
15529 }
15530
15531 let old_name = rename.old_name;
15532 let new_name = rename.editor.read(cx).text(cx);
15533
15534 let rename = self.semantics_provider.as_ref()?.perform_rename(
15535 &buffer,
15536 start,
15537 new_name.clone(),
15538 cx,
15539 )?;
15540
15541 Some(cx.spawn_in(window, async move |editor, cx| {
15542 let project_transaction = rename.await?;
15543 Self::open_project_transaction(
15544 &editor,
15545 workspace,
15546 project_transaction,
15547 format!("Rename: {} → {}", old_name, new_name),
15548 cx,
15549 )
15550 .await?;
15551
15552 editor.update(cx, |editor, cx| {
15553 editor.refresh_document_highlights(cx);
15554 })?;
15555 Ok(())
15556 }))
15557 }
15558
15559 fn take_rename(
15560 &mut self,
15561 moving_cursor: bool,
15562 window: &mut Window,
15563 cx: &mut Context<Self>,
15564 ) -> Option<RenameState> {
15565 let rename = self.pending_rename.take()?;
15566 if rename.editor.focus_handle(cx).is_focused(window) {
15567 window.focus(&self.focus_handle);
15568 }
15569
15570 self.remove_blocks(
15571 [rename.block_id].into_iter().collect(),
15572 Some(Autoscroll::fit()),
15573 cx,
15574 );
15575 self.clear_highlights::<Rename>(cx);
15576 self.show_local_selections = true;
15577
15578 if moving_cursor {
15579 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15580 editor.selections.newest::<usize>(cx).head()
15581 });
15582
15583 // Update the selection to match the position of the selection inside
15584 // the rename editor.
15585 let snapshot = self.buffer.read(cx).read(cx);
15586 let rename_range = rename.range.to_offset(&snapshot);
15587 let cursor_in_editor = snapshot
15588 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15589 .min(rename_range.end);
15590 drop(snapshot);
15591
15592 self.change_selections(None, window, cx, |s| {
15593 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15594 });
15595 } else {
15596 self.refresh_document_highlights(cx);
15597 }
15598
15599 Some(rename)
15600 }
15601
15602 pub fn pending_rename(&self) -> Option<&RenameState> {
15603 self.pending_rename.as_ref()
15604 }
15605
15606 fn format(
15607 &mut self,
15608 _: &Format,
15609 window: &mut Window,
15610 cx: &mut Context<Self>,
15611 ) -> Option<Task<Result<()>>> {
15612 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15613
15614 let project = match &self.project {
15615 Some(project) => project.clone(),
15616 None => return None,
15617 };
15618
15619 Some(self.perform_format(
15620 project,
15621 FormatTrigger::Manual,
15622 FormatTarget::Buffers,
15623 window,
15624 cx,
15625 ))
15626 }
15627
15628 fn format_selections(
15629 &mut self,
15630 _: &FormatSelections,
15631 window: &mut Window,
15632 cx: &mut Context<Self>,
15633 ) -> Option<Task<Result<()>>> {
15634 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15635
15636 let project = match &self.project {
15637 Some(project) => project.clone(),
15638 None => return None,
15639 };
15640
15641 let ranges = self
15642 .selections
15643 .all_adjusted(cx)
15644 .into_iter()
15645 .map(|selection| selection.range())
15646 .collect_vec();
15647
15648 Some(self.perform_format(
15649 project,
15650 FormatTrigger::Manual,
15651 FormatTarget::Ranges(ranges),
15652 window,
15653 cx,
15654 ))
15655 }
15656
15657 fn perform_format(
15658 &mut self,
15659 project: Entity<Project>,
15660 trigger: FormatTrigger,
15661 target: FormatTarget,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) -> Task<Result<()>> {
15665 let buffer = self.buffer.clone();
15666 let (buffers, target) = match target {
15667 FormatTarget::Buffers => {
15668 let mut buffers = buffer.read(cx).all_buffers();
15669 if trigger == FormatTrigger::Save {
15670 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15671 }
15672 (buffers, LspFormatTarget::Buffers)
15673 }
15674 FormatTarget::Ranges(selection_ranges) => {
15675 let multi_buffer = buffer.read(cx);
15676 let snapshot = multi_buffer.read(cx);
15677 let mut buffers = HashSet::default();
15678 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15679 BTreeMap::new();
15680 for selection_range in selection_ranges {
15681 for (buffer, buffer_range, _) in
15682 snapshot.range_to_buffer_ranges(selection_range)
15683 {
15684 let buffer_id = buffer.remote_id();
15685 let start = buffer.anchor_before(buffer_range.start);
15686 let end = buffer.anchor_after(buffer_range.end);
15687 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15688 buffer_id_to_ranges
15689 .entry(buffer_id)
15690 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15691 .or_insert_with(|| vec![start..end]);
15692 }
15693 }
15694 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15695 }
15696 };
15697
15698 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15699 let selections_prev = transaction_id_prev
15700 .and_then(|transaction_id_prev| {
15701 // default to selections as they were after the last edit, if we have them,
15702 // instead of how they are now.
15703 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15704 // will take you back to where you made the last edit, instead of staying where you scrolled
15705 self.selection_history
15706 .transaction(transaction_id_prev)
15707 .map(|t| t.0.clone())
15708 })
15709 .unwrap_or_else(|| {
15710 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15711 self.selections.disjoint_anchors()
15712 });
15713
15714 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15715 let format = project.update(cx, |project, cx| {
15716 project.format(buffers, target, true, trigger, cx)
15717 });
15718
15719 cx.spawn_in(window, async move |editor, cx| {
15720 let transaction = futures::select_biased! {
15721 transaction = format.log_err().fuse() => transaction,
15722 () = timeout => {
15723 log::warn!("timed out waiting for formatting");
15724 None
15725 }
15726 };
15727
15728 buffer
15729 .update(cx, |buffer, cx| {
15730 if let Some(transaction) = transaction {
15731 if !buffer.is_singleton() {
15732 buffer.push_transaction(&transaction.0, cx);
15733 }
15734 }
15735 cx.notify();
15736 })
15737 .ok();
15738
15739 if let Some(transaction_id_now) =
15740 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15741 {
15742 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15743 if has_new_transaction {
15744 _ = editor.update(cx, |editor, _| {
15745 editor
15746 .selection_history
15747 .insert_transaction(transaction_id_now, selections_prev);
15748 });
15749 }
15750 }
15751
15752 Ok(())
15753 })
15754 }
15755
15756 fn organize_imports(
15757 &mut self,
15758 _: &OrganizeImports,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) -> Option<Task<Result<()>>> {
15762 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15763 let project = match &self.project {
15764 Some(project) => project.clone(),
15765 None => return None,
15766 };
15767 Some(self.perform_code_action_kind(
15768 project,
15769 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15770 window,
15771 cx,
15772 ))
15773 }
15774
15775 fn perform_code_action_kind(
15776 &mut self,
15777 project: Entity<Project>,
15778 kind: CodeActionKind,
15779 window: &mut Window,
15780 cx: &mut Context<Self>,
15781 ) -> Task<Result<()>> {
15782 let buffer = self.buffer.clone();
15783 let buffers = buffer.read(cx).all_buffers();
15784 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15785 let apply_action = project.update(cx, |project, cx| {
15786 project.apply_code_action_kind(buffers, kind, true, cx)
15787 });
15788 cx.spawn_in(window, async move |_, cx| {
15789 let transaction = futures::select_biased! {
15790 () = timeout => {
15791 log::warn!("timed out waiting for executing code action");
15792 None
15793 }
15794 transaction = apply_action.log_err().fuse() => transaction,
15795 };
15796 buffer
15797 .update(cx, |buffer, cx| {
15798 // check if we need this
15799 if let Some(transaction) = transaction {
15800 if !buffer.is_singleton() {
15801 buffer.push_transaction(&transaction.0, cx);
15802 }
15803 }
15804 cx.notify();
15805 })
15806 .ok();
15807 Ok(())
15808 })
15809 }
15810
15811 fn restart_language_server(
15812 &mut self,
15813 _: &RestartLanguageServer,
15814 _: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 if let Some(project) = self.project.clone() {
15818 self.buffer.update(cx, |multi_buffer, cx| {
15819 project.update(cx, |project, cx| {
15820 project.restart_language_servers_for_buffers(
15821 multi_buffer.all_buffers().into_iter().collect(),
15822 cx,
15823 );
15824 });
15825 })
15826 }
15827 }
15828
15829 fn stop_language_server(
15830 &mut self,
15831 _: &StopLanguageServer,
15832 _: &mut Window,
15833 cx: &mut Context<Self>,
15834 ) {
15835 if let Some(project) = self.project.clone() {
15836 self.buffer.update(cx, |multi_buffer, cx| {
15837 project.update(cx, |project, cx| {
15838 project.stop_language_servers_for_buffers(
15839 multi_buffer.all_buffers().into_iter().collect(),
15840 cx,
15841 );
15842 cx.emit(project::Event::RefreshInlayHints);
15843 });
15844 });
15845 }
15846 }
15847
15848 fn cancel_language_server_work(
15849 workspace: &mut Workspace,
15850 _: &actions::CancelLanguageServerWork,
15851 _: &mut Window,
15852 cx: &mut Context<Workspace>,
15853 ) {
15854 let project = workspace.project();
15855 let buffers = workspace
15856 .active_item(cx)
15857 .and_then(|item| item.act_as::<Editor>(cx))
15858 .map_or(HashSet::default(), |editor| {
15859 editor.read(cx).buffer.read(cx).all_buffers()
15860 });
15861 project.update(cx, |project, cx| {
15862 project.cancel_language_server_work_for_buffers(buffers, cx);
15863 });
15864 }
15865
15866 fn show_character_palette(
15867 &mut self,
15868 _: &ShowCharacterPalette,
15869 window: &mut Window,
15870 _: &mut Context<Self>,
15871 ) {
15872 window.show_character_palette();
15873 }
15874
15875 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15876 if self.mode.is_minimap() {
15877 return;
15878 }
15879
15880 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15881 let buffer = self.buffer.read(cx).snapshot(cx);
15882 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15883 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15884 let is_valid = buffer
15885 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15886 .any(|entry| {
15887 entry.diagnostic.is_primary
15888 && !entry.range.is_empty()
15889 && entry.range.start == primary_range_start
15890 && entry.diagnostic.message == active_diagnostics.active_message
15891 });
15892
15893 if !is_valid {
15894 self.dismiss_diagnostics(cx);
15895 }
15896 }
15897 }
15898
15899 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15900 match &self.active_diagnostics {
15901 ActiveDiagnostic::Group(group) => Some(group),
15902 _ => None,
15903 }
15904 }
15905
15906 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15907 self.dismiss_diagnostics(cx);
15908 self.active_diagnostics = ActiveDiagnostic::All;
15909 }
15910
15911 fn activate_diagnostics(
15912 &mut self,
15913 buffer_id: BufferId,
15914 diagnostic: DiagnosticEntry<usize>,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15919 return;
15920 }
15921 self.dismiss_diagnostics(cx);
15922 let snapshot = self.snapshot(window, cx);
15923 let buffer = self.buffer.read(cx).snapshot(cx);
15924 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15925 return;
15926 };
15927
15928 let diagnostic_group = buffer
15929 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15930 .collect::<Vec<_>>();
15931
15932 let blocks =
15933 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15934
15935 let blocks = self.display_map.update(cx, |display_map, cx| {
15936 display_map.insert_blocks(blocks, cx).into_iter().collect()
15937 });
15938 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15939 active_range: buffer.anchor_before(diagnostic.range.start)
15940 ..buffer.anchor_after(diagnostic.range.end),
15941 active_message: diagnostic.diagnostic.message.clone(),
15942 group_id: diagnostic.diagnostic.group_id,
15943 blocks,
15944 });
15945 cx.notify();
15946 }
15947
15948 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15949 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15950 return;
15951 };
15952
15953 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15954 if let ActiveDiagnostic::Group(group) = prev {
15955 self.display_map.update(cx, |display_map, cx| {
15956 display_map.remove_blocks(group.blocks, cx);
15957 });
15958 cx.notify();
15959 }
15960 }
15961
15962 /// Disable inline diagnostics rendering for this editor.
15963 pub fn disable_inline_diagnostics(&mut self) {
15964 self.inline_diagnostics_enabled = false;
15965 self.inline_diagnostics_update = Task::ready(());
15966 self.inline_diagnostics.clear();
15967 }
15968
15969 pub fn diagnostics_enabled(&self) -> bool {
15970 self.mode.is_full()
15971 }
15972
15973 pub fn inline_diagnostics_enabled(&self) -> bool {
15974 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15975 }
15976
15977 pub fn show_inline_diagnostics(&self) -> bool {
15978 self.show_inline_diagnostics
15979 }
15980
15981 pub fn toggle_inline_diagnostics(
15982 &mut self,
15983 _: &ToggleInlineDiagnostics,
15984 window: &mut Window,
15985 cx: &mut Context<Editor>,
15986 ) {
15987 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15988 self.refresh_inline_diagnostics(false, window, cx);
15989 }
15990
15991 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15992 self.diagnostics_max_severity = severity;
15993 self.display_map.update(cx, |display_map, _| {
15994 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15995 });
15996 }
15997
15998 pub fn toggle_diagnostics(
15999 &mut self,
16000 _: &ToggleDiagnostics,
16001 window: &mut Window,
16002 cx: &mut Context<Editor>,
16003 ) {
16004 if !self.diagnostics_enabled() {
16005 return;
16006 }
16007
16008 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16009 EditorSettings::get_global(cx)
16010 .diagnostics_max_severity
16011 .filter(|severity| severity != &DiagnosticSeverity::Off)
16012 .unwrap_or(DiagnosticSeverity::Hint)
16013 } else {
16014 DiagnosticSeverity::Off
16015 };
16016 self.set_max_diagnostics_severity(new_severity, cx);
16017 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16018 self.active_diagnostics = ActiveDiagnostic::None;
16019 self.inline_diagnostics_update = Task::ready(());
16020 self.inline_diagnostics.clear();
16021 } else {
16022 self.refresh_inline_diagnostics(false, window, cx);
16023 }
16024
16025 cx.notify();
16026 }
16027
16028 pub fn toggle_minimap(
16029 &mut self,
16030 _: &ToggleMinimap,
16031 window: &mut Window,
16032 cx: &mut Context<Editor>,
16033 ) {
16034 if self.supports_minimap(cx) {
16035 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16036 }
16037 }
16038
16039 fn refresh_inline_diagnostics(
16040 &mut self,
16041 debounce: bool,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) {
16045 let max_severity = ProjectSettings::get_global(cx)
16046 .diagnostics
16047 .inline
16048 .max_severity
16049 .unwrap_or(self.diagnostics_max_severity);
16050
16051 if !self.inline_diagnostics_enabled()
16052 || !self.show_inline_diagnostics
16053 || max_severity == DiagnosticSeverity::Off
16054 {
16055 self.inline_diagnostics_update = Task::ready(());
16056 self.inline_diagnostics.clear();
16057 return;
16058 }
16059
16060 let debounce_ms = ProjectSettings::get_global(cx)
16061 .diagnostics
16062 .inline
16063 .update_debounce_ms;
16064 let debounce = if debounce && debounce_ms > 0 {
16065 Some(Duration::from_millis(debounce_ms))
16066 } else {
16067 None
16068 };
16069 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16070 if let Some(debounce) = debounce {
16071 cx.background_executor().timer(debounce).await;
16072 }
16073 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16074 editor
16075 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16076 .ok()
16077 }) else {
16078 return;
16079 };
16080
16081 let new_inline_diagnostics = cx
16082 .background_spawn(async move {
16083 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16084 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16085 let message = diagnostic_entry
16086 .diagnostic
16087 .message
16088 .split_once('\n')
16089 .map(|(line, _)| line)
16090 .map(SharedString::new)
16091 .unwrap_or_else(|| {
16092 SharedString::from(diagnostic_entry.diagnostic.message)
16093 });
16094 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16095 let (Ok(i) | Err(i)) = inline_diagnostics
16096 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16097 inline_diagnostics.insert(
16098 i,
16099 (
16100 start_anchor,
16101 InlineDiagnostic {
16102 message,
16103 group_id: diagnostic_entry.diagnostic.group_id,
16104 start: diagnostic_entry.range.start.to_point(&snapshot),
16105 is_primary: diagnostic_entry.diagnostic.is_primary,
16106 severity: diagnostic_entry.diagnostic.severity,
16107 },
16108 ),
16109 );
16110 }
16111 inline_diagnostics
16112 })
16113 .await;
16114
16115 editor
16116 .update(cx, |editor, cx| {
16117 editor.inline_diagnostics = new_inline_diagnostics;
16118 cx.notify();
16119 })
16120 .ok();
16121 });
16122 }
16123
16124 fn pull_diagnostics(
16125 &mut self,
16126 buffer_id: Option<BufferId>,
16127 window: &Window,
16128 cx: &mut Context<Self>,
16129 ) -> Option<()> {
16130 if !self.mode().is_full() {
16131 return None;
16132 }
16133 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16134 .diagnostics
16135 .lsp_pull_diagnostics;
16136 if !pull_diagnostics_settings.enabled {
16137 return None;
16138 }
16139 let project = self.project.as_ref()?.downgrade();
16140 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16141 let mut buffers = self.buffer.read(cx).all_buffers();
16142 if let Some(buffer_id) = buffer_id {
16143 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16144 }
16145
16146 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16147 cx.background_executor().timer(debounce).await;
16148
16149 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16150 buffers
16151 .into_iter()
16152 .flat_map(|buffer| {
16153 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16154 })
16155 .collect::<FuturesUnordered<_>>()
16156 }) else {
16157 return;
16158 };
16159
16160 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16161 match pull_task {
16162 Ok(()) => {
16163 if editor
16164 .update_in(cx, |editor, window, cx| {
16165 editor.update_diagnostics_state(window, cx);
16166 })
16167 .is_err()
16168 {
16169 return;
16170 }
16171 }
16172 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16173 }
16174 }
16175 });
16176
16177 Some(())
16178 }
16179
16180 pub fn set_selections_from_remote(
16181 &mut self,
16182 selections: Vec<Selection<Anchor>>,
16183 pending_selection: Option<Selection<Anchor>>,
16184 window: &mut Window,
16185 cx: &mut Context<Self>,
16186 ) {
16187 let old_cursor_position = self.selections.newest_anchor().head();
16188 self.selections.change_with(cx, |s| {
16189 s.select_anchors(selections);
16190 if let Some(pending_selection) = pending_selection {
16191 s.set_pending(pending_selection, SelectMode::Character);
16192 } else {
16193 s.clear_pending();
16194 }
16195 });
16196 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16197 }
16198
16199 pub fn transact(
16200 &mut self,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16204 ) -> Option<TransactionId> {
16205 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16206 this.start_transaction_at(Instant::now(), window, cx);
16207 update(this, window, cx);
16208 this.end_transaction_at(Instant::now(), cx)
16209 })
16210 }
16211
16212 pub fn start_transaction_at(
16213 &mut self,
16214 now: Instant,
16215 window: &mut Window,
16216 cx: &mut Context<Self>,
16217 ) {
16218 self.end_selection(window, cx);
16219 if let Some(tx_id) = self
16220 .buffer
16221 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16222 {
16223 self.selection_history
16224 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16225 cx.emit(EditorEvent::TransactionBegun {
16226 transaction_id: tx_id,
16227 })
16228 }
16229 }
16230
16231 pub fn end_transaction_at(
16232 &mut self,
16233 now: Instant,
16234 cx: &mut Context<Self>,
16235 ) -> Option<TransactionId> {
16236 if let Some(transaction_id) = self
16237 .buffer
16238 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16239 {
16240 if let Some((_, end_selections)) =
16241 self.selection_history.transaction_mut(transaction_id)
16242 {
16243 *end_selections = Some(self.selections.disjoint_anchors());
16244 } else {
16245 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16246 }
16247
16248 cx.emit(EditorEvent::Edited { transaction_id });
16249 Some(transaction_id)
16250 } else {
16251 None
16252 }
16253 }
16254
16255 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16256 if self.selection_mark_mode {
16257 self.change_selections(None, window, cx, |s| {
16258 s.move_with(|_, sel| {
16259 sel.collapse_to(sel.head(), SelectionGoal::None);
16260 });
16261 })
16262 }
16263 self.selection_mark_mode = true;
16264 cx.notify();
16265 }
16266
16267 pub fn swap_selection_ends(
16268 &mut self,
16269 _: &actions::SwapSelectionEnds,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 self.change_selections(None, window, cx, |s| {
16274 s.move_with(|_, sel| {
16275 if sel.start != sel.end {
16276 sel.reversed = !sel.reversed
16277 }
16278 });
16279 });
16280 self.request_autoscroll(Autoscroll::newest(), cx);
16281 cx.notify();
16282 }
16283
16284 pub fn toggle_fold(
16285 &mut self,
16286 _: &actions::ToggleFold,
16287 window: &mut Window,
16288 cx: &mut Context<Self>,
16289 ) {
16290 if self.is_singleton(cx) {
16291 let selection = self.selections.newest::<Point>(cx);
16292
16293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16294 let range = if selection.is_empty() {
16295 let point = selection.head().to_display_point(&display_map);
16296 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16297 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16298 .to_point(&display_map);
16299 start..end
16300 } else {
16301 selection.range()
16302 };
16303 if display_map.folds_in_range(range).next().is_some() {
16304 self.unfold_lines(&Default::default(), window, cx)
16305 } else {
16306 self.fold(&Default::default(), window, cx)
16307 }
16308 } else {
16309 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16310 let buffer_ids: HashSet<_> = self
16311 .selections
16312 .disjoint_anchor_ranges()
16313 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16314 .collect();
16315
16316 let should_unfold = buffer_ids
16317 .iter()
16318 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16319
16320 for buffer_id in buffer_ids {
16321 if should_unfold {
16322 self.unfold_buffer(buffer_id, cx);
16323 } else {
16324 self.fold_buffer(buffer_id, cx);
16325 }
16326 }
16327 }
16328 }
16329
16330 pub fn toggle_fold_recursive(
16331 &mut self,
16332 _: &actions::ToggleFoldRecursive,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) {
16336 let selection = self.selections.newest::<Point>(cx);
16337
16338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16339 let range = if selection.is_empty() {
16340 let point = selection.head().to_display_point(&display_map);
16341 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16342 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16343 .to_point(&display_map);
16344 start..end
16345 } else {
16346 selection.range()
16347 };
16348 if display_map.folds_in_range(range).next().is_some() {
16349 self.unfold_recursive(&Default::default(), window, cx)
16350 } else {
16351 self.fold_recursive(&Default::default(), window, cx)
16352 }
16353 }
16354
16355 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16356 if self.is_singleton(cx) {
16357 let mut to_fold = Vec::new();
16358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16359 let selections = self.selections.all_adjusted(cx);
16360
16361 for selection in selections {
16362 let range = selection.range().sorted();
16363 let buffer_start_row = range.start.row;
16364
16365 if range.start.row != range.end.row {
16366 let mut found = false;
16367 let mut row = range.start.row;
16368 while row <= range.end.row {
16369 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16370 {
16371 found = true;
16372 row = crease.range().end.row + 1;
16373 to_fold.push(crease);
16374 } else {
16375 row += 1
16376 }
16377 }
16378 if found {
16379 continue;
16380 }
16381 }
16382
16383 for row in (0..=range.start.row).rev() {
16384 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16385 if crease.range().end.row >= buffer_start_row {
16386 to_fold.push(crease);
16387 if row <= range.start.row {
16388 break;
16389 }
16390 }
16391 }
16392 }
16393 }
16394
16395 self.fold_creases(to_fold, true, window, cx);
16396 } else {
16397 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16398 let buffer_ids = self
16399 .selections
16400 .disjoint_anchor_ranges()
16401 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16402 .collect::<HashSet<_>>();
16403 for buffer_id in buffer_ids {
16404 self.fold_buffer(buffer_id, cx);
16405 }
16406 }
16407 }
16408
16409 fn fold_at_level(
16410 &mut self,
16411 fold_at: &FoldAtLevel,
16412 window: &mut Window,
16413 cx: &mut Context<Self>,
16414 ) {
16415 if !self.buffer.read(cx).is_singleton() {
16416 return;
16417 }
16418
16419 let fold_at_level = fold_at.0;
16420 let snapshot = self.buffer.read(cx).snapshot(cx);
16421 let mut to_fold = Vec::new();
16422 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16423
16424 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16425 while start_row < end_row {
16426 match self
16427 .snapshot(window, cx)
16428 .crease_for_buffer_row(MultiBufferRow(start_row))
16429 {
16430 Some(crease) => {
16431 let nested_start_row = crease.range().start.row + 1;
16432 let nested_end_row = crease.range().end.row;
16433
16434 if current_level < fold_at_level {
16435 stack.push((nested_start_row, nested_end_row, current_level + 1));
16436 } else if current_level == fold_at_level {
16437 to_fold.push(crease);
16438 }
16439
16440 start_row = nested_end_row + 1;
16441 }
16442 None => start_row += 1,
16443 }
16444 }
16445 }
16446
16447 self.fold_creases(to_fold, true, window, cx);
16448 }
16449
16450 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16451 if self.buffer.read(cx).is_singleton() {
16452 let mut fold_ranges = Vec::new();
16453 let snapshot = self.buffer.read(cx).snapshot(cx);
16454
16455 for row in 0..snapshot.max_row().0 {
16456 if let Some(foldable_range) = self
16457 .snapshot(window, cx)
16458 .crease_for_buffer_row(MultiBufferRow(row))
16459 {
16460 fold_ranges.push(foldable_range);
16461 }
16462 }
16463
16464 self.fold_creases(fold_ranges, true, window, cx);
16465 } else {
16466 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16467 editor
16468 .update_in(cx, |editor, _, cx| {
16469 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16470 editor.fold_buffer(buffer_id, cx);
16471 }
16472 })
16473 .ok();
16474 });
16475 }
16476 }
16477
16478 pub fn fold_function_bodies(
16479 &mut self,
16480 _: &actions::FoldFunctionBodies,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) {
16484 let snapshot = self.buffer.read(cx).snapshot(cx);
16485
16486 let ranges = snapshot
16487 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16488 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16489 .collect::<Vec<_>>();
16490
16491 let creases = ranges
16492 .into_iter()
16493 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16494 .collect();
16495
16496 self.fold_creases(creases, true, window, cx);
16497 }
16498
16499 pub fn fold_recursive(
16500 &mut self,
16501 _: &actions::FoldRecursive,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 let mut to_fold = Vec::new();
16506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16507 let selections = self.selections.all_adjusted(cx);
16508
16509 for selection in selections {
16510 let range = selection.range().sorted();
16511 let buffer_start_row = range.start.row;
16512
16513 if range.start.row != range.end.row {
16514 let mut found = false;
16515 for row in range.start.row..=range.end.row {
16516 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16517 found = true;
16518 to_fold.push(crease);
16519 }
16520 }
16521 if found {
16522 continue;
16523 }
16524 }
16525
16526 for row in (0..=range.start.row).rev() {
16527 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16528 if crease.range().end.row >= buffer_start_row {
16529 to_fold.push(crease);
16530 } else {
16531 break;
16532 }
16533 }
16534 }
16535 }
16536
16537 self.fold_creases(to_fold, true, window, cx);
16538 }
16539
16540 pub fn fold_at(
16541 &mut self,
16542 buffer_row: MultiBufferRow,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) {
16546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16547
16548 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16549 let autoscroll = self
16550 .selections
16551 .all::<Point>(cx)
16552 .iter()
16553 .any(|selection| crease.range().overlaps(&selection.range()));
16554
16555 self.fold_creases(vec![crease], autoscroll, window, cx);
16556 }
16557 }
16558
16559 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16560 if self.is_singleton(cx) {
16561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16562 let buffer = &display_map.buffer_snapshot;
16563 let selections = self.selections.all::<Point>(cx);
16564 let ranges = selections
16565 .iter()
16566 .map(|s| {
16567 let range = s.display_range(&display_map).sorted();
16568 let mut start = range.start.to_point(&display_map);
16569 let mut end = range.end.to_point(&display_map);
16570 start.column = 0;
16571 end.column = buffer.line_len(MultiBufferRow(end.row));
16572 start..end
16573 })
16574 .collect::<Vec<_>>();
16575
16576 self.unfold_ranges(&ranges, true, true, cx);
16577 } else {
16578 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16579 let buffer_ids = self
16580 .selections
16581 .disjoint_anchor_ranges()
16582 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16583 .collect::<HashSet<_>>();
16584 for buffer_id in buffer_ids {
16585 self.unfold_buffer(buffer_id, cx);
16586 }
16587 }
16588 }
16589
16590 pub fn unfold_recursive(
16591 &mut self,
16592 _: &UnfoldRecursive,
16593 _window: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) {
16596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16597 let selections = self.selections.all::<Point>(cx);
16598 let ranges = selections
16599 .iter()
16600 .map(|s| {
16601 let mut range = s.display_range(&display_map).sorted();
16602 *range.start.column_mut() = 0;
16603 *range.end.column_mut() = display_map.line_len(range.end.row());
16604 let start = range.start.to_point(&display_map);
16605 let end = range.end.to_point(&display_map);
16606 start..end
16607 })
16608 .collect::<Vec<_>>();
16609
16610 self.unfold_ranges(&ranges, true, true, cx);
16611 }
16612
16613 pub fn unfold_at(
16614 &mut self,
16615 buffer_row: MultiBufferRow,
16616 _window: &mut Window,
16617 cx: &mut Context<Self>,
16618 ) {
16619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16620
16621 let intersection_range = Point::new(buffer_row.0, 0)
16622 ..Point::new(
16623 buffer_row.0,
16624 display_map.buffer_snapshot.line_len(buffer_row),
16625 );
16626
16627 let autoscroll = self
16628 .selections
16629 .all::<Point>(cx)
16630 .iter()
16631 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16632
16633 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16634 }
16635
16636 pub fn unfold_all(
16637 &mut self,
16638 _: &actions::UnfoldAll,
16639 _window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) {
16642 if self.buffer.read(cx).is_singleton() {
16643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16644 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16645 } else {
16646 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16647 editor
16648 .update(cx, |editor, cx| {
16649 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16650 editor.unfold_buffer(buffer_id, cx);
16651 }
16652 })
16653 .ok();
16654 });
16655 }
16656 }
16657
16658 pub fn fold_selected_ranges(
16659 &mut self,
16660 _: &FoldSelectedRanges,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 let selections = self.selections.all_adjusted(cx);
16665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16666 let ranges = selections
16667 .into_iter()
16668 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16669 .collect::<Vec<_>>();
16670 self.fold_creases(ranges, true, window, cx);
16671 }
16672
16673 pub fn fold_ranges<T: ToOffset + Clone>(
16674 &mut self,
16675 ranges: Vec<Range<T>>,
16676 auto_scroll: bool,
16677 window: &mut Window,
16678 cx: &mut Context<Self>,
16679 ) {
16680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16681 let ranges = ranges
16682 .into_iter()
16683 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16684 .collect::<Vec<_>>();
16685 self.fold_creases(ranges, auto_scroll, window, cx);
16686 }
16687
16688 pub fn fold_creases<T: ToOffset + Clone>(
16689 &mut self,
16690 creases: Vec<Crease<T>>,
16691 auto_scroll: bool,
16692 _window: &mut Window,
16693 cx: &mut Context<Self>,
16694 ) {
16695 if creases.is_empty() {
16696 return;
16697 }
16698
16699 let mut buffers_affected = HashSet::default();
16700 let multi_buffer = self.buffer().read(cx);
16701 for crease in &creases {
16702 if let Some((_, buffer, _)) =
16703 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16704 {
16705 buffers_affected.insert(buffer.read(cx).remote_id());
16706 };
16707 }
16708
16709 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16710
16711 if auto_scroll {
16712 self.request_autoscroll(Autoscroll::fit(), cx);
16713 }
16714
16715 cx.notify();
16716
16717 self.scrollbar_marker_state.dirty = true;
16718 self.folds_did_change(cx);
16719 }
16720
16721 /// Removes any folds whose ranges intersect any of the given ranges.
16722 pub fn unfold_ranges<T: ToOffset + Clone>(
16723 &mut self,
16724 ranges: &[Range<T>],
16725 inclusive: bool,
16726 auto_scroll: bool,
16727 cx: &mut Context<Self>,
16728 ) {
16729 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16730 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16731 });
16732 self.folds_did_change(cx);
16733 }
16734
16735 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16736 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16737 return;
16738 }
16739 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16740 self.display_map.update(cx, |display_map, cx| {
16741 display_map.fold_buffers([buffer_id], cx)
16742 });
16743 cx.emit(EditorEvent::BufferFoldToggled {
16744 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16745 folded: true,
16746 });
16747 cx.notify();
16748 }
16749
16750 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16751 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16752 return;
16753 }
16754 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16755 self.display_map.update(cx, |display_map, cx| {
16756 display_map.unfold_buffers([buffer_id], cx);
16757 });
16758 cx.emit(EditorEvent::BufferFoldToggled {
16759 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16760 folded: false,
16761 });
16762 cx.notify();
16763 }
16764
16765 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16766 self.display_map.read(cx).is_buffer_folded(buffer)
16767 }
16768
16769 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16770 self.display_map.read(cx).folded_buffers()
16771 }
16772
16773 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16774 self.display_map.update(cx, |display_map, cx| {
16775 display_map.disable_header_for_buffer(buffer_id, cx);
16776 });
16777 cx.notify();
16778 }
16779
16780 /// Removes any folds with the given ranges.
16781 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16782 &mut self,
16783 ranges: &[Range<T>],
16784 type_id: TypeId,
16785 auto_scroll: bool,
16786 cx: &mut Context<Self>,
16787 ) {
16788 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16789 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16790 });
16791 self.folds_did_change(cx);
16792 }
16793
16794 fn remove_folds_with<T: ToOffset + Clone>(
16795 &mut self,
16796 ranges: &[Range<T>],
16797 auto_scroll: bool,
16798 cx: &mut Context<Self>,
16799 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16800 ) {
16801 if ranges.is_empty() {
16802 return;
16803 }
16804
16805 let mut buffers_affected = HashSet::default();
16806 let multi_buffer = self.buffer().read(cx);
16807 for range in ranges {
16808 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16809 buffers_affected.insert(buffer.read(cx).remote_id());
16810 };
16811 }
16812
16813 self.display_map.update(cx, update);
16814
16815 if auto_scroll {
16816 self.request_autoscroll(Autoscroll::fit(), cx);
16817 }
16818
16819 cx.notify();
16820 self.scrollbar_marker_state.dirty = true;
16821 self.active_indent_guides_state.dirty = true;
16822 }
16823
16824 pub fn update_fold_widths(
16825 &mut self,
16826 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16827 cx: &mut Context<Self>,
16828 ) -> bool {
16829 self.display_map
16830 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16831 }
16832
16833 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16834 self.display_map.read(cx).fold_placeholder.clone()
16835 }
16836
16837 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16838 self.buffer.update(cx, |buffer, cx| {
16839 buffer.set_all_diff_hunks_expanded(cx);
16840 });
16841 }
16842
16843 pub fn expand_all_diff_hunks(
16844 &mut self,
16845 _: &ExpandAllDiffHunks,
16846 _window: &mut Window,
16847 cx: &mut Context<Self>,
16848 ) {
16849 self.buffer.update(cx, |buffer, cx| {
16850 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16851 });
16852 }
16853
16854 pub fn toggle_selected_diff_hunks(
16855 &mut self,
16856 _: &ToggleSelectedDiffHunks,
16857 _window: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) {
16860 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16861 self.toggle_diff_hunks_in_ranges(ranges, cx);
16862 }
16863
16864 pub fn diff_hunks_in_ranges<'a>(
16865 &'a self,
16866 ranges: &'a [Range<Anchor>],
16867 buffer: &'a MultiBufferSnapshot,
16868 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16869 ranges.iter().flat_map(move |range| {
16870 let end_excerpt_id = range.end.excerpt_id;
16871 let range = range.to_point(buffer);
16872 let mut peek_end = range.end;
16873 if range.end.row < buffer.max_row().0 {
16874 peek_end = Point::new(range.end.row + 1, 0);
16875 }
16876 buffer
16877 .diff_hunks_in_range(range.start..peek_end)
16878 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16879 })
16880 }
16881
16882 pub fn has_stageable_diff_hunks_in_ranges(
16883 &self,
16884 ranges: &[Range<Anchor>],
16885 snapshot: &MultiBufferSnapshot,
16886 ) -> bool {
16887 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16888 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16889 }
16890
16891 pub fn toggle_staged_selected_diff_hunks(
16892 &mut self,
16893 _: &::git::ToggleStaged,
16894 _: &mut Window,
16895 cx: &mut Context<Self>,
16896 ) {
16897 let snapshot = self.buffer.read(cx).snapshot(cx);
16898 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16899 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16900 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16901 }
16902
16903 pub fn set_render_diff_hunk_controls(
16904 &mut self,
16905 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16906 cx: &mut Context<Self>,
16907 ) {
16908 self.render_diff_hunk_controls = render_diff_hunk_controls;
16909 cx.notify();
16910 }
16911
16912 pub fn stage_and_next(
16913 &mut self,
16914 _: &::git::StageAndNext,
16915 window: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) {
16918 self.do_stage_or_unstage_and_next(true, window, cx);
16919 }
16920
16921 pub fn unstage_and_next(
16922 &mut self,
16923 _: &::git::UnstageAndNext,
16924 window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) {
16927 self.do_stage_or_unstage_and_next(false, window, cx);
16928 }
16929
16930 pub fn stage_or_unstage_diff_hunks(
16931 &mut self,
16932 stage: bool,
16933 ranges: Vec<Range<Anchor>>,
16934 cx: &mut Context<Self>,
16935 ) {
16936 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16937 cx.spawn(async move |this, cx| {
16938 task.await?;
16939 this.update(cx, |this, cx| {
16940 let snapshot = this.buffer.read(cx).snapshot(cx);
16941 let chunk_by = this
16942 .diff_hunks_in_ranges(&ranges, &snapshot)
16943 .chunk_by(|hunk| hunk.buffer_id);
16944 for (buffer_id, hunks) in &chunk_by {
16945 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16946 }
16947 })
16948 })
16949 .detach_and_log_err(cx);
16950 }
16951
16952 fn save_buffers_for_ranges_if_needed(
16953 &mut self,
16954 ranges: &[Range<Anchor>],
16955 cx: &mut Context<Editor>,
16956 ) -> Task<Result<()>> {
16957 let multibuffer = self.buffer.read(cx);
16958 let snapshot = multibuffer.read(cx);
16959 let buffer_ids: HashSet<_> = ranges
16960 .iter()
16961 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16962 .collect();
16963 drop(snapshot);
16964
16965 let mut buffers = HashSet::default();
16966 for buffer_id in buffer_ids {
16967 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16968 let buffer = buffer_entity.read(cx);
16969 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16970 {
16971 buffers.insert(buffer_entity);
16972 }
16973 }
16974 }
16975
16976 if let Some(project) = &self.project {
16977 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16978 } else {
16979 Task::ready(Ok(()))
16980 }
16981 }
16982
16983 fn do_stage_or_unstage_and_next(
16984 &mut self,
16985 stage: bool,
16986 window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) {
16989 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16990
16991 if ranges.iter().any(|range| range.start != range.end) {
16992 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16993 return;
16994 }
16995
16996 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16997 let snapshot = self.snapshot(window, cx);
16998 let position = self.selections.newest::<Point>(cx).head();
16999 let mut row = snapshot
17000 .buffer_snapshot
17001 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17002 .find(|hunk| hunk.row_range.start.0 > position.row)
17003 .map(|hunk| hunk.row_range.start);
17004
17005 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17006 // Outside of the project diff editor, wrap around to the beginning.
17007 if !all_diff_hunks_expanded {
17008 row = row.or_else(|| {
17009 snapshot
17010 .buffer_snapshot
17011 .diff_hunks_in_range(Point::zero()..position)
17012 .find(|hunk| hunk.row_range.end.0 < position.row)
17013 .map(|hunk| hunk.row_range.start)
17014 });
17015 }
17016
17017 if let Some(row) = row {
17018 let destination = Point::new(row.0, 0);
17019 let autoscroll = Autoscroll::center();
17020
17021 self.unfold_ranges(&[destination..destination], false, false, cx);
17022 self.change_selections(Some(autoscroll), window, cx, |s| {
17023 s.select_ranges([destination..destination]);
17024 });
17025 }
17026 }
17027
17028 fn do_stage_or_unstage(
17029 &self,
17030 stage: bool,
17031 buffer_id: BufferId,
17032 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17033 cx: &mut App,
17034 ) -> Option<()> {
17035 let project = self.project.as_ref()?;
17036 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17037 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17038 let buffer_snapshot = buffer.read(cx).snapshot();
17039 let file_exists = buffer_snapshot
17040 .file()
17041 .is_some_and(|file| file.disk_state().exists());
17042 diff.update(cx, |diff, cx| {
17043 diff.stage_or_unstage_hunks(
17044 stage,
17045 &hunks
17046 .map(|hunk| buffer_diff::DiffHunk {
17047 buffer_range: hunk.buffer_range,
17048 diff_base_byte_range: hunk.diff_base_byte_range,
17049 secondary_status: hunk.secondary_status,
17050 range: Point::zero()..Point::zero(), // unused
17051 })
17052 .collect::<Vec<_>>(),
17053 &buffer_snapshot,
17054 file_exists,
17055 cx,
17056 )
17057 });
17058 None
17059 }
17060
17061 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17062 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17063 self.buffer
17064 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17065 }
17066
17067 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17068 self.buffer.update(cx, |buffer, cx| {
17069 let ranges = vec![Anchor::min()..Anchor::max()];
17070 if !buffer.all_diff_hunks_expanded()
17071 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17072 {
17073 buffer.collapse_diff_hunks(ranges, cx);
17074 true
17075 } else {
17076 false
17077 }
17078 })
17079 }
17080
17081 fn toggle_diff_hunks_in_ranges(
17082 &mut self,
17083 ranges: Vec<Range<Anchor>>,
17084 cx: &mut Context<Editor>,
17085 ) {
17086 self.buffer.update(cx, |buffer, cx| {
17087 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17088 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17089 })
17090 }
17091
17092 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17093 self.buffer.update(cx, |buffer, cx| {
17094 let snapshot = buffer.snapshot(cx);
17095 let excerpt_id = range.end.excerpt_id;
17096 let point_range = range.to_point(&snapshot);
17097 let expand = !buffer.single_hunk_is_expanded(range, cx);
17098 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17099 })
17100 }
17101
17102 pub(crate) fn apply_all_diff_hunks(
17103 &mut self,
17104 _: &ApplyAllDiffHunks,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17109
17110 let buffers = self.buffer.read(cx).all_buffers();
17111 for branch_buffer in buffers {
17112 branch_buffer.update(cx, |branch_buffer, cx| {
17113 branch_buffer.merge_into_base(Vec::new(), cx);
17114 });
17115 }
17116
17117 if let Some(project) = self.project.clone() {
17118 self.save(true, project, window, cx).detach_and_log_err(cx);
17119 }
17120 }
17121
17122 pub(crate) fn apply_selected_diff_hunks(
17123 &mut self,
17124 _: &ApplyDiffHunk,
17125 window: &mut Window,
17126 cx: &mut Context<Self>,
17127 ) {
17128 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17129 let snapshot = self.snapshot(window, cx);
17130 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17131 let mut ranges_by_buffer = HashMap::default();
17132 self.transact(window, cx, |editor, _window, cx| {
17133 for hunk in hunks {
17134 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17135 ranges_by_buffer
17136 .entry(buffer.clone())
17137 .or_insert_with(Vec::new)
17138 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17139 }
17140 }
17141
17142 for (buffer, ranges) in ranges_by_buffer {
17143 buffer.update(cx, |buffer, cx| {
17144 buffer.merge_into_base(ranges, cx);
17145 });
17146 }
17147 });
17148
17149 if let Some(project) = self.project.clone() {
17150 self.save(true, project, window, cx).detach_and_log_err(cx);
17151 }
17152 }
17153
17154 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17155 if hovered != self.gutter_hovered {
17156 self.gutter_hovered = hovered;
17157 cx.notify();
17158 }
17159 }
17160
17161 pub fn insert_blocks(
17162 &mut self,
17163 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17164 autoscroll: Option<Autoscroll>,
17165 cx: &mut Context<Self>,
17166 ) -> Vec<CustomBlockId> {
17167 let blocks = self
17168 .display_map
17169 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17170 if let Some(autoscroll) = autoscroll {
17171 self.request_autoscroll(autoscroll, cx);
17172 }
17173 cx.notify();
17174 blocks
17175 }
17176
17177 pub fn resize_blocks(
17178 &mut self,
17179 heights: HashMap<CustomBlockId, u32>,
17180 autoscroll: Option<Autoscroll>,
17181 cx: &mut Context<Self>,
17182 ) {
17183 self.display_map
17184 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17185 if let Some(autoscroll) = autoscroll {
17186 self.request_autoscroll(autoscroll, cx);
17187 }
17188 cx.notify();
17189 }
17190
17191 pub fn replace_blocks(
17192 &mut self,
17193 renderers: HashMap<CustomBlockId, RenderBlock>,
17194 autoscroll: Option<Autoscroll>,
17195 cx: &mut Context<Self>,
17196 ) {
17197 self.display_map
17198 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17199 if let Some(autoscroll) = autoscroll {
17200 self.request_autoscroll(autoscroll, cx);
17201 }
17202 cx.notify();
17203 }
17204
17205 pub fn remove_blocks(
17206 &mut self,
17207 block_ids: HashSet<CustomBlockId>,
17208 autoscroll: Option<Autoscroll>,
17209 cx: &mut Context<Self>,
17210 ) {
17211 self.display_map.update(cx, |display_map, cx| {
17212 display_map.remove_blocks(block_ids, cx)
17213 });
17214 if let Some(autoscroll) = autoscroll {
17215 self.request_autoscroll(autoscroll, cx);
17216 }
17217 cx.notify();
17218 }
17219
17220 pub fn row_for_block(
17221 &self,
17222 block_id: CustomBlockId,
17223 cx: &mut Context<Self>,
17224 ) -> Option<DisplayRow> {
17225 self.display_map
17226 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17227 }
17228
17229 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17230 self.focused_block = Some(focused_block);
17231 }
17232
17233 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17234 self.focused_block.take()
17235 }
17236
17237 pub fn insert_creases(
17238 &mut self,
17239 creases: impl IntoIterator<Item = Crease<Anchor>>,
17240 cx: &mut Context<Self>,
17241 ) -> Vec<CreaseId> {
17242 self.display_map
17243 .update(cx, |map, cx| map.insert_creases(creases, cx))
17244 }
17245
17246 pub fn remove_creases(
17247 &mut self,
17248 ids: impl IntoIterator<Item = CreaseId>,
17249 cx: &mut Context<Self>,
17250 ) -> Vec<(CreaseId, Range<Anchor>)> {
17251 self.display_map
17252 .update(cx, |map, cx| map.remove_creases(ids, cx))
17253 }
17254
17255 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17256 self.display_map
17257 .update(cx, |map, cx| map.snapshot(cx))
17258 .longest_row()
17259 }
17260
17261 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17262 self.display_map
17263 .update(cx, |map, cx| map.snapshot(cx))
17264 .max_point()
17265 }
17266
17267 pub fn text(&self, cx: &App) -> String {
17268 self.buffer.read(cx).read(cx).text()
17269 }
17270
17271 pub fn is_empty(&self, cx: &App) -> bool {
17272 self.buffer.read(cx).read(cx).is_empty()
17273 }
17274
17275 pub fn text_option(&self, cx: &App) -> Option<String> {
17276 let text = self.text(cx);
17277 let text = text.trim();
17278
17279 if text.is_empty() {
17280 return None;
17281 }
17282
17283 Some(text.to_string())
17284 }
17285
17286 pub fn set_text(
17287 &mut self,
17288 text: impl Into<Arc<str>>,
17289 window: &mut Window,
17290 cx: &mut Context<Self>,
17291 ) {
17292 self.transact(window, cx, |this, _, cx| {
17293 this.buffer
17294 .read(cx)
17295 .as_singleton()
17296 .expect("you can only call set_text on editors for singleton buffers")
17297 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17298 });
17299 }
17300
17301 pub fn display_text(&self, cx: &mut App) -> String {
17302 self.display_map
17303 .update(cx, |map, cx| map.snapshot(cx))
17304 .text()
17305 }
17306
17307 fn create_minimap(
17308 &self,
17309 minimap_settings: MinimapSettings,
17310 window: &mut Window,
17311 cx: &mut Context<Self>,
17312 ) -> Option<Entity<Self>> {
17313 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17314 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17315 }
17316
17317 fn initialize_new_minimap(
17318 &self,
17319 minimap_settings: MinimapSettings,
17320 window: &mut Window,
17321 cx: &mut Context<Self>,
17322 ) -> Entity<Self> {
17323 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17324
17325 let mut minimap = Editor::new_internal(
17326 EditorMode::Minimap {
17327 parent: cx.weak_entity(),
17328 },
17329 self.buffer.clone(),
17330 self.project.clone(),
17331 Some(self.display_map.clone()),
17332 window,
17333 cx,
17334 );
17335 minimap.scroll_manager.clone_state(&self.scroll_manager);
17336 minimap.set_text_style_refinement(TextStyleRefinement {
17337 font_size: Some(MINIMAP_FONT_SIZE),
17338 font_weight: Some(MINIMAP_FONT_WEIGHT),
17339 ..Default::default()
17340 });
17341 minimap.update_minimap_configuration(minimap_settings, cx);
17342 cx.new(|_| minimap)
17343 }
17344
17345 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17346 let current_line_highlight = minimap_settings
17347 .current_line_highlight
17348 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17349 self.set_current_line_highlight(Some(current_line_highlight));
17350 }
17351
17352 pub fn minimap(&self) -> Option<&Entity<Self>> {
17353 self.minimap
17354 .as_ref()
17355 .filter(|_| self.minimap_visibility.visible())
17356 }
17357
17358 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17359 let mut wrap_guides = smallvec![];
17360
17361 if self.show_wrap_guides == Some(false) {
17362 return wrap_guides;
17363 }
17364
17365 let settings = self.buffer.read(cx).language_settings(cx);
17366 if settings.show_wrap_guides {
17367 match self.soft_wrap_mode(cx) {
17368 SoftWrap::Column(soft_wrap) => {
17369 wrap_guides.push((soft_wrap as usize, true));
17370 }
17371 SoftWrap::Bounded(soft_wrap) => {
17372 wrap_guides.push((soft_wrap as usize, true));
17373 }
17374 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17375 }
17376 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17377 }
17378
17379 wrap_guides
17380 }
17381
17382 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17383 let settings = self.buffer.read(cx).language_settings(cx);
17384 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17385 match mode {
17386 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17387 SoftWrap::None
17388 }
17389 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17390 language_settings::SoftWrap::PreferredLineLength => {
17391 SoftWrap::Column(settings.preferred_line_length)
17392 }
17393 language_settings::SoftWrap::Bounded => {
17394 SoftWrap::Bounded(settings.preferred_line_length)
17395 }
17396 }
17397 }
17398
17399 pub fn set_soft_wrap_mode(
17400 &mut self,
17401 mode: language_settings::SoftWrap,
17402
17403 cx: &mut Context<Self>,
17404 ) {
17405 self.soft_wrap_mode_override = Some(mode);
17406 cx.notify();
17407 }
17408
17409 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17410 self.hard_wrap = hard_wrap;
17411 cx.notify();
17412 }
17413
17414 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17415 self.text_style_refinement = Some(style);
17416 }
17417
17418 /// called by the Element so we know what style we were most recently rendered with.
17419 pub(crate) fn set_style(
17420 &mut self,
17421 style: EditorStyle,
17422 window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 // We intentionally do not inform the display map about the minimap style
17426 // so that wrapping is not recalculated and stays consistent for the editor
17427 // and its linked minimap.
17428 if !self.mode.is_minimap() {
17429 let rem_size = window.rem_size();
17430 self.display_map.update(cx, |map, cx| {
17431 map.set_font(
17432 style.text.font(),
17433 style.text.font_size.to_pixels(rem_size),
17434 cx,
17435 )
17436 });
17437 }
17438 self.style = Some(style);
17439 }
17440
17441 pub fn style(&self) -> Option<&EditorStyle> {
17442 self.style.as_ref()
17443 }
17444
17445 // Called by the element. This method is not designed to be called outside of the editor
17446 // element's layout code because it does not notify when rewrapping is computed synchronously.
17447 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17448 self.display_map
17449 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17450 }
17451
17452 pub fn set_soft_wrap(&mut self) {
17453 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17454 }
17455
17456 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17457 if self.soft_wrap_mode_override.is_some() {
17458 self.soft_wrap_mode_override.take();
17459 } else {
17460 let soft_wrap = match self.soft_wrap_mode(cx) {
17461 SoftWrap::GitDiff => return,
17462 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17463 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17464 language_settings::SoftWrap::None
17465 }
17466 };
17467 self.soft_wrap_mode_override = Some(soft_wrap);
17468 }
17469 cx.notify();
17470 }
17471
17472 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17473 let Some(workspace) = self.workspace() else {
17474 return;
17475 };
17476 let fs = workspace.read(cx).app_state().fs.clone();
17477 let current_show = TabBarSettings::get_global(cx).show;
17478 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17479 setting.show = Some(!current_show);
17480 });
17481 }
17482
17483 pub fn toggle_indent_guides(
17484 &mut self,
17485 _: &ToggleIndentGuides,
17486 _: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) {
17489 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17490 self.buffer
17491 .read(cx)
17492 .language_settings(cx)
17493 .indent_guides
17494 .enabled
17495 });
17496 self.show_indent_guides = Some(!currently_enabled);
17497 cx.notify();
17498 }
17499
17500 fn should_show_indent_guides(&self) -> Option<bool> {
17501 self.show_indent_guides
17502 }
17503
17504 pub fn toggle_line_numbers(
17505 &mut self,
17506 _: &ToggleLineNumbers,
17507 _: &mut Window,
17508 cx: &mut Context<Self>,
17509 ) {
17510 let mut editor_settings = EditorSettings::get_global(cx).clone();
17511 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17512 EditorSettings::override_global(editor_settings, cx);
17513 }
17514
17515 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17516 if let Some(show_line_numbers) = self.show_line_numbers {
17517 return show_line_numbers;
17518 }
17519 EditorSettings::get_global(cx).gutter.line_numbers
17520 }
17521
17522 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17523 self.use_relative_line_numbers
17524 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17525 }
17526
17527 pub fn toggle_relative_line_numbers(
17528 &mut self,
17529 _: &ToggleRelativeLineNumbers,
17530 _: &mut Window,
17531 cx: &mut Context<Self>,
17532 ) {
17533 let is_relative = self.should_use_relative_line_numbers(cx);
17534 self.set_relative_line_number(Some(!is_relative), cx)
17535 }
17536
17537 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17538 self.use_relative_line_numbers = is_relative;
17539 cx.notify();
17540 }
17541
17542 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17543 self.show_gutter = show_gutter;
17544 cx.notify();
17545 }
17546
17547 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17548 self.show_scrollbars = ScrollbarAxes {
17549 horizontal: show,
17550 vertical: show,
17551 };
17552 cx.notify();
17553 }
17554
17555 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17556 self.show_scrollbars.vertical = show;
17557 cx.notify();
17558 }
17559
17560 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17561 self.show_scrollbars.horizontal = show;
17562 cx.notify();
17563 }
17564
17565 pub fn set_minimap_visibility(
17566 &mut self,
17567 minimap_visibility: MinimapVisibility,
17568 window: &mut Window,
17569 cx: &mut Context<Self>,
17570 ) {
17571 if self.minimap_visibility != minimap_visibility {
17572 if minimap_visibility.visible() && self.minimap.is_none() {
17573 let minimap_settings = EditorSettings::get_global(cx).minimap;
17574 self.minimap =
17575 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17576 }
17577 self.minimap_visibility = minimap_visibility;
17578 cx.notify();
17579 }
17580 }
17581
17582 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17583 self.set_show_scrollbars(false, cx);
17584 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17585 }
17586
17587 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17588 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17589 }
17590
17591 /// Normally the text in full mode and auto height editors is padded on the
17592 /// left side by roughly half a character width for improved hit testing.
17593 ///
17594 /// Use this method to disable this for cases where this is not wanted (e.g.
17595 /// if you want to align the editor text with some other text above or below)
17596 /// or if you want to add this padding to single-line editors.
17597 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17598 self.offset_content = offset_content;
17599 cx.notify();
17600 }
17601
17602 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17603 self.show_line_numbers = Some(show_line_numbers);
17604 cx.notify();
17605 }
17606
17607 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17608 self.disable_expand_excerpt_buttons = true;
17609 cx.notify();
17610 }
17611
17612 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17613 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17614 cx.notify();
17615 }
17616
17617 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17618 self.show_code_actions = Some(show_code_actions);
17619 cx.notify();
17620 }
17621
17622 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17623 self.show_runnables = Some(show_runnables);
17624 cx.notify();
17625 }
17626
17627 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17628 self.show_breakpoints = Some(show_breakpoints);
17629 cx.notify();
17630 }
17631
17632 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17633 if self.display_map.read(cx).masked != masked {
17634 self.display_map.update(cx, |map, _| map.masked = masked);
17635 }
17636 cx.notify()
17637 }
17638
17639 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17640 self.show_wrap_guides = Some(show_wrap_guides);
17641 cx.notify();
17642 }
17643
17644 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17645 self.show_indent_guides = Some(show_indent_guides);
17646 cx.notify();
17647 }
17648
17649 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17650 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17651 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17652 if let Some(dir) = file.abs_path(cx).parent() {
17653 return Some(dir.to_owned());
17654 }
17655 }
17656
17657 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17658 return Some(project_path.path.to_path_buf());
17659 }
17660 }
17661
17662 None
17663 }
17664
17665 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17666 self.active_excerpt(cx)?
17667 .1
17668 .read(cx)
17669 .file()
17670 .and_then(|f| f.as_local())
17671 }
17672
17673 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17674 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17675 let buffer = buffer.read(cx);
17676 if let Some(project_path) = buffer.project_path(cx) {
17677 let project = self.project.as_ref()?.read(cx);
17678 project.absolute_path(&project_path, cx)
17679 } else {
17680 buffer
17681 .file()
17682 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17683 }
17684 })
17685 }
17686
17687 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17688 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17689 let project_path = buffer.read(cx).project_path(cx)?;
17690 let project = self.project.as_ref()?.read(cx);
17691 let entry = project.entry_for_path(&project_path, cx)?;
17692 let path = entry.path.to_path_buf();
17693 Some(path)
17694 })
17695 }
17696
17697 pub fn reveal_in_finder(
17698 &mut self,
17699 _: &RevealInFileManager,
17700 _window: &mut Window,
17701 cx: &mut Context<Self>,
17702 ) {
17703 if let Some(target) = self.target_file(cx) {
17704 cx.reveal_path(&target.abs_path(cx));
17705 }
17706 }
17707
17708 pub fn copy_path(
17709 &mut self,
17710 _: &zed_actions::workspace::CopyPath,
17711 _window: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 if let Some(path) = self.target_file_abs_path(cx) {
17715 if let Some(path) = path.to_str() {
17716 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17717 }
17718 }
17719 }
17720
17721 pub fn copy_relative_path(
17722 &mut self,
17723 _: &zed_actions::workspace::CopyRelativePath,
17724 _window: &mut Window,
17725 cx: &mut Context<Self>,
17726 ) {
17727 if let Some(path) = self.target_file_path(cx) {
17728 if let Some(path) = path.to_str() {
17729 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17730 }
17731 }
17732 }
17733
17734 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17735 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17736 buffer.read(cx).project_path(cx)
17737 } else {
17738 None
17739 }
17740 }
17741
17742 // Returns true if the editor handled a go-to-line request
17743 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17744 maybe!({
17745 let breakpoint_store = self.breakpoint_store.as_ref()?;
17746
17747 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17748 else {
17749 self.clear_row_highlights::<ActiveDebugLine>();
17750 return None;
17751 };
17752
17753 let position = active_stack_frame.position;
17754 let buffer_id = position.buffer_id?;
17755 let snapshot = self
17756 .project
17757 .as_ref()?
17758 .read(cx)
17759 .buffer_for_id(buffer_id, cx)?
17760 .read(cx)
17761 .snapshot();
17762
17763 let mut handled = false;
17764 for (id, ExcerptRange { context, .. }) in
17765 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17766 {
17767 if context.start.cmp(&position, &snapshot).is_ge()
17768 || context.end.cmp(&position, &snapshot).is_lt()
17769 {
17770 continue;
17771 }
17772 let snapshot = self.buffer.read(cx).snapshot(cx);
17773 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17774
17775 handled = true;
17776 self.clear_row_highlights::<ActiveDebugLine>();
17777
17778 self.go_to_line::<ActiveDebugLine>(
17779 multibuffer_anchor,
17780 Some(cx.theme().colors().editor_debugger_active_line_background),
17781 window,
17782 cx,
17783 );
17784
17785 cx.notify();
17786 }
17787
17788 handled.then_some(())
17789 })
17790 .is_some()
17791 }
17792
17793 pub fn copy_file_name_without_extension(
17794 &mut self,
17795 _: &CopyFileNameWithoutExtension,
17796 _: &mut Window,
17797 cx: &mut Context<Self>,
17798 ) {
17799 if let Some(file) = self.target_file(cx) {
17800 if let Some(file_stem) = file.path().file_stem() {
17801 if let Some(name) = file_stem.to_str() {
17802 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17803 }
17804 }
17805 }
17806 }
17807
17808 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17809 if let Some(file) = self.target_file(cx) {
17810 if let Some(file_name) = file.path().file_name() {
17811 if let Some(name) = file_name.to_str() {
17812 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17813 }
17814 }
17815 }
17816 }
17817
17818 pub fn toggle_git_blame(
17819 &mut self,
17820 _: &::git::Blame,
17821 window: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) {
17824 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17825
17826 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17827 self.start_git_blame(true, window, cx);
17828 }
17829
17830 cx.notify();
17831 }
17832
17833 pub fn toggle_git_blame_inline(
17834 &mut self,
17835 _: &ToggleGitBlameInline,
17836 window: &mut Window,
17837 cx: &mut Context<Self>,
17838 ) {
17839 self.toggle_git_blame_inline_internal(true, window, cx);
17840 cx.notify();
17841 }
17842
17843 pub fn open_git_blame_commit(
17844 &mut self,
17845 _: &OpenGitBlameCommit,
17846 window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) {
17849 self.open_git_blame_commit_internal(window, cx);
17850 }
17851
17852 fn open_git_blame_commit_internal(
17853 &mut self,
17854 window: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) -> Option<()> {
17857 let blame = self.blame.as_ref()?;
17858 let snapshot = self.snapshot(window, cx);
17859 let cursor = self.selections.newest::<Point>(cx).head();
17860 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17861 let blame_entry = blame
17862 .update(cx, |blame, cx| {
17863 blame
17864 .blame_for_rows(
17865 &[RowInfo {
17866 buffer_id: Some(buffer.remote_id()),
17867 buffer_row: Some(point.row),
17868 ..Default::default()
17869 }],
17870 cx,
17871 )
17872 .next()
17873 })
17874 .flatten()?;
17875 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17876 let repo = blame.read(cx).repository(cx)?;
17877 let workspace = self.workspace()?.downgrade();
17878 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17879 None
17880 }
17881
17882 pub fn git_blame_inline_enabled(&self) -> bool {
17883 self.git_blame_inline_enabled
17884 }
17885
17886 pub fn toggle_selection_menu(
17887 &mut self,
17888 _: &ToggleSelectionMenu,
17889 _: &mut Window,
17890 cx: &mut Context<Self>,
17891 ) {
17892 self.show_selection_menu = self
17893 .show_selection_menu
17894 .map(|show_selections_menu| !show_selections_menu)
17895 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17896
17897 cx.notify();
17898 }
17899
17900 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17901 self.show_selection_menu
17902 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17903 }
17904
17905 fn start_git_blame(
17906 &mut self,
17907 user_triggered: bool,
17908 window: &mut Window,
17909 cx: &mut Context<Self>,
17910 ) {
17911 if let Some(project) = self.project.as_ref() {
17912 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17913 return;
17914 };
17915
17916 if buffer.read(cx).file().is_none() {
17917 return;
17918 }
17919
17920 let focused = self.focus_handle(cx).contains_focused(window, cx);
17921
17922 let project = project.clone();
17923 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17924 self.blame_subscription =
17925 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17926 self.blame = Some(blame);
17927 }
17928 }
17929
17930 fn toggle_git_blame_inline_internal(
17931 &mut self,
17932 user_triggered: bool,
17933 window: &mut Window,
17934 cx: &mut Context<Self>,
17935 ) {
17936 if self.git_blame_inline_enabled {
17937 self.git_blame_inline_enabled = false;
17938 self.show_git_blame_inline = false;
17939 self.show_git_blame_inline_delay_task.take();
17940 } else {
17941 self.git_blame_inline_enabled = true;
17942 self.start_git_blame_inline(user_triggered, window, cx);
17943 }
17944
17945 cx.notify();
17946 }
17947
17948 fn start_git_blame_inline(
17949 &mut self,
17950 user_triggered: bool,
17951 window: &mut Window,
17952 cx: &mut Context<Self>,
17953 ) {
17954 self.start_git_blame(user_triggered, window, cx);
17955
17956 if ProjectSettings::get_global(cx)
17957 .git
17958 .inline_blame_delay()
17959 .is_some()
17960 {
17961 self.start_inline_blame_timer(window, cx);
17962 } else {
17963 self.show_git_blame_inline = true
17964 }
17965 }
17966
17967 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17968 self.blame.as_ref()
17969 }
17970
17971 pub fn show_git_blame_gutter(&self) -> bool {
17972 self.show_git_blame_gutter
17973 }
17974
17975 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17976 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17977 }
17978
17979 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17980 self.show_git_blame_inline
17981 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17982 && !self.newest_selection_head_on_empty_line(cx)
17983 && self.has_blame_entries(cx)
17984 }
17985
17986 fn has_blame_entries(&self, cx: &App) -> bool {
17987 self.blame()
17988 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17989 }
17990
17991 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17992 let cursor_anchor = self.selections.newest_anchor().head();
17993
17994 let snapshot = self.buffer.read(cx).snapshot(cx);
17995 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17996
17997 snapshot.line_len(buffer_row) == 0
17998 }
17999
18000 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18001 let buffer_and_selection = maybe!({
18002 let selection = self.selections.newest::<Point>(cx);
18003 let selection_range = selection.range();
18004
18005 let multi_buffer = self.buffer().read(cx);
18006 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18007 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18008
18009 let (buffer, range, _) = if selection.reversed {
18010 buffer_ranges.first()
18011 } else {
18012 buffer_ranges.last()
18013 }?;
18014
18015 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18016 ..text::ToPoint::to_point(&range.end, &buffer).row;
18017 Some((
18018 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18019 selection,
18020 ))
18021 });
18022
18023 let Some((buffer, selection)) = buffer_and_selection else {
18024 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18025 };
18026
18027 let Some(project) = self.project.as_ref() else {
18028 return Task::ready(Err(anyhow!("editor does not have project")));
18029 };
18030
18031 project.update(cx, |project, cx| {
18032 project.get_permalink_to_line(&buffer, selection, cx)
18033 })
18034 }
18035
18036 pub fn copy_permalink_to_line(
18037 &mut self,
18038 _: &CopyPermalinkToLine,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 let permalink_task = self.get_permalink_to_line(cx);
18043 let workspace = self.workspace();
18044
18045 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18046 Ok(permalink) => {
18047 cx.update(|_, cx| {
18048 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18049 })
18050 .ok();
18051 }
18052 Err(err) => {
18053 let message = format!("Failed to copy permalink: {err}");
18054
18055 anyhow::Result::<()>::Err(err).log_err();
18056
18057 if let Some(workspace) = workspace {
18058 workspace
18059 .update_in(cx, |workspace, _, cx| {
18060 struct CopyPermalinkToLine;
18061
18062 workspace.show_toast(
18063 Toast::new(
18064 NotificationId::unique::<CopyPermalinkToLine>(),
18065 message,
18066 ),
18067 cx,
18068 )
18069 })
18070 .ok();
18071 }
18072 }
18073 })
18074 .detach();
18075 }
18076
18077 pub fn copy_file_location(
18078 &mut self,
18079 _: &CopyFileLocation,
18080 _: &mut Window,
18081 cx: &mut Context<Self>,
18082 ) {
18083 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18084 if let Some(file) = self.target_file(cx) {
18085 if let Some(path) = file.path().to_str() {
18086 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18087 }
18088 }
18089 }
18090
18091 pub fn open_permalink_to_line(
18092 &mut self,
18093 _: &OpenPermalinkToLine,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 let permalink_task = self.get_permalink_to_line(cx);
18098 let workspace = self.workspace();
18099
18100 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18101 Ok(permalink) => {
18102 cx.update(|_, cx| {
18103 cx.open_url(permalink.as_ref());
18104 })
18105 .ok();
18106 }
18107 Err(err) => {
18108 let message = format!("Failed to open permalink: {err}");
18109
18110 anyhow::Result::<()>::Err(err).log_err();
18111
18112 if let Some(workspace) = workspace {
18113 workspace
18114 .update(cx, |workspace, cx| {
18115 struct OpenPermalinkToLine;
18116
18117 workspace.show_toast(
18118 Toast::new(
18119 NotificationId::unique::<OpenPermalinkToLine>(),
18120 message,
18121 ),
18122 cx,
18123 )
18124 })
18125 .ok();
18126 }
18127 }
18128 })
18129 .detach();
18130 }
18131
18132 pub fn insert_uuid_v4(
18133 &mut self,
18134 _: &InsertUuidV4,
18135 window: &mut Window,
18136 cx: &mut Context<Self>,
18137 ) {
18138 self.insert_uuid(UuidVersion::V4, window, cx);
18139 }
18140
18141 pub fn insert_uuid_v7(
18142 &mut self,
18143 _: &InsertUuidV7,
18144 window: &mut Window,
18145 cx: &mut Context<Self>,
18146 ) {
18147 self.insert_uuid(UuidVersion::V7, window, cx);
18148 }
18149
18150 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18151 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18152 self.transact(window, cx, |this, window, cx| {
18153 let edits = this
18154 .selections
18155 .all::<Point>(cx)
18156 .into_iter()
18157 .map(|selection| {
18158 let uuid = match version {
18159 UuidVersion::V4 => uuid::Uuid::new_v4(),
18160 UuidVersion::V7 => uuid::Uuid::now_v7(),
18161 };
18162
18163 (selection.range(), uuid.to_string())
18164 });
18165 this.edit(edits, cx);
18166 this.refresh_inline_completion(true, false, window, cx);
18167 });
18168 }
18169
18170 pub fn open_selections_in_multibuffer(
18171 &mut self,
18172 _: &OpenSelectionsInMultibuffer,
18173 window: &mut Window,
18174 cx: &mut Context<Self>,
18175 ) {
18176 let multibuffer = self.buffer.read(cx);
18177
18178 let Some(buffer) = multibuffer.as_singleton() else {
18179 return;
18180 };
18181
18182 let Some(workspace) = self.workspace() else {
18183 return;
18184 };
18185
18186 let locations = self
18187 .selections
18188 .disjoint_anchors()
18189 .iter()
18190 .map(|range| Location {
18191 buffer: buffer.clone(),
18192 range: range.start.text_anchor..range.end.text_anchor,
18193 })
18194 .collect::<Vec<_>>();
18195
18196 let title = multibuffer.title(cx).to_string();
18197
18198 cx.spawn_in(window, async move |_, cx| {
18199 workspace.update_in(cx, |workspace, window, cx| {
18200 Self::open_locations_in_multibuffer(
18201 workspace,
18202 locations,
18203 format!("Selections for '{title}'"),
18204 false,
18205 MultibufferSelectionMode::All,
18206 window,
18207 cx,
18208 );
18209 })
18210 })
18211 .detach();
18212 }
18213
18214 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18215 /// last highlight added will be used.
18216 ///
18217 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18218 pub fn highlight_rows<T: 'static>(
18219 &mut self,
18220 range: Range<Anchor>,
18221 color: Hsla,
18222 options: RowHighlightOptions,
18223 cx: &mut Context<Self>,
18224 ) {
18225 let snapshot = self.buffer().read(cx).snapshot(cx);
18226 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18227 let ix = row_highlights.binary_search_by(|highlight| {
18228 Ordering::Equal
18229 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18230 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18231 });
18232
18233 if let Err(mut ix) = ix {
18234 let index = post_inc(&mut self.highlight_order);
18235
18236 // If this range intersects with the preceding highlight, then merge it with
18237 // the preceding highlight. Otherwise insert a new highlight.
18238 let mut merged = false;
18239 if ix > 0 {
18240 let prev_highlight = &mut row_highlights[ix - 1];
18241 if prev_highlight
18242 .range
18243 .end
18244 .cmp(&range.start, &snapshot)
18245 .is_ge()
18246 {
18247 ix -= 1;
18248 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18249 prev_highlight.range.end = range.end;
18250 }
18251 merged = true;
18252 prev_highlight.index = index;
18253 prev_highlight.color = color;
18254 prev_highlight.options = options;
18255 }
18256 }
18257
18258 if !merged {
18259 row_highlights.insert(
18260 ix,
18261 RowHighlight {
18262 range: range.clone(),
18263 index,
18264 color,
18265 options,
18266 type_id: TypeId::of::<T>(),
18267 },
18268 );
18269 }
18270
18271 // If any of the following highlights intersect with this one, merge them.
18272 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18273 let highlight = &row_highlights[ix];
18274 if next_highlight
18275 .range
18276 .start
18277 .cmp(&highlight.range.end, &snapshot)
18278 .is_le()
18279 {
18280 if next_highlight
18281 .range
18282 .end
18283 .cmp(&highlight.range.end, &snapshot)
18284 .is_gt()
18285 {
18286 row_highlights[ix].range.end = next_highlight.range.end;
18287 }
18288 row_highlights.remove(ix + 1);
18289 } else {
18290 break;
18291 }
18292 }
18293 }
18294 }
18295
18296 /// Remove any highlighted row ranges of the given type that intersect the
18297 /// given ranges.
18298 pub fn remove_highlighted_rows<T: 'static>(
18299 &mut self,
18300 ranges_to_remove: Vec<Range<Anchor>>,
18301 cx: &mut Context<Self>,
18302 ) {
18303 let snapshot = self.buffer().read(cx).snapshot(cx);
18304 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18305 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18306 row_highlights.retain(|highlight| {
18307 while let Some(range_to_remove) = ranges_to_remove.peek() {
18308 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18309 Ordering::Less | Ordering::Equal => {
18310 ranges_to_remove.next();
18311 }
18312 Ordering::Greater => {
18313 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18314 Ordering::Less | Ordering::Equal => {
18315 return false;
18316 }
18317 Ordering::Greater => break,
18318 }
18319 }
18320 }
18321 }
18322
18323 true
18324 })
18325 }
18326
18327 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18328 pub fn clear_row_highlights<T: 'static>(&mut self) {
18329 self.highlighted_rows.remove(&TypeId::of::<T>());
18330 }
18331
18332 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18333 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18334 self.highlighted_rows
18335 .get(&TypeId::of::<T>())
18336 .map_or(&[] as &[_], |vec| vec.as_slice())
18337 .iter()
18338 .map(|highlight| (highlight.range.clone(), highlight.color))
18339 }
18340
18341 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18342 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18343 /// Allows to ignore certain kinds of highlights.
18344 pub fn highlighted_display_rows(
18345 &self,
18346 window: &mut Window,
18347 cx: &mut App,
18348 ) -> BTreeMap<DisplayRow, LineHighlight> {
18349 let snapshot = self.snapshot(window, cx);
18350 let mut used_highlight_orders = HashMap::default();
18351 self.highlighted_rows
18352 .iter()
18353 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18354 .fold(
18355 BTreeMap::<DisplayRow, LineHighlight>::new(),
18356 |mut unique_rows, highlight| {
18357 let start = highlight.range.start.to_display_point(&snapshot);
18358 let end = highlight.range.end.to_display_point(&snapshot);
18359 let start_row = start.row().0;
18360 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18361 && end.column() == 0
18362 {
18363 end.row().0.saturating_sub(1)
18364 } else {
18365 end.row().0
18366 };
18367 for row in start_row..=end_row {
18368 let used_index =
18369 used_highlight_orders.entry(row).or_insert(highlight.index);
18370 if highlight.index >= *used_index {
18371 *used_index = highlight.index;
18372 unique_rows.insert(
18373 DisplayRow(row),
18374 LineHighlight {
18375 include_gutter: highlight.options.include_gutter,
18376 border: None,
18377 background: highlight.color.into(),
18378 type_id: Some(highlight.type_id),
18379 },
18380 );
18381 }
18382 }
18383 unique_rows
18384 },
18385 )
18386 }
18387
18388 pub fn highlighted_display_row_for_autoscroll(
18389 &self,
18390 snapshot: &DisplaySnapshot,
18391 ) -> Option<DisplayRow> {
18392 self.highlighted_rows
18393 .values()
18394 .flat_map(|highlighted_rows| highlighted_rows.iter())
18395 .filter_map(|highlight| {
18396 if highlight.options.autoscroll {
18397 Some(highlight.range.start.to_display_point(snapshot).row())
18398 } else {
18399 None
18400 }
18401 })
18402 .min()
18403 }
18404
18405 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18406 self.highlight_background::<SearchWithinRange>(
18407 ranges,
18408 |theme| theme.colors().editor_document_highlight_read_background,
18409 cx,
18410 )
18411 }
18412
18413 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18414 self.breadcrumb_header = Some(new_header);
18415 }
18416
18417 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18418 self.clear_background_highlights::<SearchWithinRange>(cx);
18419 }
18420
18421 pub fn highlight_background<T: 'static>(
18422 &mut self,
18423 ranges: &[Range<Anchor>],
18424 color_fetcher: fn(&Theme) -> Hsla,
18425 cx: &mut Context<Self>,
18426 ) {
18427 let highlights = ranges
18428 .iter()
18429 .map(|range| BackgroundHighlight {
18430 range: range.clone(),
18431 color_fetcher,
18432 })
18433 .collect();
18434 self.background_highlights
18435 .insert(TypeId::of::<T>(), highlights);
18436 self.scrollbar_marker_state.dirty = true;
18437 cx.notify();
18438 }
18439
18440 pub fn highlight_background_ranges<T: 'static>(
18441 &mut self,
18442 background_highlights: Vec<BackgroundHighlight>,
18443 cx: &mut Context<'_, Editor>,
18444 ) {
18445 self.background_highlights
18446 .insert(TypeId::of::<T>(), background_highlights);
18447 self.scrollbar_marker_state.dirty = true;
18448 cx.notify();
18449 }
18450
18451 pub fn clear_background_highlights<T: 'static>(
18452 &mut self,
18453 cx: &mut Context<Self>,
18454 ) -> Option<Vec<BackgroundHighlight>> {
18455 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18456 if !text_highlights.is_empty() {
18457 self.scrollbar_marker_state.dirty = true;
18458 cx.notify();
18459 }
18460 Some(text_highlights)
18461 }
18462
18463 pub fn highlight_gutter<T: 'static>(
18464 &mut self,
18465 ranges: impl Into<Vec<Range<Anchor>>>,
18466 color_fetcher: fn(&App) -> Hsla,
18467 cx: &mut Context<Self>,
18468 ) {
18469 self.gutter_highlights
18470 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18471 cx.notify();
18472 }
18473
18474 pub fn clear_gutter_highlights<T: 'static>(
18475 &mut self,
18476 cx: &mut Context<Self>,
18477 ) -> Option<GutterHighlight> {
18478 cx.notify();
18479 self.gutter_highlights.remove(&TypeId::of::<T>())
18480 }
18481
18482 pub fn insert_gutter_highlight<T: 'static>(
18483 &mut self,
18484 range: Range<Anchor>,
18485 color_fetcher: fn(&App) -> Hsla,
18486 cx: &mut Context<Self>,
18487 ) {
18488 let snapshot = self.buffer().read(cx).snapshot(cx);
18489 let mut highlights = self
18490 .gutter_highlights
18491 .remove(&TypeId::of::<T>())
18492 .map(|(_, highlights)| highlights)
18493 .unwrap_or_default();
18494 let ix = highlights.binary_search_by(|highlight| {
18495 Ordering::Equal
18496 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18497 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18498 });
18499 if let Err(ix) = ix {
18500 highlights.insert(ix, range);
18501 }
18502 self.gutter_highlights
18503 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18504 }
18505
18506 pub fn remove_gutter_highlights<T: 'static>(
18507 &mut self,
18508 ranges_to_remove: Vec<Range<Anchor>>,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let snapshot = self.buffer().read(cx).snapshot(cx);
18512 let Some((color_fetcher, mut gutter_highlights)) =
18513 self.gutter_highlights.remove(&TypeId::of::<T>())
18514 else {
18515 return;
18516 };
18517 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18518 gutter_highlights.retain(|highlight| {
18519 while let Some(range_to_remove) = ranges_to_remove.peek() {
18520 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18521 Ordering::Less | Ordering::Equal => {
18522 ranges_to_remove.next();
18523 }
18524 Ordering::Greater => {
18525 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18526 Ordering::Less | Ordering::Equal => {
18527 return false;
18528 }
18529 Ordering::Greater => break,
18530 }
18531 }
18532 }
18533 }
18534
18535 true
18536 });
18537 self.gutter_highlights
18538 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18539 }
18540
18541 #[cfg(feature = "test-support")]
18542 pub fn all_text_background_highlights(
18543 &self,
18544 window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18547 let snapshot = self.snapshot(window, cx);
18548 let buffer = &snapshot.buffer_snapshot;
18549 let start = buffer.anchor_before(0);
18550 let end = buffer.anchor_after(buffer.len());
18551 let theme = cx.theme();
18552 self.background_highlights_in_range(start..end, &snapshot, theme)
18553 }
18554
18555 #[cfg(feature = "test-support")]
18556 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18557 let snapshot = self.buffer().read(cx).snapshot(cx);
18558
18559 let highlights = self
18560 .background_highlights
18561 .get(&TypeId::of::<items::BufferSearchHighlights>());
18562
18563 if let Some(highlights) = highlights {
18564 highlights
18565 .iter()
18566 .map(|highlight| {
18567 highlight.range.start.to_point(&snapshot)
18568 ..highlight.range.end.to_point(&snapshot)
18569 })
18570 .collect_vec()
18571 } else {
18572 vec![]
18573 }
18574 }
18575
18576 fn document_highlights_for_position<'a>(
18577 &'a self,
18578 position: Anchor,
18579 buffer: &'a MultiBufferSnapshot,
18580 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18581 let read_highlights = self
18582 .background_highlights
18583 .get(&TypeId::of::<DocumentHighlightRead>());
18584 let write_highlights = self
18585 .background_highlights
18586 .get(&TypeId::of::<DocumentHighlightWrite>());
18587 let left_position = position.bias_left(buffer);
18588 let right_position = position.bias_right(buffer);
18589 read_highlights
18590 .into_iter()
18591 .chain(write_highlights)
18592 .flat_map(move |highlights| {
18593 let start_ix = match highlights.binary_search_by(|probe| {
18594 let cmp = probe.range.end.cmp(&left_position, buffer);
18595 if cmp.is_ge() {
18596 Ordering::Greater
18597 } else {
18598 Ordering::Less
18599 }
18600 }) {
18601 Ok(i) | Err(i) => i,
18602 };
18603
18604 highlights[start_ix..]
18605 .iter()
18606 .take_while(move |highlight| {
18607 highlight.range.start.cmp(&right_position, buffer).is_le()
18608 })
18609 .map(|highlight| &highlight.range)
18610 })
18611 }
18612
18613 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18614 self.background_highlights
18615 .get(&TypeId::of::<T>())
18616 .map_or(false, |highlights| !highlights.is_empty())
18617 }
18618
18619 pub fn background_highlights_in_range(
18620 &self,
18621 search_range: Range<Anchor>,
18622 display_snapshot: &DisplaySnapshot,
18623 theme: &Theme,
18624 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18625 let mut results = Vec::new();
18626 for highlights in self.background_highlights.values() {
18627 let start_ix = match highlights.binary_search_by(|probe| {
18628 let cmp = probe
18629 .range
18630 .end
18631 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18632 if cmp.is_gt() {
18633 Ordering::Greater
18634 } else {
18635 Ordering::Less
18636 }
18637 }) {
18638 Ok(i) | Err(i) => i,
18639 };
18640 for highlight in &highlights[start_ix..] {
18641 if highlight
18642 .range
18643 .start
18644 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18645 .is_ge()
18646 {
18647 break;
18648 }
18649
18650 let start = highlight.range.start.to_display_point(display_snapshot);
18651 let end = highlight.range.end.to_display_point(display_snapshot);
18652 let color = (highlight.color_fetcher)(theme);
18653 results.push((start..end, color))
18654 }
18655 }
18656 results
18657 }
18658
18659 pub fn background_highlight_row_ranges<T: 'static>(
18660 &self,
18661 search_range: Range<Anchor>,
18662 display_snapshot: &DisplaySnapshot,
18663 count: usize,
18664 ) -> Vec<RangeInclusive<DisplayPoint>> {
18665 let mut results = Vec::new();
18666 let Some(highlights) = self.background_highlights.get(&TypeId::of::<T>()) else {
18667 return vec![];
18668 };
18669
18670 let start_ix = match highlights.binary_search_by(|probe| {
18671 let cmp = probe
18672 .range
18673 .end
18674 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18675 if cmp.is_gt() {
18676 Ordering::Greater
18677 } else {
18678 Ordering::Less
18679 }
18680 }) {
18681 Ok(i) | Err(i) => i,
18682 };
18683 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18684 if let (Some(start_display), Some(end_display)) = (start, end) {
18685 results.push(
18686 start_display.to_display_point(display_snapshot)
18687 ..=end_display.to_display_point(display_snapshot),
18688 );
18689 }
18690 };
18691 let mut start_row: Option<Point> = None;
18692 let mut end_row: Option<Point> = None;
18693 if highlights.len() > count {
18694 return Vec::new();
18695 }
18696 for highlight in &highlights[start_ix..] {
18697 if highlight
18698 .range
18699 .start
18700 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18701 .is_ge()
18702 {
18703 break;
18704 }
18705 let end = highlight
18706 .range
18707 .end
18708 .to_point(&display_snapshot.buffer_snapshot);
18709 if let Some(current_row) = &end_row {
18710 if end.row == current_row.row {
18711 continue;
18712 }
18713 }
18714 let start = highlight
18715 .range
18716 .start
18717 .to_point(&display_snapshot.buffer_snapshot);
18718 if start_row.is_none() {
18719 assert_eq!(end_row, None);
18720 start_row = Some(start);
18721 end_row = Some(end);
18722 continue;
18723 }
18724 if let Some(current_end) = end_row.as_mut() {
18725 if start.row > current_end.row + 1 {
18726 push_region(start_row, end_row);
18727 start_row = Some(start);
18728 end_row = Some(end);
18729 } else {
18730 // Merge two hunks.
18731 *current_end = end;
18732 }
18733 } else {
18734 unreachable!();
18735 }
18736 }
18737 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18738 push_region(start_row, end_row);
18739 results
18740 }
18741
18742 pub fn gutter_highlights_in_range(
18743 &self,
18744 search_range: Range<Anchor>,
18745 display_snapshot: &DisplaySnapshot,
18746 cx: &App,
18747 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18748 let mut results = Vec::new();
18749 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18750 let color = color_fetcher(cx);
18751 let start_ix = match ranges.binary_search_by(|probe| {
18752 let cmp = probe
18753 .end
18754 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18755 if cmp.is_gt() {
18756 Ordering::Greater
18757 } else {
18758 Ordering::Less
18759 }
18760 }) {
18761 Ok(i) | Err(i) => i,
18762 };
18763 for range in &ranges[start_ix..] {
18764 if range
18765 .start
18766 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18767 .is_ge()
18768 {
18769 break;
18770 }
18771
18772 let start = range.start.to_display_point(display_snapshot);
18773 let end = range.end.to_display_point(display_snapshot);
18774 results.push((start..end, color))
18775 }
18776 }
18777 results
18778 }
18779
18780 /// Get the text ranges corresponding to the redaction query
18781 pub fn redacted_ranges(
18782 &self,
18783 search_range: Range<Anchor>,
18784 display_snapshot: &DisplaySnapshot,
18785 cx: &App,
18786 ) -> Vec<Range<DisplayPoint>> {
18787 display_snapshot
18788 .buffer_snapshot
18789 .redacted_ranges(search_range, |file| {
18790 if let Some(file) = file {
18791 file.is_private()
18792 && EditorSettings::get(
18793 Some(SettingsLocation {
18794 worktree_id: file.worktree_id(cx),
18795 path: file.path().as_ref(),
18796 }),
18797 cx,
18798 )
18799 .redact_private_values
18800 } else {
18801 false
18802 }
18803 })
18804 .map(|range| {
18805 range.start.to_display_point(display_snapshot)
18806 ..range.end.to_display_point(display_snapshot)
18807 })
18808 .collect()
18809 }
18810
18811 pub fn highlight_text<T: 'static>(
18812 &mut self,
18813 ranges: Vec<(Range<Anchor>, HighlightStyle)>,
18814 cx: &mut Context<Self>,
18815 ) {
18816 self.display_map
18817 .update(cx, |map, _| map.highlight_text(TypeId::of::<T>(), ranges));
18818 cx.notify();
18819 }
18820
18821 pub(crate) fn highlight_inlays<T: 'static>(
18822 &mut self,
18823 highlights: Vec<InlayHighlight>,
18824 style: HighlightStyle,
18825 cx: &mut Context<Self>,
18826 ) {
18827 self.display_map.update(cx, |map, _| {
18828 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18829 });
18830 cx.notify();
18831 }
18832
18833 pub fn text_highlights<'a, T: 'static>(
18834 &'a self,
18835 cx: &'a App,
18836 ) -> Option<&'a [(Range<Anchor>, HighlightStyle)]> {
18837 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18838 }
18839
18840 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18841 let cleared = self
18842 .display_map
18843 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18844 if cleared {
18845 cx.notify();
18846 }
18847 }
18848
18849 pub fn remove_text_highlights<T: 'static>(
18850 &mut self,
18851 cx: &mut Context<Self>,
18852 ) -> Option<Vec<(Range<Anchor>, HighlightStyle)>> {
18853 self.display_map
18854 .update(cx, |map, _| map.remove_text_highlights(TypeId::of::<T>()))
18855 }
18856
18857 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18858 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18859 && self.focus_handle.is_focused(window)
18860 }
18861
18862 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18863 self.show_cursor_when_unfocused = is_enabled;
18864 cx.notify();
18865 }
18866
18867 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18868 cx.notify();
18869 }
18870
18871 fn on_debug_session_event(
18872 &mut self,
18873 _session: Entity<Session>,
18874 event: &SessionEvent,
18875 cx: &mut Context<Self>,
18876 ) {
18877 match event {
18878 SessionEvent::InvalidateInlineValue => {
18879 self.refresh_inline_values(cx);
18880 }
18881 _ => {}
18882 }
18883 }
18884
18885 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18886 let Some(project) = self.project.clone() else {
18887 return;
18888 };
18889
18890 if !self.inline_value_cache.enabled {
18891 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18892 self.splice_inlays(&inlays, Vec::new(), cx);
18893 return;
18894 }
18895
18896 let current_execution_position = self
18897 .highlighted_rows
18898 .get(&TypeId::of::<ActiveDebugLine>())
18899 .and_then(|lines| lines.last().map(|line| line.range.start));
18900
18901 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18902 let inline_values = editor
18903 .update(cx, |editor, cx| {
18904 let Some(current_execution_position) = current_execution_position else {
18905 return Some(Task::ready(Ok(Vec::new())));
18906 };
18907
18908 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18909 let snapshot = buffer.snapshot(cx);
18910
18911 let excerpt = snapshot.excerpt_containing(
18912 current_execution_position..current_execution_position,
18913 )?;
18914
18915 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18916 })?;
18917
18918 let range =
18919 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18920
18921 project.inline_values(buffer, range, cx)
18922 })
18923 .ok()
18924 .flatten()?
18925 .await
18926 .context("refreshing debugger inlays")
18927 .log_err()?;
18928
18929 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18930
18931 for (buffer_id, inline_value) in inline_values
18932 .into_iter()
18933 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18934 {
18935 buffer_inline_values
18936 .entry(buffer_id)
18937 .or_default()
18938 .push(inline_value);
18939 }
18940
18941 editor
18942 .update(cx, |editor, cx| {
18943 let snapshot = editor.buffer.read(cx).snapshot(cx);
18944 let mut new_inlays = Vec::default();
18945
18946 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18947 let buffer_id = buffer_snapshot.remote_id();
18948 buffer_inline_values
18949 .get(&buffer_id)
18950 .into_iter()
18951 .flatten()
18952 .for_each(|hint| {
18953 let inlay = Inlay::debugger_hint(
18954 post_inc(&mut editor.next_inlay_id),
18955 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18956 hint.text(),
18957 );
18958
18959 new_inlays.push(inlay);
18960 });
18961 }
18962
18963 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18964 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18965
18966 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18967 })
18968 .ok()?;
18969 Some(())
18970 });
18971 }
18972
18973 fn on_buffer_event(
18974 &mut self,
18975 multibuffer: &Entity<MultiBuffer>,
18976 event: &multi_buffer::Event,
18977 window: &mut Window,
18978 cx: &mut Context<Self>,
18979 ) {
18980 match event {
18981 multi_buffer::Event::Edited {
18982 singleton_buffer_edited,
18983 edited_buffer,
18984 } => {
18985 self.scrollbar_marker_state.dirty = true;
18986 self.active_indent_guides_state.dirty = true;
18987 self.refresh_active_diagnostics(cx);
18988 self.refresh_code_actions(window, cx);
18989 self.refresh_selected_text_highlights(true, window, cx);
18990 refresh_matching_bracket_highlights(self, window, cx);
18991 if self.has_active_inline_completion() {
18992 self.update_visible_inline_completion(window, cx);
18993 }
18994 if let Some(project) = self.project.as_ref() {
18995 if let Some(edited_buffer) = edited_buffer {
18996 project.update(cx, |project, cx| {
18997 self.registered_buffers
18998 .entry(edited_buffer.read(cx).remote_id())
18999 .or_insert_with(|| {
19000 project
19001 .register_buffer_with_language_servers(&edited_buffer, cx)
19002 });
19003 });
19004 if edited_buffer.read(cx).file().is_some() {
19005 self.pull_diagnostics(
19006 Some(edited_buffer.read(cx).remote_id()),
19007 window,
19008 cx,
19009 );
19010 }
19011 }
19012 }
19013 cx.emit(EditorEvent::BufferEdited);
19014 cx.emit(SearchEvent::MatchesInvalidated);
19015 if *singleton_buffer_edited {
19016 if let Some(buffer) = edited_buffer {
19017 if buffer.read(cx).file().is_none() {
19018 cx.emit(EditorEvent::TitleChanged);
19019 }
19020 }
19021 if let Some(project) = &self.project {
19022 #[allow(clippy::mutable_key_type)]
19023 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19024 multibuffer
19025 .all_buffers()
19026 .into_iter()
19027 .filter_map(|buffer| {
19028 buffer.update(cx, |buffer, cx| {
19029 let language = buffer.language()?;
19030 let should_discard = project.update(cx, |project, cx| {
19031 project.is_local()
19032 && !project.has_language_servers_for(buffer, cx)
19033 });
19034 should_discard.not().then_some(language.clone())
19035 })
19036 })
19037 .collect::<HashSet<_>>()
19038 });
19039 if !languages_affected.is_empty() {
19040 self.refresh_inlay_hints(
19041 InlayHintRefreshReason::BufferEdited(languages_affected),
19042 cx,
19043 );
19044 }
19045 }
19046 }
19047
19048 let Some(project) = &self.project else { return };
19049 let (telemetry, is_via_ssh) = {
19050 let project = project.read(cx);
19051 let telemetry = project.client().telemetry().clone();
19052 let is_via_ssh = project.is_via_ssh();
19053 (telemetry, is_via_ssh)
19054 };
19055 refresh_linked_ranges(self, window, cx);
19056 telemetry.log_edit_event("editor", is_via_ssh);
19057 }
19058 multi_buffer::Event::ExcerptsAdded {
19059 buffer,
19060 predecessor,
19061 excerpts,
19062 } => {
19063 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19064 let buffer_id = buffer.read(cx).remote_id();
19065 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19066 if let Some(project) = &self.project {
19067 update_uncommitted_diff_for_buffer(
19068 cx.entity(),
19069 project,
19070 [buffer.clone()],
19071 self.buffer.clone(),
19072 cx,
19073 )
19074 .detach();
19075 }
19076 }
19077 cx.emit(EditorEvent::ExcerptsAdded {
19078 buffer: buffer.clone(),
19079 predecessor: *predecessor,
19080 excerpts: excerpts.clone(),
19081 });
19082 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19083 }
19084 multi_buffer::Event::ExcerptsRemoved {
19085 ids,
19086 removed_buffer_ids,
19087 } => {
19088 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19089 let buffer = self.buffer.read(cx);
19090 self.registered_buffers
19091 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19092 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19093 cx.emit(EditorEvent::ExcerptsRemoved {
19094 ids: ids.clone(),
19095 removed_buffer_ids: removed_buffer_ids.clone(),
19096 })
19097 }
19098 multi_buffer::Event::ExcerptsEdited {
19099 excerpt_ids,
19100 buffer_ids,
19101 } => {
19102 self.display_map.update(cx, |map, cx| {
19103 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19104 });
19105 cx.emit(EditorEvent::ExcerptsEdited {
19106 ids: excerpt_ids.clone(),
19107 })
19108 }
19109 multi_buffer::Event::ExcerptsExpanded { ids } => {
19110 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19111 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19112 }
19113 multi_buffer::Event::Reparsed(buffer_id) => {
19114 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19115 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19116
19117 cx.emit(EditorEvent::Reparsed(*buffer_id));
19118 }
19119 multi_buffer::Event::DiffHunksToggled => {
19120 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19121 }
19122 multi_buffer::Event::LanguageChanged(buffer_id) => {
19123 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19124 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19125 cx.emit(EditorEvent::Reparsed(*buffer_id));
19126 cx.notify();
19127 }
19128 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19129 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19130 multi_buffer::Event::FileHandleChanged
19131 | multi_buffer::Event::Reloaded
19132 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19133 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19134 multi_buffer::Event::DiagnosticsUpdated => {
19135 self.update_diagnostics_state(window, cx);
19136 }
19137 _ => {}
19138 };
19139 }
19140
19141 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19142 self.refresh_active_diagnostics(cx);
19143 self.refresh_inline_diagnostics(true, window, cx);
19144 self.scrollbar_marker_state.dirty = true;
19145 cx.notify();
19146 }
19147
19148 pub fn start_temporary_diff_override(&mut self) {
19149 self.load_diff_task.take();
19150 self.temporary_diff_override = true;
19151 }
19152
19153 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19154 self.temporary_diff_override = false;
19155 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19156 self.buffer.update(cx, |buffer, cx| {
19157 buffer.set_all_diff_hunks_collapsed(cx);
19158 });
19159
19160 if let Some(project) = self.project.clone() {
19161 self.load_diff_task = Some(
19162 update_uncommitted_diff_for_buffer(
19163 cx.entity(),
19164 &project,
19165 self.buffer.read(cx).all_buffers(),
19166 self.buffer.clone(),
19167 cx,
19168 )
19169 .shared(),
19170 );
19171 }
19172 }
19173
19174 fn on_display_map_changed(
19175 &mut self,
19176 _: Entity<DisplayMap>,
19177 _: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 cx.notify();
19181 }
19182
19183 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19184 let new_severity = if self.diagnostics_enabled() {
19185 EditorSettings::get_global(cx)
19186 .diagnostics_max_severity
19187 .unwrap_or(DiagnosticSeverity::Hint)
19188 } else {
19189 DiagnosticSeverity::Off
19190 };
19191 self.set_max_diagnostics_severity(new_severity, cx);
19192 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19193 self.update_edit_prediction_settings(cx);
19194 self.refresh_inline_completion(true, false, window, cx);
19195 self.refresh_inlay_hints(
19196 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19197 self.selections.newest_anchor().head(),
19198 &self.buffer.read(cx).snapshot(cx),
19199 cx,
19200 )),
19201 cx,
19202 );
19203
19204 let old_cursor_shape = self.cursor_shape;
19205
19206 {
19207 let editor_settings = EditorSettings::get_global(cx);
19208 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19209 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19210 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19211 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19212 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19213 }
19214
19215 if old_cursor_shape != self.cursor_shape {
19216 cx.emit(EditorEvent::CursorShapeChanged);
19217 }
19218
19219 let project_settings = ProjectSettings::get_global(cx);
19220 self.serialize_dirty_buffers =
19221 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19222
19223 if self.mode.is_full() {
19224 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19225 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19226 if self.show_inline_diagnostics != show_inline_diagnostics {
19227 self.show_inline_diagnostics = show_inline_diagnostics;
19228 self.refresh_inline_diagnostics(false, window, cx);
19229 }
19230
19231 if self.git_blame_inline_enabled != inline_blame_enabled {
19232 self.toggle_git_blame_inline_internal(false, window, cx);
19233 }
19234
19235 let minimap_settings = EditorSettings::get_global(cx).minimap;
19236 if self.minimap_visibility != MinimapVisibility::Disabled {
19237 if self.minimap_visibility.settings_visibility()
19238 != minimap_settings.minimap_enabled()
19239 {
19240 self.set_minimap_visibility(
19241 MinimapVisibility::for_mode(self.mode(), cx),
19242 window,
19243 cx,
19244 );
19245 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19246 minimap_entity.update(cx, |minimap_editor, cx| {
19247 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19248 })
19249 }
19250 }
19251 }
19252
19253 cx.notify();
19254 }
19255
19256 pub fn set_searchable(&mut self, searchable: bool) {
19257 self.searchable = searchable;
19258 }
19259
19260 pub fn searchable(&self) -> bool {
19261 self.searchable
19262 }
19263
19264 fn open_proposed_changes_editor(
19265 &mut self,
19266 _: &OpenProposedChangesEditor,
19267 window: &mut Window,
19268 cx: &mut Context<Self>,
19269 ) {
19270 let Some(workspace) = self.workspace() else {
19271 cx.propagate();
19272 return;
19273 };
19274
19275 let selections = self.selections.all::<usize>(cx);
19276 let multi_buffer = self.buffer.read(cx);
19277 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19278 let mut new_selections_by_buffer = HashMap::default();
19279 for selection in selections {
19280 for (buffer, range, _) in
19281 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19282 {
19283 let mut range = range.to_point(buffer);
19284 range.start.column = 0;
19285 range.end.column = buffer.line_len(range.end.row);
19286 new_selections_by_buffer
19287 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19288 .or_insert(Vec::new())
19289 .push(range)
19290 }
19291 }
19292
19293 let proposed_changes_buffers = new_selections_by_buffer
19294 .into_iter()
19295 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19296 .collect::<Vec<_>>();
19297 let proposed_changes_editor = cx.new(|cx| {
19298 ProposedChangesEditor::new(
19299 "Proposed changes",
19300 proposed_changes_buffers,
19301 self.project.clone(),
19302 window,
19303 cx,
19304 )
19305 });
19306
19307 window.defer(cx, move |window, cx| {
19308 workspace.update(cx, |workspace, cx| {
19309 workspace.active_pane().update(cx, |pane, cx| {
19310 pane.add_item(
19311 Box::new(proposed_changes_editor),
19312 true,
19313 true,
19314 None,
19315 window,
19316 cx,
19317 );
19318 });
19319 });
19320 });
19321 }
19322
19323 pub fn open_excerpts_in_split(
19324 &mut self,
19325 _: &OpenExcerptsSplit,
19326 window: &mut Window,
19327 cx: &mut Context<Self>,
19328 ) {
19329 self.open_excerpts_common(None, true, window, cx)
19330 }
19331
19332 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19333 self.open_excerpts_common(None, false, window, cx)
19334 }
19335
19336 fn open_excerpts_common(
19337 &mut self,
19338 jump_data: Option<JumpData>,
19339 split: bool,
19340 window: &mut Window,
19341 cx: &mut Context<Self>,
19342 ) {
19343 let Some(workspace) = self.workspace() else {
19344 cx.propagate();
19345 return;
19346 };
19347
19348 if self.buffer.read(cx).is_singleton() {
19349 cx.propagate();
19350 return;
19351 }
19352
19353 let mut new_selections_by_buffer = HashMap::default();
19354 match &jump_data {
19355 Some(JumpData::MultiBufferPoint {
19356 excerpt_id,
19357 position,
19358 anchor,
19359 line_offset_from_top,
19360 }) => {
19361 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19362 if let Some(buffer) = multi_buffer_snapshot
19363 .buffer_id_for_excerpt(*excerpt_id)
19364 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19365 {
19366 let buffer_snapshot = buffer.read(cx).snapshot();
19367 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19368 language::ToPoint::to_point(anchor, &buffer_snapshot)
19369 } else {
19370 buffer_snapshot.clip_point(*position, Bias::Left)
19371 };
19372 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19373 new_selections_by_buffer.insert(
19374 buffer,
19375 (
19376 vec![jump_to_offset..jump_to_offset],
19377 Some(*line_offset_from_top),
19378 ),
19379 );
19380 }
19381 }
19382 Some(JumpData::MultiBufferRow {
19383 row,
19384 line_offset_from_top,
19385 }) => {
19386 let point = MultiBufferPoint::new(row.0, 0);
19387 if let Some((buffer, buffer_point, _)) =
19388 self.buffer.read(cx).point_to_buffer_point(point, cx)
19389 {
19390 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19391 new_selections_by_buffer
19392 .entry(buffer)
19393 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19394 .0
19395 .push(buffer_offset..buffer_offset)
19396 }
19397 }
19398 None => {
19399 let selections = self.selections.all::<usize>(cx);
19400 let multi_buffer = self.buffer.read(cx);
19401 for selection in selections {
19402 for (snapshot, range, _, anchor) in multi_buffer
19403 .snapshot(cx)
19404 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19405 {
19406 if let Some(anchor) = anchor {
19407 // selection is in a deleted hunk
19408 let Some(buffer_id) = anchor.buffer_id else {
19409 continue;
19410 };
19411 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19412 continue;
19413 };
19414 let offset = text::ToOffset::to_offset(
19415 &anchor.text_anchor,
19416 &buffer_handle.read(cx).snapshot(),
19417 );
19418 let range = offset..offset;
19419 new_selections_by_buffer
19420 .entry(buffer_handle)
19421 .or_insert((Vec::new(), None))
19422 .0
19423 .push(range)
19424 } else {
19425 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19426 else {
19427 continue;
19428 };
19429 new_selections_by_buffer
19430 .entry(buffer_handle)
19431 .or_insert((Vec::new(), None))
19432 .0
19433 .push(range)
19434 }
19435 }
19436 }
19437 }
19438 }
19439
19440 new_selections_by_buffer
19441 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19442
19443 if new_selections_by_buffer.is_empty() {
19444 return;
19445 }
19446
19447 // We defer the pane interaction because we ourselves are a workspace item
19448 // and activating a new item causes the pane to call a method on us reentrantly,
19449 // which panics if we're on the stack.
19450 window.defer(cx, move |window, cx| {
19451 workspace.update(cx, |workspace, cx| {
19452 let pane = if split {
19453 workspace.adjacent_pane(window, cx)
19454 } else {
19455 workspace.active_pane().clone()
19456 };
19457
19458 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19459 let editor = buffer
19460 .read(cx)
19461 .file()
19462 .is_none()
19463 .then(|| {
19464 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19465 // so `workspace.open_project_item` will never find them, always opening a new editor.
19466 // Instead, we try to activate the existing editor in the pane first.
19467 let (editor, pane_item_index) =
19468 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19469 let editor = item.downcast::<Editor>()?;
19470 let singleton_buffer =
19471 editor.read(cx).buffer().read(cx).as_singleton()?;
19472 if singleton_buffer == buffer {
19473 Some((editor, i))
19474 } else {
19475 None
19476 }
19477 })?;
19478 pane.update(cx, |pane, cx| {
19479 pane.activate_item(pane_item_index, true, true, window, cx)
19480 });
19481 Some(editor)
19482 })
19483 .flatten()
19484 .unwrap_or_else(|| {
19485 workspace.open_project_item::<Self>(
19486 pane.clone(),
19487 buffer,
19488 true,
19489 true,
19490 window,
19491 cx,
19492 )
19493 });
19494
19495 editor.update(cx, |editor, cx| {
19496 let autoscroll = match scroll_offset {
19497 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19498 None => Autoscroll::newest(),
19499 };
19500 let nav_history = editor.nav_history.take();
19501 editor.change_selections(Some(autoscroll), window, cx, |s| {
19502 s.select_ranges(ranges);
19503 });
19504 editor.nav_history = nav_history;
19505 });
19506 }
19507 })
19508 });
19509 }
19510
19511 // For now, don't allow opening excerpts in buffers that aren't backed by
19512 // regular project files.
19513 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19514 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19515 }
19516
19517 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19518 let snapshot = self.buffer.read(cx).read(cx);
19519 let ranges = self.text_highlights::<InputComposition>(cx)?;
19520 Some(
19521 ranges
19522 .iter()
19523 .map(move |(range, _)| {
19524 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19525 })
19526 .collect(),
19527 )
19528 }
19529
19530 fn selection_replacement_ranges(
19531 &self,
19532 range: Range<OffsetUtf16>,
19533 cx: &mut App,
19534 ) -> Vec<Range<OffsetUtf16>> {
19535 let selections = self.selections.all::<OffsetUtf16>(cx);
19536 let newest_selection = selections
19537 .iter()
19538 .max_by_key(|selection| selection.id)
19539 .unwrap();
19540 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19541 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19542 let snapshot = self.buffer.read(cx).read(cx);
19543 selections
19544 .into_iter()
19545 .map(|mut selection| {
19546 selection.start.0 =
19547 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19548 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19549 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19550 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19551 })
19552 .collect()
19553 }
19554
19555 fn report_editor_event(
19556 &self,
19557 event_type: &'static str,
19558 file_extension: Option<String>,
19559 cx: &App,
19560 ) {
19561 if cfg!(any(test, feature = "test-support")) {
19562 return;
19563 }
19564
19565 let Some(project) = &self.project else { return };
19566
19567 // If None, we are in a file without an extension
19568 let file = self
19569 .buffer
19570 .read(cx)
19571 .as_singleton()
19572 .and_then(|b| b.read(cx).file());
19573 let file_extension = file_extension.or(file
19574 .as_ref()
19575 .and_then(|file| Path::new(file.file_name(cx)).extension())
19576 .and_then(|e| e.to_str())
19577 .map(|a| a.to_string()));
19578
19579 let vim_mode = vim_enabled(cx);
19580
19581 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19582 let copilot_enabled = edit_predictions_provider
19583 == language::language_settings::EditPredictionProvider::Copilot;
19584 let copilot_enabled_for_language = self
19585 .buffer
19586 .read(cx)
19587 .language_settings(cx)
19588 .show_edit_predictions;
19589
19590 let project = project.read(cx);
19591 telemetry::event!(
19592 event_type,
19593 file_extension,
19594 vim_mode,
19595 copilot_enabled,
19596 copilot_enabled_for_language,
19597 edit_predictions_provider,
19598 is_via_ssh = project.is_via_ssh(),
19599 );
19600 }
19601
19602 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19603 /// with each line being an array of {text, highlight} objects.
19604 fn copy_highlight_json(
19605 &mut self,
19606 _: &CopyHighlightJson,
19607 window: &mut Window,
19608 cx: &mut Context<Self>,
19609 ) {
19610 #[derive(Serialize)]
19611 struct Chunk<'a> {
19612 text: String,
19613 highlight: Option<&'a str>,
19614 }
19615
19616 let snapshot = self.buffer.read(cx).snapshot(cx);
19617 let range = self
19618 .selected_text_range(false, window, cx)
19619 .and_then(|selection| {
19620 if selection.range.is_empty() {
19621 None
19622 } else {
19623 Some(selection.range)
19624 }
19625 })
19626 .unwrap_or_else(|| 0..snapshot.len());
19627
19628 let chunks = snapshot.chunks(range, true);
19629 let mut lines = Vec::new();
19630 let mut line: VecDeque<Chunk> = VecDeque::new();
19631
19632 let Some(style) = self.style.as_ref() else {
19633 return;
19634 };
19635
19636 for chunk in chunks {
19637 let highlight = chunk
19638 .syntax_highlight_id
19639 .and_then(|id| id.name(&style.syntax));
19640 let mut chunk_lines = chunk.text.split('\n').peekable();
19641 while let Some(text) = chunk_lines.next() {
19642 let mut merged_with_last_token = false;
19643 if let Some(last_token) = line.back_mut() {
19644 if last_token.highlight == highlight {
19645 last_token.text.push_str(text);
19646 merged_with_last_token = true;
19647 }
19648 }
19649
19650 if !merged_with_last_token {
19651 line.push_back(Chunk {
19652 text: text.into(),
19653 highlight,
19654 });
19655 }
19656
19657 if chunk_lines.peek().is_some() {
19658 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19659 line.pop_front();
19660 }
19661 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19662 line.pop_back();
19663 }
19664
19665 lines.push(mem::take(&mut line));
19666 }
19667 }
19668 }
19669
19670 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19671 return;
19672 };
19673 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19674 }
19675
19676 pub fn open_context_menu(
19677 &mut self,
19678 _: &OpenContextMenu,
19679 window: &mut Window,
19680 cx: &mut Context<Self>,
19681 ) {
19682 self.request_autoscroll(Autoscroll::newest(), cx);
19683 let position = self.selections.newest_display(cx).start;
19684 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19685 }
19686
19687 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19688 &self.inlay_hint_cache
19689 }
19690
19691 pub fn replay_insert_event(
19692 &mut self,
19693 text: &str,
19694 relative_utf16_range: Option<Range<isize>>,
19695 window: &mut Window,
19696 cx: &mut Context<Self>,
19697 ) {
19698 if !self.input_enabled {
19699 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19700 return;
19701 }
19702 if let Some(relative_utf16_range) = relative_utf16_range {
19703 let selections = self.selections.all::<OffsetUtf16>(cx);
19704 self.change_selections(None, window, cx, |s| {
19705 let new_ranges = selections.into_iter().map(|range| {
19706 let start = OffsetUtf16(
19707 range
19708 .head()
19709 .0
19710 .saturating_add_signed(relative_utf16_range.start),
19711 );
19712 let end = OffsetUtf16(
19713 range
19714 .head()
19715 .0
19716 .saturating_add_signed(relative_utf16_range.end),
19717 );
19718 start..end
19719 });
19720 s.select_ranges(new_ranges);
19721 });
19722 }
19723
19724 self.handle_input(text, window, cx);
19725 }
19726
19727 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19728 let Some(provider) = self.semantics_provider.as_ref() else {
19729 return false;
19730 };
19731
19732 let mut supports = false;
19733 self.buffer().update(cx, |this, cx| {
19734 this.for_each_buffer(|buffer| {
19735 supports |= provider.supports_inlay_hints(buffer, cx);
19736 });
19737 });
19738
19739 supports
19740 }
19741
19742 pub fn is_focused(&self, window: &Window) -> bool {
19743 self.focus_handle.is_focused(window)
19744 }
19745
19746 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19747 cx.emit(EditorEvent::Focused);
19748
19749 if let Some(descendant) = self
19750 .last_focused_descendant
19751 .take()
19752 .and_then(|descendant| descendant.upgrade())
19753 {
19754 window.focus(&descendant);
19755 } else {
19756 if let Some(blame) = self.blame.as_ref() {
19757 blame.update(cx, GitBlame::focus)
19758 }
19759
19760 self.blink_manager.update(cx, BlinkManager::enable);
19761 self.show_cursor_names(window, cx);
19762 self.buffer.update(cx, |buffer, cx| {
19763 buffer.finalize_last_transaction(cx);
19764 if self.leader_id.is_none() {
19765 buffer.set_active_selections(
19766 &self.selections.disjoint_anchors(),
19767 self.selections.line_mode,
19768 self.cursor_shape,
19769 cx,
19770 );
19771 }
19772 });
19773 }
19774 }
19775
19776 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19777 cx.emit(EditorEvent::FocusedIn)
19778 }
19779
19780 fn handle_focus_out(
19781 &mut self,
19782 event: FocusOutEvent,
19783 _window: &mut Window,
19784 cx: &mut Context<Self>,
19785 ) {
19786 if event.blurred != self.focus_handle {
19787 self.last_focused_descendant = Some(event.blurred);
19788 }
19789 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19790 }
19791
19792 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19793 self.blink_manager.update(cx, BlinkManager::disable);
19794 self.buffer
19795 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19796
19797 if let Some(blame) = self.blame.as_ref() {
19798 blame.update(cx, GitBlame::blur)
19799 }
19800 if !self.hover_state.focused(window, cx) {
19801 hide_hover(self, cx);
19802 }
19803 if !self
19804 .context_menu
19805 .borrow()
19806 .as_ref()
19807 .is_some_and(|context_menu| context_menu.focused(window, cx))
19808 {
19809 self.hide_context_menu(window, cx);
19810 }
19811 self.discard_inline_completion(false, cx);
19812 cx.emit(EditorEvent::Blurred);
19813 cx.notify();
19814 }
19815
19816 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19817 let mut pending: String = window
19818 .pending_input_keystrokes()
19819 .into_iter()
19820 .flatten()
19821 .filter_map(|keystroke| {
19822 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19823 keystroke.key_char.clone()
19824 } else {
19825 None
19826 }
19827 })
19828 .collect();
19829
19830 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19831 pending = "".to_string();
19832 }
19833
19834 let existing_pending = self.text_highlights::<PendingInput>(cx).map(|ranges| {
19835 ranges
19836 .iter()
19837 .map(|(range, _)| range.clone())
19838 .collect::<Vec<_>>()
19839 });
19840 if existing_pending.is_none() && pending.is_empty() {
19841 return;
19842 }
19843 let transaction =
19844 self.transact(window, cx, |this, window, cx| {
19845 let selections = this.selections.all::<usize>(cx);
19846 let edits = selections
19847 .iter()
19848 .map(|selection| (selection.end..selection.end, pending.clone()));
19849 this.edit(edits, cx);
19850 this.change_selections(None, window, cx, |s| {
19851 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19852 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19853 }));
19854 });
19855 if let Some(existing_ranges) = existing_pending {
19856 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19857 this.edit(edits, cx);
19858 }
19859 });
19860
19861 let snapshot = self.snapshot(window, cx);
19862 let ranges = self
19863 .selections
19864 .all::<usize>(cx)
19865 .into_iter()
19866 .map(|selection| {
19867 (
19868 snapshot.buffer_snapshot.anchor_after(selection.end)
19869 ..snapshot
19870 .buffer_snapshot
19871 .anchor_before(selection.end + pending.len()),
19872 HighlightStyle {
19873 underline: Some(UnderlineStyle {
19874 thickness: px(1.),
19875 color: None,
19876 wavy: false,
19877 }),
19878 ..Default::default()
19879 },
19880 )
19881 })
19882 .collect();
19883
19884 if pending.is_empty() {
19885 self.clear_highlights::<PendingInput>(cx);
19886 } else {
19887 self.highlight_text::<PendingInput>(ranges, cx);
19888 }
19889
19890 self.ime_transaction = self.ime_transaction.or(transaction);
19891 if let Some(transaction) = self.ime_transaction {
19892 self.buffer.update(cx, |buffer, cx| {
19893 buffer.group_until_transaction(transaction, cx);
19894 });
19895 }
19896
19897 if self.text_highlights::<PendingInput>(cx).is_none() {
19898 self.ime_transaction.take();
19899 }
19900 }
19901
19902 pub fn register_action_renderer(
19903 &mut self,
19904 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19905 ) -> Subscription {
19906 let id = self.next_editor_action_id.post_inc();
19907 self.editor_actions
19908 .borrow_mut()
19909 .insert(id, Box::new(listener));
19910
19911 let editor_actions = self.editor_actions.clone();
19912 Subscription::new(move || {
19913 editor_actions.borrow_mut().remove(&id);
19914 })
19915 }
19916
19917 pub fn register_action<A: Action>(
19918 &mut self,
19919 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19920 ) -> Subscription {
19921 let id = self.next_editor_action_id.post_inc();
19922 let listener = Arc::new(listener);
19923 self.editor_actions.borrow_mut().insert(
19924 id,
19925 Box::new(move |_, window, _| {
19926 let listener = listener.clone();
19927 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19928 let action = action.downcast_ref().unwrap();
19929 if phase == DispatchPhase::Bubble {
19930 listener(action, window, cx)
19931 }
19932 })
19933 }),
19934 );
19935
19936 let editor_actions = self.editor_actions.clone();
19937 Subscription::new(move || {
19938 editor_actions.borrow_mut().remove(&id);
19939 })
19940 }
19941
19942 pub fn file_header_size(&self) -> u32 {
19943 FILE_HEADER_HEIGHT
19944 }
19945
19946 pub fn restore(
19947 &mut self,
19948 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19949 window: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 let workspace = self.workspace();
19953 let project = self.project.as_ref();
19954 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19955 let mut tasks = Vec::new();
19956 for (buffer_id, changes) in revert_changes {
19957 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19958 buffer.update(cx, |buffer, cx| {
19959 buffer.edit(
19960 changes
19961 .into_iter()
19962 .map(|(range, text)| (range, text.to_string())),
19963 None,
19964 cx,
19965 );
19966 });
19967
19968 if let Some(project) =
19969 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19970 {
19971 project.update(cx, |project, cx| {
19972 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19973 })
19974 }
19975 }
19976 }
19977 tasks
19978 });
19979 cx.spawn_in(window, async move |_, cx| {
19980 for (buffer, task) in save_tasks {
19981 let result = task.await;
19982 if result.is_err() {
19983 let Some(path) = buffer
19984 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19985 .ok()
19986 else {
19987 continue;
19988 };
19989 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19990 let Some(task) = cx
19991 .update_window_entity(&workspace, |workspace, window, cx| {
19992 workspace
19993 .open_path_preview(path, None, false, false, false, window, cx)
19994 })
19995 .ok()
19996 else {
19997 continue;
19998 };
19999 task.await.log_err();
20000 }
20001 }
20002 }
20003 })
20004 .detach();
20005 self.change_selections(None, window, cx, |selections| selections.refresh());
20006 }
20007
20008 pub fn to_pixel_point(
20009 &self,
20010 source: multi_buffer::Anchor,
20011 editor_snapshot: &EditorSnapshot,
20012 window: &mut Window,
20013 ) -> Option<gpui::Point<Pixels>> {
20014 let source_point = source.to_display_point(editor_snapshot);
20015 self.display_to_pixel_point(source_point, editor_snapshot, window)
20016 }
20017
20018 pub fn display_to_pixel_point(
20019 &self,
20020 source: DisplayPoint,
20021 editor_snapshot: &EditorSnapshot,
20022 window: &mut Window,
20023 ) -> Option<gpui::Point<Pixels>> {
20024 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20025 let text_layout_details = self.text_layout_details(window);
20026 let scroll_top = text_layout_details
20027 .scroll_anchor
20028 .scroll_position(editor_snapshot)
20029 .y;
20030
20031 if source.row().as_f32() < scroll_top.floor() {
20032 return None;
20033 }
20034 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20035 let source_y = line_height * (source.row().as_f32() - scroll_top);
20036 Some(gpui::Point::new(source_x, source_y))
20037 }
20038
20039 pub fn has_visible_completions_menu(&self) -> bool {
20040 !self.edit_prediction_preview_is_active()
20041 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20042 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20043 })
20044 }
20045
20046 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20047 if self.mode.is_minimap() {
20048 return;
20049 }
20050 self.addons
20051 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20052 }
20053
20054 pub fn unregister_addon<T: Addon>(&mut self) {
20055 self.addons.remove(&std::any::TypeId::of::<T>());
20056 }
20057
20058 pub fn addon<T: Addon>(&self) -> Option<&T> {
20059 let type_id = std::any::TypeId::of::<T>();
20060 self.addons
20061 .get(&type_id)
20062 .and_then(|item| item.to_any().downcast_ref::<T>())
20063 }
20064
20065 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20066 let type_id = std::any::TypeId::of::<T>();
20067 self.addons
20068 .get_mut(&type_id)
20069 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20070 }
20071
20072 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20073 let text_layout_details = self.text_layout_details(window);
20074 let style = &text_layout_details.editor_style;
20075 let font_id = window.text_system().resolve_font(&style.text.font());
20076 let font_size = style.text.font_size.to_pixels(window.rem_size());
20077 let line_height = style.text.line_height_in_pixels(window.rem_size());
20078 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20079
20080 gpui::Size::new(em_width, line_height)
20081 }
20082
20083 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20084 self.load_diff_task.clone()
20085 }
20086
20087 fn read_metadata_from_db(
20088 &mut self,
20089 item_id: u64,
20090 workspace_id: WorkspaceId,
20091 window: &mut Window,
20092 cx: &mut Context<Editor>,
20093 ) {
20094 if self.is_singleton(cx)
20095 && !self.mode.is_minimap()
20096 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20097 {
20098 let buffer_snapshot = OnceCell::new();
20099
20100 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20101 if !folds.is_empty() {
20102 let snapshot =
20103 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20104 self.fold_ranges(
20105 folds
20106 .into_iter()
20107 .map(|(start, end)| {
20108 snapshot.clip_offset(start, Bias::Left)
20109 ..snapshot.clip_offset(end, Bias::Right)
20110 })
20111 .collect(),
20112 false,
20113 window,
20114 cx,
20115 );
20116 }
20117 }
20118
20119 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20120 if !selections.is_empty() {
20121 let snapshot =
20122 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20123 // skip adding the initial selection to selection history
20124 self.selection_history.mode = SelectionHistoryMode::Skipping;
20125 self.change_selections(None, window, cx, |s| {
20126 s.select_ranges(selections.into_iter().map(|(start, end)| {
20127 snapshot.clip_offset(start, Bias::Left)
20128 ..snapshot.clip_offset(end, Bias::Right)
20129 }));
20130 });
20131 self.selection_history.mode = SelectionHistoryMode::Normal;
20132 }
20133 };
20134 }
20135
20136 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20137 }
20138}
20139
20140fn vim_enabled(cx: &App) -> bool {
20141 cx.global::<SettingsStore>()
20142 .raw_user_settings()
20143 .get("vim_mode")
20144 == Some(&serde_json::Value::Bool(true))
20145}
20146
20147fn process_completion_for_edit(
20148 completion: &Completion,
20149 intent: CompletionIntent,
20150 buffer: &Entity<Buffer>,
20151 cursor_position: &text::Anchor,
20152 cx: &mut Context<Editor>,
20153) -> CompletionEdit {
20154 let buffer = buffer.read(cx);
20155 let buffer_snapshot = buffer.snapshot();
20156 let (snippet, new_text) = if completion.is_snippet() {
20157 // Workaround for typescript language server issues so that methods don't expand within
20158 // strings and functions with type expressions. The previous point is used because the query
20159 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20160 let mut snippet_source = completion.new_text.clone();
20161 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20162 previous_point.column = previous_point.column.saturating_sub(1);
20163 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20164 if scope.prefers_label_for_snippet_in_completion() {
20165 if let Some(label) = completion.label() {
20166 if matches!(
20167 completion.kind(),
20168 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20169 ) {
20170 snippet_source = label;
20171 }
20172 }
20173 }
20174 }
20175 match Snippet::parse(&snippet_source).log_err() {
20176 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20177 None => (None, completion.new_text.clone()),
20178 }
20179 } else {
20180 (None, completion.new_text.clone())
20181 };
20182
20183 let mut range_to_replace = {
20184 let replace_range = &completion.replace_range;
20185 if let CompletionSource::Lsp {
20186 insert_range: Some(insert_range),
20187 ..
20188 } = &completion.source
20189 {
20190 debug_assert_eq!(
20191 insert_range.start, replace_range.start,
20192 "insert_range and replace_range should start at the same position"
20193 );
20194 debug_assert!(
20195 insert_range
20196 .start
20197 .cmp(&cursor_position, &buffer_snapshot)
20198 .is_le(),
20199 "insert_range should start before or at cursor position"
20200 );
20201 debug_assert!(
20202 replace_range
20203 .start
20204 .cmp(&cursor_position, &buffer_snapshot)
20205 .is_le(),
20206 "replace_range should start before or at cursor position"
20207 );
20208 debug_assert!(
20209 insert_range
20210 .end
20211 .cmp(&cursor_position, &buffer_snapshot)
20212 .is_le(),
20213 "insert_range should end before or at cursor position"
20214 );
20215
20216 let should_replace = match intent {
20217 CompletionIntent::CompleteWithInsert => false,
20218 CompletionIntent::CompleteWithReplace => true,
20219 CompletionIntent::Complete | CompletionIntent::Compose => {
20220 let insert_mode =
20221 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20222 .completions
20223 .lsp_insert_mode;
20224 match insert_mode {
20225 LspInsertMode::Insert => false,
20226 LspInsertMode::Replace => true,
20227 LspInsertMode::ReplaceSubsequence => {
20228 let mut text_to_replace = buffer.chars_for_range(
20229 buffer.anchor_before(replace_range.start)
20230 ..buffer.anchor_after(replace_range.end),
20231 );
20232 let mut current_needle = text_to_replace.next();
20233 for haystack_ch in completion.label.text.chars() {
20234 if let Some(needle_ch) = current_needle {
20235 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20236 current_needle = text_to_replace.next();
20237 }
20238 }
20239 }
20240 current_needle.is_none()
20241 }
20242 LspInsertMode::ReplaceSuffix => {
20243 if replace_range
20244 .end
20245 .cmp(&cursor_position, &buffer_snapshot)
20246 .is_gt()
20247 {
20248 let range_after_cursor = *cursor_position..replace_range.end;
20249 let text_after_cursor = buffer
20250 .text_for_range(
20251 buffer.anchor_before(range_after_cursor.start)
20252 ..buffer.anchor_after(range_after_cursor.end),
20253 )
20254 .collect::<String>()
20255 .to_ascii_lowercase();
20256 completion
20257 .label
20258 .text
20259 .to_ascii_lowercase()
20260 .ends_with(&text_after_cursor)
20261 } else {
20262 true
20263 }
20264 }
20265 }
20266 }
20267 };
20268
20269 if should_replace {
20270 replace_range.clone()
20271 } else {
20272 insert_range.clone()
20273 }
20274 } else {
20275 replace_range.clone()
20276 }
20277 };
20278
20279 if range_to_replace
20280 .end
20281 .cmp(&cursor_position, &buffer_snapshot)
20282 .is_lt()
20283 {
20284 range_to_replace.end = *cursor_position;
20285 }
20286
20287 CompletionEdit {
20288 new_text,
20289 replace_range: range_to_replace.to_offset(&buffer),
20290 snippet,
20291 }
20292}
20293
20294struct CompletionEdit {
20295 new_text: String,
20296 replace_range: Range<usize>,
20297 snippet: Option<Snippet>,
20298}
20299
20300fn insert_extra_newline_brackets(
20301 buffer: &MultiBufferSnapshot,
20302 range: Range<usize>,
20303 language: &language::LanguageScope,
20304) -> bool {
20305 let leading_whitespace_len = buffer
20306 .reversed_chars_at(range.start)
20307 .take_while(|c| c.is_whitespace() && *c != '\n')
20308 .map(|c| c.len_utf8())
20309 .sum::<usize>();
20310 let trailing_whitespace_len = buffer
20311 .chars_at(range.end)
20312 .take_while(|c| c.is_whitespace() && *c != '\n')
20313 .map(|c| c.len_utf8())
20314 .sum::<usize>();
20315 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20316
20317 language.brackets().any(|(pair, enabled)| {
20318 let pair_start = pair.start.trim_end();
20319 let pair_end = pair.end.trim_start();
20320
20321 enabled
20322 && pair.newline
20323 && buffer.contains_str_at(range.end, pair_end)
20324 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20325 })
20326}
20327
20328fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20329 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20330 [(buffer, range, _)] => (*buffer, range.clone()),
20331 _ => return false,
20332 };
20333 let pair = {
20334 let mut result: Option<BracketMatch> = None;
20335
20336 for pair in buffer
20337 .all_bracket_ranges(range.clone())
20338 .filter(move |pair| {
20339 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20340 })
20341 {
20342 let len = pair.close_range.end - pair.open_range.start;
20343
20344 if let Some(existing) = &result {
20345 let existing_len = existing.close_range.end - existing.open_range.start;
20346 if len > existing_len {
20347 continue;
20348 }
20349 }
20350
20351 result = Some(pair);
20352 }
20353
20354 result
20355 };
20356 let Some(pair) = pair else {
20357 return false;
20358 };
20359 pair.newline_only
20360 && buffer
20361 .chars_for_range(pair.open_range.end..range.start)
20362 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20363 .all(|c| c.is_whitespace() && c != '\n')
20364}
20365
20366fn update_uncommitted_diff_for_buffer(
20367 editor: Entity<Editor>,
20368 project: &Entity<Project>,
20369 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20370 buffer: Entity<MultiBuffer>,
20371 cx: &mut App,
20372) -> Task<()> {
20373 let mut tasks = Vec::new();
20374 project.update(cx, |project, cx| {
20375 for buffer in buffers {
20376 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20377 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20378 }
20379 }
20380 });
20381 cx.spawn(async move |cx| {
20382 let diffs = future::join_all(tasks).await;
20383 if editor
20384 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20385 .unwrap_or(false)
20386 {
20387 return;
20388 }
20389
20390 buffer
20391 .update(cx, |buffer, cx| {
20392 for diff in diffs.into_iter().flatten() {
20393 buffer.add_diff(diff, cx);
20394 }
20395 })
20396 .ok();
20397 })
20398}
20399
20400fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20401 let tab_size = tab_size.get() as usize;
20402 let mut width = offset;
20403
20404 for ch in text.chars() {
20405 width += if ch == '\t' {
20406 tab_size - (width % tab_size)
20407 } else {
20408 1
20409 };
20410 }
20411
20412 width - offset
20413}
20414
20415#[cfg(test)]
20416mod tests {
20417 use super::*;
20418
20419 #[test]
20420 fn test_string_size_with_expanded_tabs() {
20421 let nz = |val| NonZeroU32::new(val).unwrap();
20422 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20423 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20424 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20425 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20426 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20427 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20428 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20429 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20430 }
20431}
20432
20433/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20434struct WordBreakingTokenizer<'a> {
20435 input: &'a str,
20436}
20437
20438impl<'a> WordBreakingTokenizer<'a> {
20439 fn new(input: &'a str) -> Self {
20440 Self { input }
20441 }
20442}
20443
20444fn is_char_ideographic(ch: char) -> bool {
20445 use unicode_script::Script::*;
20446 use unicode_script::UnicodeScript;
20447 matches!(ch.script(), Han | Tangut | Yi)
20448}
20449
20450fn is_grapheme_ideographic(text: &str) -> bool {
20451 text.chars().any(is_char_ideographic)
20452}
20453
20454fn is_grapheme_whitespace(text: &str) -> bool {
20455 text.chars().any(|x| x.is_whitespace())
20456}
20457
20458fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20459 text.chars().next().map_or(false, |ch| {
20460 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20461 })
20462}
20463
20464#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20465enum WordBreakToken<'a> {
20466 Word { token: &'a str, grapheme_len: usize },
20467 InlineWhitespace { token: &'a str, grapheme_len: usize },
20468 Newline,
20469}
20470
20471impl<'a> Iterator for WordBreakingTokenizer<'a> {
20472 /// Yields a span, the count of graphemes in the token, and whether it was
20473 /// whitespace. Note that it also breaks at word boundaries.
20474 type Item = WordBreakToken<'a>;
20475
20476 fn next(&mut self) -> Option<Self::Item> {
20477 use unicode_segmentation::UnicodeSegmentation;
20478 if self.input.is_empty() {
20479 return None;
20480 }
20481
20482 let mut iter = self.input.graphemes(true).peekable();
20483 let mut offset = 0;
20484 let mut grapheme_len = 0;
20485 if let Some(first_grapheme) = iter.next() {
20486 let is_newline = first_grapheme == "\n";
20487 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20488 offset += first_grapheme.len();
20489 grapheme_len += 1;
20490 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20491 if let Some(grapheme) = iter.peek().copied() {
20492 if should_stay_with_preceding_ideograph(grapheme) {
20493 offset += grapheme.len();
20494 grapheme_len += 1;
20495 }
20496 }
20497 } else {
20498 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20499 let mut next_word_bound = words.peek().copied();
20500 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20501 next_word_bound = words.next();
20502 }
20503 while let Some(grapheme) = iter.peek().copied() {
20504 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20505 break;
20506 };
20507 if is_grapheme_whitespace(grapheme) != is_whitespace
20508 || (grapheme == "\n") != is_newline
20509 {
20510 break;
20511 };
20512 offset += grapheme.len();
20513 grapheme_len += 1;
20514 iter.next();
20515 }
20516 }
20517 let token = &self.input[..offset];
20518 self.input = &self.input[offset..];
20519 if token == "\n" {
20520 Some(WordBreakToken::Newline)
20521 } else if is_whitespace {
20522 Some(WordBreakToken::InlineWhitespace {
20523 token,
20524 grapheme_len,
20525 })
20526 } else {
20527 Some(WordBreakToken::Word {
20528 token,
20529 grapheme_len,
20530 })
20531 }
20532 } else {
20533 None
20534 }
20535 }
20536}
20537
20538#[test]
20539fn test_word_breaking_tokenizer() {
20540 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20541 ("", &[]),
20542 (" ", &[whitespace(" ", 2)]),
20543 ("Ʒ", &[word("Ʒ", 1)]),
20544 ("Ǽ", &[word("Ǽ", 1)]),
20545 ("⋑", &[word("⋑", 1)]),
20546 ("⋑⋑", &[word("⋑⋑", 2)]),
20547 (
20548 "原理,进而",
20549 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20550 ),
20551 (
20552 "hello world",
20553 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20554 ),
20555 (
20556 "hello, world",
20557 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20558 ),
20559 (
20560 " hello world",
20561 &[
20562 whitespace(" ", 2),
20563 word("hello", 5),
20564 whitespace(" ", 1),
20565 word("world", 5),
20566 ],
20567 ),
20568 (
20569 "这是什么 \n 钢笔",
20570 &[
20571 word("这", 1),
20572 word("是", 1),
20573 word("什", 1),
20574 word("么", 1),
20575 whitespace(" ", 1),
20576 newline(),
20577 whitespace(" ", 1),
20578 word("钢", 1),
20579 word("笔", 1),
20580 ],
20581 ),
20582 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20583 ];
20584
20585 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20586 WordBreakToken::Word {
20587 token,
20588 grapheme_len,
20589 }
20590 }
20591
20592 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20593 WordBreakToken::InlineWhitespace {
20594 token,
20595 grapheme_len,
20596 }
20597 }
20598
20599 fn newline() -> WordBreakToken<'static> {
20600 WordBreakToken::Newline
20601 }
20602
20603 for (input, result) in tests {
20604 assert_eq!(
20605 WordBreakingTokenizer::new(input)
20606 .collect::<Vec<_>>()
20607 .as_slice(),
20608 *result,
20609 );
20610 }
20611}
20612
20613fn wrap_with_prefix(
20614 line_prefix: String,
20615 unwrapped_text: String,
20616 wrap_column: usize,
20617 tab_size: NonZeroU32,
20618 preserve_existing_whitespace: bool,
20619) -> String {
20620 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20621 let mut wrapped_text = String::new();
20622 let mut current_line = line_prefix.clone();
20623
20624 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20625 let mut current_line_len = line_prefix_len;
20626 let mut in_whitespace = false;
20627 for token in tokenizer {
20628 let have_preceding_whitespace = in_whitespace;
20629 match token {
20630 WordBreakToken::Word {
20631 token,
20632 grapheme_len,
20633 } => {
20634 in_whitespace = false;
20635 if current_line_len + grapheme_len > wrap_column
20636 && current_line_len != line_prefix_len
20637 {
20638 wrapped_text.push_str(current_line.trim_end());
20639 wrapped_text.push('\n');
20640 current_line.truncate(line_prefix.len());
20641 current_line_len = line_prefix_len;
20642 }
20643 current_line.push_str(token);
20644 current_line_len += grapheme_len;
20645 }
20646 WordBreakToken::InlineWhitespace {
20647 mut token,
20648 mut grapheme_len,
20649 } => {
20650 in_whitespace = true;
20651 if have_preceding_whitespace && !preserve_existing_whitespace {
20652 continue;
20653 }
20654 if !preserve_existing_whitespace {
20655 token = " ";
20656 grapheme_len = 1;
20657 }
20658 if current_line_len + grapheme_len > wrap_column {
20659 wrapped_text.push_str(current_line.trim_end());
20660 wrapped_text.push('\n');
20661 current_line.truncate(line_prefix.len());
20662 current_line_len = line_prefix_len;
20663 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20664 current_line.push_str(token);
20665 current_line_len += grapheme_len;
20666 }
20667 }
20668 WordBreakToken::Newline => {
20669 in_whitespace = true;
20670 if preserve_existing_whitespace {
20671 wrapped_text.push_str(current_line.trim_end());
20672 wrapped_text.push('\n');
20673 current_line.truncate(line_prefix.len());
20674 current_line_len = line_prefix_len;
20675 } else if have_preceding_whitespace {
20676 continue;
20677 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20678 {
20679 wrapped_text.push_str(current_line.trim_end());
20680 wrapped_text.push('\n');
20681 current_line.truncate(line_prefix.len());
20682 current_line_len = line_prefix_len;
20683 } else if current_line_len != line_prefix_len {
20684 current_line.push(' ');
20685 current_line_len += 1;
20686 }
20687 }
20688 }
20689 }
20690
20691 if !current_line.is_empty() {
20692 wrapped_text.push_str(¤t_line);
20693 }
20694 wrapped_text
20695}
20696
20697#[test]
20698fn test_wrap_with_prefix() {
20699 assert_eq!(
20700 wrap_with_prefix(
20701 "# ".to_string(),
20702 "abcdefg".to_string(),
20703 4,
20704 NonZeroU32::new(4).unwrap(),
20705 false,
20706 ),
20707 "# abcdefg"
20708 );
20709 assert_eq!(
20710 wrap_with_prefix(
20711 "".to_string(),
20712 "\thello world".to_string(),
20713 8,
20714 NonZeroU32::new(4).unwrap(),
20715 false,
20716 ),
20717 "hello\nworld"
20718 );
20719 assert_eq!(
20720 wrap_with_prefix(
20721 "// ".to_string(),
20722 "xx \nyy zz aa bb cc".to_string(),
20723 12,
20724 NonZeroU32::new(4).unwrap(),
20725 false,
20726 ),
20727 "// xx yy zz\n// aa bb cc"
20728 );
20729 assert_eq!(
20730 wrap_with_prefix(
20731 String::new(),
20732 "这是什么 \n 钢笔".to_string(),
20733 3,
20734 NonZeroU32::new(4).unwrap(),
20735 false,
20736 ),
20737 "这是什\n么 钢\n笔"
20738 );
20739}
20740
20741pub trait CollaborationHub {
20742 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20743 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20744 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20745}
20746
20747impl CollaborationHub for Entity<Project> {
20748 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20749 self.read(cx).collaborators()
20750 }
20751
20752 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20753 self.read(cx).user_store().read(cx).participant_indices()
20754 }
20755
20756 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20757 let this = self.read(cx);
20758 let user_ids = this.collaborators().values().map(|c| c.user_id);
20759 this.user_store().read(cx).participant_names(user_ids, cx)
20760 }
20761}
20762
20763pub trait SemanticsProvider {
20764 fn hover(
20765 &self,
20766 buffer: &Entity<Buffer>,
20767 position: text::Anchor,
20768 cx: &mut App,
20769 ) -> Option<Task<Vec<project::Hover>>>;
20770
20771 fn inline_values(
20772 &self,
20773 buffer_handle: Entity<Buffer>,
20774 range: Range<text::Anchor>,
20775 cx: &mut App,
20776 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20777
20778 fn inlay_hints(
20779 &self,
20780 buffer_handle: Entity<Buffer>,
20781 range: Range<text::Anchor>,
20782 cx: &mut App,
20783 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20784
20785 fn resolve_inlay_hint(
20786 &self,
20787 hint: InlayHint,
20788 buffer_handle: Entity<Buffer>,
20789 server_id: LanguageServerId,
20790 cx: &mut App,
20791 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20792
20793 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20794
20795 fn document_highlights(
20796 &self,
20797 buffer: &Entity<Buffer>,
20798 position: text::Anchor,
20799 cx: &mut App,
20800 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20801
20802 fn definitions(
20803 &self,
20804 buffer: &Entity<Buffer>,
20805 position: text::Anchor,
20806 kind: GotoDefinitionKind,
20807 cx: &mut App,
20808 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20809
20810 fn range_for_rename(
20811 &self,
20812 buffer: &Entity<Buffer>,
20813 position: text::Anchor,
20814 cx: &mut App,
20815 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20816
20817 fn perform_rename(
20818 &self,
20819 buffer: &Entity<Buffer>,
20820 position: text::Anchor,
20821 new_name: String,
20822 cx: &mut App,
20823 ) -> Option<Task<Result<ProjectTransaction>>>;
20824
20825 fn pull_diagnostics_for_buffer(
20826 &self,
20827 buffer: Entity<Buffer>,
20828 cx: &mut App,
20829 ) -> Task<anyhow::Result<()>>;
20830}
20831
20832pub trait CompletionProvider {
20833 fn completions(
20834 &self,
20835 excerpt_id: ExcerptId,
20836 buffer: &Entity<Buffer>,
20837 buffer_position: text::Anchor,
20838 trigger: CompletionContext,
20839 window: &mut Window,
20840 cx: &mut Context<Editor>,
20841 ) -> Task<Result<Vec<CompletionResponse>>>;
20842
20843 fn resolve_completions(
20844 &self,
20845 _buffer: Entity<Buffer>,
20846 _completion_indices: Vec<usize>,
20847 _completions: Rc<RefCell<Box<[Completion]>>>,
20848 _cx: &mut Context<Editor>,
20849 ) -> Task<Result<bool>> {
20850 Task::ready(Ok(false))
20851 }
20852
20853 fn apply_additional_edits_for_completion(
20854 &self,
20855 _buffer: Entity<Buffer>,
20856 _completions: Rc<RefCell<Box<[Completion]>>>,
20857 _completion_index: usize,
20858 _push_to_history: bool,
20859 _cx: &mut Context<Editor>,
20860 ) -> Task<Result<Option<language::Transaction>>> {
20861 Task::ready(Ok(None))
20862 }
20863
20864 fn is_completion_trigger(
20865 &self,
20866 buffer: &Entity<Buffer>,
20867 position: language::Anchor,
20868 text: &str,
20869 trigger_in_words: bool,
20870 menu_is_open: bool,
20871 cx: &mut Context<Editor>,
20872 ) -> bool;
20873
20874 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20875
20876 fn sort_completions(&self) -> bool {
20877 true
20878 }
20879
20880 fn filter_completions(&self) -> bool {
20881 true
20882 }
20883}
20884
20885pub trait CodeActionProvider {
20886 fn id(&self) -> Arc<str>;
20887
20888 fn code_actions(
20889 &self,
20890 buffer: &Entity<Buffer>,
20891 range: Range<text::Anchor>,
20892 window: &mut Window,
20893 cx: &mut App,
20894 ) -> Task<Result<Vec<CodeAction>>>;
20895
20896 fn apply_code_action(
20897 &self,
20898 buffer_handle: Entity<Buffer>,
20899 action: CodeAction,
20900 excerpt_id: ExcerptId,
20901 push_to_history: bool,
20902 window: &mut Window,
20903 cx: &mut App,
20904 ) -> Task<Result<ProjectTransaction>>;
20905}
20906
20907impl CodeActionProvider for Entity<Project> {
20908 fn id(&self) -> Arc<str> {
20909 "project".into()
20910 }
20911
20912 fn code_actions(
20913 &self,
20914 buffer: &Entity<Buffer>,
20915 range: Range<text::Anchor>,
20916 _window: &mut Window,
20917 cx: &mut App,
20918 ) -> Task<Result<Vec<CodeAction>>> {
20919 self.update(cx, |project, cx| {
20920 let code_lens = project.code_lens(buffer, range.clone(), cx);
20921 let code_actions = project.code_actions(buffer, range, None, cx);
20922 cx.background_spawn(async move {
20923 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20924 Ok(code_lens
20925 .context("code lens fetch")?
20926 .into_iter()
20927 .chain(code_actions.context("code action fetch")?)
20928 .collect())
20929 })
20930 })
20931 }
20932
20933 fn apply_code_action(
20934 &self,
20935 buffer_handle: Entity<Buffer>,
20936 action: CodeAction,
20937 _excerpt_id: ExcerptId,
20938 push_to_history: bool,
20939 _window: &mut Window,
20940 cx: &mut App,
20941 ) -> Task<Result<ProjectTransaction>> {
20942 self.update(cx, |project, cx| {
20943 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20944 })
20945 }
20946}
20947
20948fn snippet_completions(
20949 project: &Project,
20950 buffer: &Entity<Buffer>,
20951 buffer_position: text::Anchor,
20952 cx: &mut App,
20953) -> Task<Result<CompletionResponse>> {
20954 let languages = buffer.read(cx).languages_at(buffer_position);
20955 let snippet_store = project.snippets().read(cx);
20956
20957 let scopes: Vec<_> = languages
20958 .iter()
20959 .filter_map(|language| {
20960 let language_name = language.lsp_id();
20961 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20962
20963 if snippets.is_empty() {
20964 None
20965 } else {
20966 Some((language.default_scope(), snippets))
20967 }
20968 })
20969 .collect();
20970
20971 if scopes.is_empty() {
20972 return Task::ready(Ok(CompletionResponse {
20973 completions: vec![],
20974 is_incomplete: false,
20975 }));
20976 }
20977
20978 let snapshot = buffer.read(cx).text_snapshot();
20979 let chars: String = snapshot
20980 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20981 .collect();
20982 let executor = cx.background_executor().clone();
20983
20984 cx.background_spawn(async move {
20985 let mut is_incomplete = false;
20986 let mut completions: Vec<Completion> = Vec::new();
20987 for (scope, snippets) in scopes.into_iter() {
20988 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20989 let mut last_word = chars
20990 .chars()
20991 .take_while(|c| classifier.is_word(*c))
20992 .collect::<String>();
20993 last_word = last_word.chars().rev().collect();
20994
20995 if last_word.is_empty() {
20996 return Ok(CompletionResponse {
20997 completions: vec![],
20998 is_incomplete: true,
20999 });
21000 }
21001
21002 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21003 let to_lsp = |point: &text::Anchor| {
21004 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21005 point_to_lsp(end)
21006 };
21007 let lsp_end = to_lsp(&buffer_position);
21008
21009 let candidates = snippets
21010 .iter()
21011 .enumerate()
21012 .flat_map(|(ix, snippet)| {
21013 snippet
21014 .prefix
21015 .iter()
21016 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21017 })
21018 .collect::<Vec<StringMatchCandidate>>();
21019
21020 const MAX_RESULTS: usize = 100;
21021 let mut matches = fuzzy::match_strings(
21022 &candidates,
21023 &last_word,
21024 last_word.chars().any(|c| c.is_uppercase()),
21025 MAX_RESULTS,
21026 &Default::default(),
21027 executor.clone(),
21028 )
21029 .await;
21030
21031 if matches.len() >= MAX_RESULTS {
21032 is_incomplete = true;
21033 }
21034
21035 // Remove all candidates where the query's start does not match the start of any word in the candidate
21036 if let Some(query_start) = last_word.chars().next() {
21037 matches.retain(|string_match| {
21038 split_words(&string_match.string).any(|word| {
21039 // Check that the first codepoint of the word as lowercase matches the first
21040 // codepoint of the query as lowercase
21041 word.chars()
21042 .flat_map(|codepoint| codepoint.to_lowercase())
21043 .zip(query_start.to_lowercase())
21044 .all(|(word_cp, query_cp)| word_cp == query_cp)
21045 })
21046 });
21047 }
21048
21049 let matched_strings = matches
21050 .into_iter()
21051 .map(|m| m.string)
21052 .collect::<HashSet<_>>();
21053
21054 completions.extend(snippets.iter().filter_map(|snippet| {
21055 let matching_prefix = snippet
21056 .prefix
21057 .iter()
21058 .find(|prefix| matched_strings.contains(*prefix))?;
21059 let start = as_offset - last_word.len();
21060 let start = snapshot.anchor_before(start);
21061 let range = start..buffer_position;
21062 let lsp_start = to_lsp(&start);
21063 let lsp_range = lsp::Range {
21064 start: lsp_start,
21065 end: lsp_end,
21066 };
21067 Some(Completion {
21068 replace_range: range,
21069 new_text: snippet.body.clone(),
21070 source: CompletionSource::Lsp {
21071 insert_range: None,
21072 server_id: LanguageServerId(usize::MAX),
21073 resolved: true,
21074 lsp_completion: Box::new(lsp::CompletionItem {
21075 label: snippet.prefix.first().unwrap().clone(),
21076 kind: Some(CompletionItemKind::SNIPPET),
21077 label_details: snippet.description.as_ref().map(|description| {
21078 lsp::CompletionItemLabelDetails {
21079 detail: Some(description.clone()),
21080 description: None,
21081 }
21082 }),
21083 insert_text_format: Some(InsertTextFormat::SNIPPET),
21084 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21085 lsp::InsertReplaceEdit {
21086 new_text: snippet.body.clone(),
21087 insert: lsp_range,
21088 replace: lsp_range,
21089 },
21090 )),
21091 filter_text: Some(snippet.body.clone()),
21092 sort_text: Some(char::MAX.to_string()),
21093 ..lsp::CompletionItem::default()
21094 }),
21095 lsp_defaults: None,
21096 },
21097 label: CodeLabel {
21098 text: matching_prefix.clone(),
21099 runs: Vec::new(),
21100 filter_range: 0..matching_prefix.len(),
21101 },
21102 icon_path: None,
21103 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21104 single_line: snippet.name.clone().into(),
21105 plain_text: snippet
21106 .description
21107 .clone()
21108 .map(|description| description.into()),
21109 }),
21110 insert_text_mode: None,
21111 confirm: None,
21112 })
21113 }))
21114 }
21115
21116 Ok(CompletionResponse {
21117 completions,
21118 is_incomplete,
21119 })
21120 })
21121}
21122
21123impl CompletionProvider for Entity<Project> {
21124 fn completions(
21125 &self,
21126 _excerpt_id: ExcerptId,
21127 buffer: &Entity<Buffer>,
21128 buffer_position: text::Anchor,
21129 options: CompletionContext,
21130 _window: &mut Window,
21131 cx: &mut Context<Editor>,
21132 ) -> Task<Result<Vec<CompletionResponse>>> {
21133 self.update(cx, |project, cx| {
21134 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21135 let project_completions = project.completions(buffer, buffer_position, options, cx);
21136 cx.background_spawn(async move {
21137 let mut responses = project_completions.await?;
21138 let snippets = snippets.await?;
21139 if !snippets.completions.is_empty() {
21140 responses.push(snippets);
21141 }
21142 Ok(responses)
21143 })
21144 })
21145 }
21146
21147 fn resolve_completions(
21148 &self,
21149 buffer: Entity<Buffer>,
21150 completion_indices: Vec<usize>,
21151 completions: Rc<RefCell<Box<[Completion]>>>,
21152 cx: &mut Context<Editor>,
21153 ) -> Task<Result<bool>> {
21154 self.update(cx, |project, cx| {
21155 project.lsp_store().update(cx, |lsp_store, cx| {
21156 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21157 })
21158 })
21159 }
21160
21161 fn apply_additional_edits_for_completion(
21162 &self,
21163 buffer: Entity<Buffer>,
21164 completions: Rc<RefCell<Box<[Completion]>>>,
21165 completion_index: usize,
21166 push_to_history: bool,
21167 cx: &mut Context<Editor>,
21168 ) -> Task<Result<Option<language::Transaction>>> {
21169 self.update(cx, |project, cx| {
21170 project.lsp_store().update(cx, |lsp_store, cx| {
21171 lsp_store.apply_additional_edits_for_completion(
21172 buffer,
21173 completions,
21174 completion_index,
21175 push_to_history,
21176 cx,
21177 )
21178 })
21179 })
21180 }
21181
21182 fn is_completion_trigger(
21183 &self,
21184 buffer: &Entity<Buffer>,
21185 position: language::Anchor,
21186 text: &str,
21187 trigger_in_words: bool,
21188 menu_is_open: bool,
21189 cx: &mut Context<Editor>,
21190 ) -> bool {
21191 let mut chars = text.chars();
21192 let char = if let Some(char) = chars.next() {
21193 char
21194 } else {
21195 return false;
21196 };
21197 if chars.next().is_some() {
21198 return false;
21199 }
21200
21201 let buffer = buffer.read(cx);
21202 let snapshot = buffer.snapshot();
21203 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21204 return false;
21205 }
21206 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21207 if trigger_in_words && classifier.is_word(char) {
21208 return true;
21209 }
21210
21211 buffer.completion_triggers().contains(text)
21212 }
21213}
21214
21215impl SemanticsProvider for Entity<Project> {
21216 fn hover(
21217 &self,
21218 buffer: &Entity<Buffer>,
21219 position: text::Anchor,
21220 cx: &mut App,
21221 ) -> Option<Task<Vec<project::Hover>>> {
21222 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21223 }
21224
21225 fn document_highlights(
21226 &self,
21227 buffer: &Entity<Buffer>,
21228 position: text::Anchor,
21229 cx: &mut App,
21230 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21231 Some(self.update(cx, |project, cx| {
21232 project.document_highlights(buffer, position, cx)
21233 }))
21234 }
21235
21236 fn definitions(
21237 &self,
21238 buffer: &Entity<Buffer>,
21239 position: text::Anchor,
21240 kind: GotoDefinitionKind,
21241 cx: &mut App,
21242 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21243 Some(self.update(cx, |project, cx| match kind {
21244 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21245 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21246 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21247 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21248 }))
21249 }
21250
21251 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21252 // TODO: make this work for remote projects
21253 self.update(cx, |project, cx| {
21254 if project
21255 .active_debug_session(cx)
21256 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21257 {
21258 return true;
21259 }
21260
21261 buffer.update(cx, |buffer, cx| {
21262 project.any_language_server_supports_inlay_hints(buffer, cx)
21263 })
21264 })
21265 }
21266
21267 fn inline_values(
21268 &self,
21269 buffer_handle: Entity<Buffer>,
21270
21271 range: Range<text::Anchor>,
21272 cx: &mut App,
21273 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21274 self.update(cx, |project, cx| {
21275 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21276
21277 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21278 })
21279 }
21280
21281 fn inlay_hints(
21282 &self,
21283 buffer_handle: Entity<Buffer>,
21284 range: Range<text::Anchor>,
21285 cx: &mut App,
21286 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21287 Some(self.update(cx, |project, cx| {
21288 project.inlay_hints(buffer_handle, range, cx)
21289 }))
21290 }
21291
21292 fn resolve_inlay_hint(
21293 &self,
21294 hint: InlayHint,
21295 buffer_handle: Entity<Buffer>,
21296 server_id: LanguageServerId,
21297 cx: &mut App,
21298 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21299 Some(self.update(cx, |project, cx| {
21300 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21301 }))
21302 }
21303
21304 fn range_for_rename(
21305 &self,
21306 buffer: &Entity<Buffer>,
21307 position: text::Anchor,
21308 cx: &mut App,
21309 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21310 Some(self.update(cx, |project, cx| {
21311 let buffer = buffer.clone();
21312 let task = project.prepare_rename(buffer.clone(), position, cx);
21313 cx.spawn(async move |_, cx| {
21314 Ok(match task.await? {
21315 PrepareRenameResponse::Success(range) => Some(range),
21316 PrepareRenameResponse::InvalidPosition => None,
21317 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21318 // Fallback on using TreeSitter info to determine identifier range
21319 buffer.read_with(cx, |buffer, _| {
21320 let snapshot = buffer.snapshot();
21321 let (range, kind) = snapshot.surrounding_word(position);
21322 if kind != Some(CharKind::Word) {
21323 return None;
21324 }
21325 Some(
21326 snapshot.anchor_before(range.start)
21327 ..snapshot.anchor_after(range.end),
21328 )
21329 })?
21330 }
21331 })
21332 })
21333 }))
21334 }
21335
21336 fn perform_rename(
21337 &self,
21338 buffer: &Entity<Buffer>,
21339 position: text::Anchor,
21340 new_name: String,
21341 cx: &mut App,
21342 ) -> Option<Task<Result<ProjectTransaction>>> {
21343 Some(self.update(cx, |project, cx| {
21344 project.perform_rename(buffer.clone(), position, new_name, cx)
21345 }))
21346 }
21347
21348 fn pull_diagnostics_for_buffer(
21349 &self,
21350 buffer: Entity<Buffer>,
21351 cx: &mut App,
21352 ) -> Task<anyhow::Result<()>> {
21353 let diagnostics = self.update(cx, |project, cx| {
21354 project
21355 .lsp_store()
21356 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21357 });
21358 let project = self.clone();
21359 cx.spawn(async move |cx| {
21360 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21361 project.update(cx, |project, cx| {
21362 project.lsp_store().update(cx, |lsp_store, cx| {
21363 for diagnostics_set in diagnostics {
21364 let LspPullDiagnostics::Response {
21365 server_id,
21366 uri,
21367 diagnostics,
21368 } = diagnostics_set
21369 else {
21370 continue;
21371 };
21372
21373 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21374 let disk_based_sources = adapter
21375 .as_ref()
21376 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21377 .unwrap_or(&[]);
21378 match diagnostics {
21379 PulledDiagnostics::Unchanged { result_id } => {
21380 lsp_store
21381 .merge_diagnostics(
21382 server_id,
21383 lsp::PublishDiagnosticsParams {
21384 uri: uri.clone(),
21385 diagnostics: Vec::new(),
21386 version: None,
21387 },
21388 Some(result_id),
21389 DiagnosticSourceKind::Pulled,
21390 disk_based_sources,
21391 |_, _| true,
21392 cx,
21393 )
21394 .log_err();
21395 }
21396 PulledDiagnostics::Changed {
21397 diagnostics,
21398 result_id,
21399 } => {
21400 lsp_store
21401 .merge_diagnostics(
21402 server_id,
21403 lsp::PublishDiagnosticsParams {
21404 uri: uri.clone(),
21405 diagnostics,
21406 version: None,
21407 },
21408 result_id,
21409 DiagnosticSourceKind::Pulled,
21410 disk_based_sources,
21411 |old_diagnostic, _| match old_diagnostic.source_kind {
21412 DiagnosticSourceKind::Pulled => false,
21413 DiagnosticSourceKind::Other
21414 | DiagnosticSourceKind::Pushed => true,
21415 },
21416 cx,
21417 )
21418 .log_err();
21419 }
21420 }
21421 }
21422 })
21423 })
21424 })
21425 }
21426}
21427
21428fn inlay_hint_settings(
21429 location: Anchor,
21430 snapshot: &MultiBufferSnapshot,
21431 cx: &mut Context<Editor>,
21432) -> InlayHintSettings {
21433 let file = snapshot.file_at(location);
21434 let language = snapshot.language_at(location).map(|l| l.name());
21435 language_settings(language, file, cx).inlay_hints
21436}
21437
21438fn consume_contiguous_rows(
21439 contiguous_row_selections: &mut Vec<Selection<Point>>,
21440 selection: &Selection<Point>,
21441 display_map: &DisplaySnapshot,
21442 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21443) -> (MultiBufferRow, MultiBufferRow) {
21444 contiguous_row_selections.push(selection.clone());
21445 let start_row = MultiBufferRow(selection.start.row);
21446 let mut end_row = ending_row(selection, display_map);
21447
21448 while let Some(next_selection) = selections.peek() {
21449 if next_selection.start.row <= end_row.0 {
21450 end_row = ending_row(next_selection, display_map);
21451 contiguous_row_selections.push(selections.next().unwrap().clone());
21452 } else {
21453 break;
21454 }
21455 }
21456 (start_row, end_row)
21457}
21458
21459fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21460 if next_selection.end.column > 0 || next_selection.is_empty() {
21461 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21462 } else {
21463 MultiBufferRow(next_selection.end.row)
21464 }
21465}
21466
21467impl EditorSnapshot {
21468 pub fn remote_selections_in_range<'a>(
21469 &'a self,
21470 range: &'a Range<Anchor>,
21471 collaboration_hub: &dyn CollaborationHub,
21472 cx: &'a App,
21473 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21474 let participant_names = collaboration_hub.user_names(cx);
21475 let participant_indices = collaboration_hub.user_participant_indices(cx);
21476 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21477 let collaborators_by_replica_id = collaborators_by_peer_id
21478 .values()
21479 .map(|collaborator| (collaborator.replica_id, collaborator))
21480 .collect::<HashMap<_, _>>();
21481 self.buffer_snapshot
21482 .selections_in_range(range, false)
21483 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21484 if replica_id == AGENT_REPLICA_ID {
21485 Some(RemoteSelection {
21486 replica_id,
21487 selection,
21488 cursor_shape,
21489 line_mode,
21490 collaborator_id: CollaboratorId::Agent,
21491 user_name: Some("Agent".into()),
21492 color: cx.theme().players().agent(),
21493 })
21494 } else {
21495 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21496 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21497 let user_name = participant_names.get(&collaborator.user_id).cloned();
21498 Some(RemoteSelection {
21499 replica_id,
21500 selection,
21501 cursor_shape,
21502 line_mode,
21503 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21504 user_name,
21505 color: if let Some(index) = participant_index {
21506 cx.theme().players().color_for_participant(index.0)
21507 } else {
21508 cx.theme().players().absent()
21509 },
21510 })
21511 }
21512 })
21513 }
21514
21515 pub fn hunks_for_ranges(
21516 &self,
21517 ranges: impl IntoIterator<Item = Range<Point>>,
21518 ) -> Vec<MultiBufferDiffHunk> {
21519 let mut hunks = Vec::new();
21520 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21521 HashMap::default();
21522 for query_range in ranges {
21523 let query_rows =
21524 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21525 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21526 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21527 ) {
21528 // Include deleted hunks that are adjacent to the query range, because
21529 // otherwise they would be missed.
21530 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21531 if hunk.status().is_deleted() {
21532 intersects_range |= hunk.row_range.start == query_rows.end;
21533 intersects_range |= hunk.row_range.end == query_rows.start;
21534 }
21535 if intersects_range {
21536 if !processed_buffer_rows
21537 .entry(hunk.buffer_id)
21538 .or_default()
21539 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21540 {
21541 continue;
21542 }
21543 hunks.push(hunk);
21544 }
21545 }
21546 }
21547
21548 hunks
21549 }
21550
21551 fn display_diff_hunks_for_rows<'a>(
21552 &'a self,
21553 display_rows: Range<DisplayRow>,
21554 folded_buffers: &'a HashSet<BufferId>,
21555 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21556 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21557 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21558
21559 self.buffer_snapshot
21560 .diff_hunks_in_range(buffer_start..buffer_end)
21561 .filter_map(|hunk| {
21562 if folded_buffers.contains(&hunk.buffer_id) {
21563 return None;
21564 }
21565
21566 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21567 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21568
21569 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21570 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21571
21572 let display_hunk = if hunk_display_start.column() != 0 {
21573 DisplayDiffHunk::Folded {
21574 display_row: hunk_display_start.row(),
21575 }
21576 } else {
21577 let mut end_row = hunk_display_end.row();
21578 if hunk_display_end.column() > 0 {
21579 end_row.0 += 1;
21580 }
21581 let is_created_file = hunk.is_created_file();
21582 DisplayDiffHunk::Unfolded {
21583 status: hunk.status(),
21584 diff_base_byte_range: hunk.diff_base_byte_range,
21585 display_row_range: hunk_display_start.row()..end_row,
21586 multi_buffer_range: Anchor::range_in_buffer(
21587 hunk.excerpt_id,
21588 hunk.buffer_id,
21589 hunk.buffer_range,
21590 ),
21591 is_created_file,
21592 }
21593 };
21594
21595 Some(display_hunk)
21596 })
21597 }
21598
21599 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21600 self.display_snapshot.buffer_snapshot.language_at(position)
21601 }
21602
21603 pub fn is_focused(&self) -> bool {
21604 self.is_focused
21605 }
21606
21607 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21608 self.placeholder_text.as_ref()
21609 }
21610
21611 pub fn scroll_position(&self) -> gpui::Point<f32> {
21612 self.scroll_anchor.scroll_position(&self.display_snapshot)
21613 }
21614
21615 fn gutter_dimensions(
21616 &self,
21617 font_id: FontId,
21618 font_size: Pixels,
21619 max_line_number_width: Pixels,
21620 cx: &App,
21621 ) -> Option<GutterDimensions> {
21622 if !self.show_gutter {
21623 return None;
21624 }
21625
21626 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21627 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21628
21629 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21630 matches!(
21631 ProjectSettings::get_global(cx).git.git_gutter,
21632 Some(GitGutterSetting::TrackedFiles)
21633 )
21634 });
21635 let gutter_settings = EditorSettings::get_global(cx).gutter;
21636 let show_line_numbers = self
21637 .show_line_numbers
21638 .unwrap_or(gutter_settings.line_numbers);
21639 let line_gutter_width = if show_line_numbers {
21640 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21641 let min_width_for_number_on_gutter =
21642 em_advance * gutter_settings.min_line_number_digits as f32;
21643 max_line_number_width.max(min_width_for_number_on_gutter)
21644 } else {
21645 0.0.into()
21646 };
21647
21648 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21649 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21650
21651 let git_blame_entries_width =
21652 self.git_blame_gutter_max_author_length
21653 .map(|max_author_length| {
21654 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21655 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21656
21657 /// The number of characters to dedicate to gaps and margins.
21658 const SPACING_WIDTH: usize = 4;
21659
21660 let max_char_count = max_author_length.min(renderer.max_author_length())
21661 + ::git::SHORT_SHA_LENGTH
21662 + MAX_RELATIVE_TIMESTAMP.len()
21663 + SPACING_WIDTH;
21664
21665 em_advance * max_char_count
21666 });
21667
21668 let is_singleton = self.buffer_snapshot.is_singleton();
21669
21670 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21671 left_padding += if !is_singleton {
21672 em_width * 4.0
21673 } else if show_runnables || show_breakpoints {
21674 em_width * 3.0
21675 } else if show_git_gutter && show_line_numbers {
21676 em_width * 2.0
21677 } else if show_git_gutter || show_line_numbers {
21678 em_width
21679 } else {
21680 px(0.)
21681 };
21682
21683 let shows_folds = is_singleton && gutter_settings.folds;
21684
21685 let right_padding = if shows_folds && show_line_numbers {
21686 em_width * 4.0
21687 } else if shows_folds || (!is_singleton && show_line_numbers) {
21688 em_width * 3.0
21689 } else if show_line_numbers {
21690 em_width
21691 } else {
21692 px(0.)
21693 };
21694
21695 Some(GutterDimensions {
21696 left_padding,
21697 right_padding,
21698 width: line_gutter_width + left_padding + right_padding,
21699 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21700 git_blame_entries_width,
21701 })
21702 }
21703
21704 pub fn render_crease_toggle(
21705 &self,
21706 buffer_row: MultiBufferRow,
21707 row_contains_cursor: bool,
21708 editor: Entity<Editor>,
21709 window: &mut Window,
21710 cx: &mut App,
21711 ) -> Option<AnyElement> {
21712 let folded = self.is_line_folded(buffer_row);
21713 let mut is_foldable = false;
21714
21715 if let Some(crease) = self
21716 .crease_snapshot
21717 .query_row(buffer_row, &self.buffer_snapshot)
21718 {
21719 is_foldable = true;
21720 match crease {
21721 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21722 if let Some(render_toggle) = render_toggle {
21723 let toggle_callback =
21724 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21725 if folded {
21726 editor.update(cx, |editor, cx| {
21727 editor.fold_at(buffer_row, window, cx)
21728 });
21729 } else {
21730 editor.update(cx, |editor, cx| {
21731 editor.unfold_at(buffer_row, window, cx)
21732 });
21733 }
21734 });
21735 return Some((render_toggle)(
21736 buffer_row,
21737 folded,
21738 toggle_callback,
21739 window,
21740 cx,
21741 ));
21742 }
21743 }
21744 }
21745 }
21746
21747 is_foldable |= self.starts_indent(buffer_row);
21748
21749 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21750 Some(
21751 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21752 .toggle_state(folded)
21753 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21754 if folded {
21755 this.unfold_at(buffer_row, window, cx);
21756 } else {
21757 this.fold_at(buffer_row, window, cx);
21758 }
21759 }))
21760 .into_any_element(),
21761 )
21762 } else {
21763 None
21764 }
21765 }
21766
21767 pub fn render_crease_trailer(
21768 &self,
21769 buffer_row: MultiBufferRow,
21770 window: &mut Window,
21771 cx: &mut App,
21772 ) -> Option<AnyElement> {
21773 let folded = self.is_line_folded(buffer_row);
21774 if let Crease::Inline { render_trailer, .. } = self
21775 .crease_snapshot
21776 .query_row(buffer_row, &self.buffer_snapshot)?
21777 {
21778 let render_trailer = render_trailer.as_ref()?;
21779 Some(render_trailer(buffer_row, folded, window, cx))
21780 } else {
21781 None
21782 }
21783 }
21784}
21785
21786impl Deref for EditorSnapshot {
21787 type Target = DisplaySnapshot;
21788
21789 fn deref(&self) -> &Self::Target {
21790 &self.display_snapshot
21791 }
21792}
21793
21794#[derive(Clone, Debug, PartialEq, Eq)]
21795pub enum EditorEvent {
21796 InputIgnored {
21797 text: Arc<str>,
21798 },
21799 InputHandled {
21800 utf16_range_to_replace: Option<Range<isize>>,
21801 text: Arc<str>,
21802 },
21803 ExcerptsAdded {
21804 buffer: Entity<Buffer>,
21805 predecessor: ExcerptId,
21806 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21807 },
21808 ExcerptsRemoved {
21809 ids: Vec<ExcerptId>,
21810 removed_buffer_ids: Vec<BufferId>,
21811 },
21812 BufferFoldToggled {
21813 ids: Vec<ExcerptId>,
21814 folded: bool,
21815 },
21816 ExcerptsEdited {
21817 ids: Vec<ExcerptId>,
21818 },
21819 ExcerptsExpanded {
21820 ids: Vec<ExcerptId>,
21821 },
21822 BufferEdited,
21823 Edited {
21824 transaction_id: clock::Lamport,
21825 },
21826 Reparsed(BufferId),
21827 Focused,
21828 FocusedIn,
21829 Blurred,
21830 DirtyChanged,
21831 Saved,
21832 TitleChanged,
21833 DiffBaseChanged,
21834 SelectionsChanged {
21835 local: bool,
21836 },
21837 ScrollPositionChanged {
21838 local: bool,
21839 autoscroll: bool,
21840 },
21841 Closed,
21842 TransactionUndone {
21843 transaction_id: clock::Lamport,
21844 },
21845 TransactionBegun {
21846 transaction_id: clock::Lamport,
21847 },
21848 Reloaded,
21849 CursorShapeChanged,
21850 PushedToNavHistory {
21851 anchor: Anchor,
21852 is_deactivate: bool,
21853 },
21854}
21855
21856impl EventEmitter<EditorEvent> for Editor {}
21857
21858impl Focusable for Editor {
21859 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21860 self.focus_handle.clone()
21861 }
21862}
21863
21864impl Render for Editor {
21865 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21866 let settings = ThemeSettings::get_global(cx);
21867
21868 let mut text_style = match self.mode {
21869 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21870 color: cx.theme().colors().editor_foreground,
21871 font_family: settings.ui_font.family.clone(),
21872 font_features: settings.ui_font.features.clone(),
21873 font_fallbacks: settings.ui_font.fallbacks.clone(),
21874 font_size: rems(0.875).into(),
21875 font_weight: settings.ui_font.weight,
21876 line_height: relative(settings.buffer_line_height.value()),
21877 ..Default::default()
21878 },
21879 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21880 color: cx.theme().colors().editor_foreground,
21881 font_family: settings.buffer_font.family.clone(),
21882 font_features: settings.buffer_font.features.clone(),
21883 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21884 font_size: settings.buffer_font_size(cx).into(),
21885 font_weight: settings.buffer_font.weight,
21886 line_height: relative(settings.buffer_line_height.value()),
21887 ..Default::default()
21888 },
21889 };
21890 if let Some(text_style_refinement) = &self.text_style_refinement {
21891 text_style.refine(text_style_refinement)
21892 }
21893
21894 let background = match self.mode {
21895 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21896 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21897 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21898 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21899 };
21900
21901 EditorElement::new(
21902 &cx.entity(),
21903 EditorStyle {
21904 background,
21905 local_player: cx.theme().players().local(),
21906 text: text_style,
21907 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21908 syntax: cx.theme().syntax().clone(),
21909 status: cx.theme().status().clone(),
21910 inlay_hints_style: make_inlay_hints_style(cx),
21911 inline_completion_styles: make_suggestion_styles(cx),
21912 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21913 show_underlines: !self.mode.is_minimap(),
21914 },
21915 )
21916 }
21917}
21918
21919impl EntityInputHandler for Editor {
21920 fn text_for_range(
21921 &mut self,
21922 range_utf16: Range<usize>,
21923 adjusted_range: &mut Option<Range<usize>>,
21924 _: &mut Window,
21925 cx: &mut Context<Self>,
21926 ) -> Option<String> {
21927 let snapshot = self.buffer.read(cx).read(cx);
21928 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21929 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21930 if (start.0..end.0) != range_utf16 {
21931 adjusted_range.replace(start.0..end.0);
21932 }
21933 Some(snapshot.text_for_range(start..end).collect())
21934 }
21935
21936 fn selected_text_range(
21937 &mut self,
21938 ignore_disabled_input: bool,
21939 _: &mut Window,
21940 cx: &mut Context<Self>,
21941 ) -> Option<UTF16Selection> {
21942 // Prevent the IME menu from appearing when holding down an alphabetic key
21943 // while input is disabled.
21944 if !ignore_disabled_input && !self.input_enabled {
21945 return None;
21946 }
21947
21948 let selection = self.selections.newest::<OffsetUtf16>(cx);
21949 let range = selection.range();
21950
21951 Some(UTF16Selection {
21952 range: range.start.0..range.end.0,
21953 reversed: selection.reversed,
21954 })
21955 }
21956
21957 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21958 let snapshot = self.buffer.read(cx).read(cx);
21959 let (range, _) = self.text_highlights::<InputComposition>(cx)?.first()?;
21960 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21961 }
21962
21963 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21964 self.clear_highlights::<InputComposition>(cx);
21965 self.ime_transaction.take();
21966 }
21967
21968 fn replace_text_in_range(
21969 &mut self,
21970 range_utf16: Option<Range<usize>>,
21971 text: &str,
21972 window: &mut Window,
21973 cx: &mut Context<Self>,
21974 ) {
21975 if !self.input_enabled {
21976 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21977 return;
21978 }
21979
21980 self.transact(window, cx, |this, window, cx| {
21981 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21982 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21983 Some(this.selection_replacement_ranges(range_utf16, cx))
21984 } else {
21985 this.marked_text_ranges(cx)
21986 };
21987
21988 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21989 let newest_selection_id = this.selections.newest_anchor().id;
21990 this.selections
21991 .all::<OffsetUtf16>(cx)
21992 .iter()
21993 .zip(ranges_to_replace.iter())
21994 .find_map(|(selection, range)| {
21995 if selection.id == newest_selection_id {
21996 Some(
21997 (range.start.0 as isize - selection.head().0 as isize)
21998 ..(range.end.0 as isize - selection.head().0 as isize),
21999 )
22000 } else {
22001 None
22002 }
22003 })
22004 });
22005
22006 cx.emit(EditorEvent::InputHandled {
22007 utf16_range_to_replace: range_to_replace,
22008 text: text.into(),
22009 });
22010
22011 if let Some(new_selected_ranges) = new_selected_ranges {
22012 this.change_selections(None, window, cx, |selections| {
22013 selections.select_ranges(new_selected_ranges)
22014 });
22015 this.backspace(&Default::default(), window, cx);
22016 }
22017
22018 this.handle_input(text, window, cx);
22019 });
22020
22021 if let Some(transaction) = self.ime_transaction {
22022 self.buffer.update(cx, |buffer, cx| {
22023 buffer.group_until_transaction(transaction, cx);
22024 });
22025 }
22026
22027 self.unmark_text(window, cx);
22028 }
22029
22030 fn replace_and_mark_text_in_range(
22031 &mut self,
22032 range_utf16: Option<Range<usize>>,
22033 text: &str,
22034 new_selected_range_utf16: Option<Range<usize>>,
22035 window: &mut Window,
22036 cx: &mut Context<Self>,
22037 ) {
22038 if !self.input_enabled {
22039 return;
22040 }
22041
22042 let transaction = self.transact(window, cx, |this, window, cx| {
22043 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22044 let snapshot = this.buffer.read(cx).read(cx);
22045 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22046 for marked_range in &mut marked_ranges {
22047 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22048 marked_range.start.0 += relative_range_utf16.start;
22049 marked_range.start =
22050 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22051 marked_range.end =
22052 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22053 }
22054 }
22055 Some(marked_ranges)
22056 } else if let Some(range_utf16) = range_utf16 {
22057 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22058 Some(this.selection_replacement_ranges(range_utf16, cx))
22059 } else {
22060 None
22061 };
22062
22063 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22064 let newest_selection_id = this.selections.newest_anchor().id;
22065 this.selections
22066 .all::<OffsetUtf16>(cx)
22067 .iter()
22068 .zip(ranges_to_replace.iter())
22069 .find_map(|(selection, range)| {
22070 if selection.id == newest_selection_id {
22071 Some(
22072 (range.start.0 as isize - selection.head().0 as isize)
22073 ..(range.end.0 as isize - selection.head().0 as isize),
22074 )
22075 } else {
22076 None
22077 }
22078 })
22079 });
22080
22081 cx.emit(EditorEvent::InputHandled {
22082 utf16_range_to_replace: range_to_replace,
22083 text: text.into(),
22084 });
22085
22086 if let Some(ranges) = ranges_to_replace {
22087 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22088 }
22089
22090 let marked_ranges = {
22091 let snapshot = this.buffer.read(cx).read(cx);
22092 this.selections
22093 .disjoint_anchors()
22094 .iter()
22095 .map(|selection| {
22096 (
22097 selection.start.bias_left(&snapshot)
22098 ..selection.end.bias_right(&snapshot),
22099 HighlightStyle {
22100 underline: Some(UnderlineStyle {
22101 thickness: px(1.),
22102 color: None,
22103 wavy: false,
22104 }),
22105 ..Default::default()
22106 },
22107 )
22108 })
22109 .collect::<Vec<_>>()
22110 };
22111
22112 if text.is_empty() {
22113 this.unmark_text(window, cx);
22114 } else {
22115 this.highlight_text::<InputComposition>(marked_ranges.clone(), cx);
22116 }
22117
22118 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22119 let use_autoclose = this.use_autoclose;
22120 let use_auto_surround = this.use_auto_surround;
22121 this.set_use_autoclose(false);
22122 this.set_use_auto_surround(false);
22123 this.handle_input(text, window, cx);
22124 this.set_use_autoclose(use_autoclose);
22125 this.set_use_auto_surround(use_auto_surround);
22126
22127 if let Some(new_selected_range) = new_selected_range_utf16 {
22128 let snapshot = this.buffer.read(cx).read(cx);
22129 let new_selected_ranges = marked_ranges
22130 .into_iter()
22131 .map(|(marked_range, _)| {
22132 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22133 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22134 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22135 snapshot.clip_offset_utf16(new_start, Bias::Left)
22136 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22137 })
22138 .collect::<Vec<_>>();
22139
22140 drop(snapshot);
22141 this.change_selections(None, window, cx, |selections| {
22142 selections.select_ranges(new_selected_ranges)
22143 });
22144 }
22145 });
22146
22147 self.ime_transaction = self.ime_transaction.or(transaction);
22148 if let Some(transaction) = self.ime_transaction {
22149 self.buffer.update(cx, |buffer, cx| {
22150 buffer.group_until_transaction(transaction, cx);
22151 });
22152 }
22153
22154 if self.text_highlights::<InputComposition>(cx).is_none() {
22155 self.ime_transaction.take();
22156 }
22157 }
22158
22159 fn bounds_for_range(
22160 &mut self,
22161 range_utf16: Range<usize>,
22162 element_bounds: gpui::Bounds<Pixels>,
22163 window: &mut Window,
22164 cx: &mut Context<Self>,
22165 ) -> Option<gpui::Bounds<Pixels>> {
22166 let text_layout_details = self.text_layout_details(window);
22167 let gpui::Size {
22168 width: em_width,
22169 height: line_height,
22170 } = self.character_size(window);
22171
22172 let snapshot = self.snapshot(window, cx);
22173 let scroll_position = snapshot.scroll_position();
22174 let scroll_left = scroll_position.x * em_width;
22175
22176 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22177 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22178 + self.gutter_dimensions.width
22179 + self.gutter_dimensions.margin;
22180 let y = line_height * (start.row().as_f32() - scroll_position.y);
22181
22182 Some(Bounds {
22183 origin: element_bounds.origin + point(x, y),
22184 size: size(em_width, line_height),
22185 })
22186 }
22187
22188 fn character_index_for_point(
22189 &mut self,
22190 point: gpui::Point<Pixels>,
22191 _window: &mut Window,
22192 _cx: &mut Context<Self>,
22193 ) -> Option<usize> {
22194 let position_map = self.last_position_map.as_ref()?;
22195 if !position_map.text_hitbox.contains(&point) {
22196 return None;
22197 }
22198 let display_point = position_map.point_for_position(point).previous_valid;
22199 let anchor = position_map
22200 .snapshot
22201 .display_point_to_anchor(display_point, Bias::Left);
22202 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22203 Some(utf16_offset.0)
22204 }
22205}
22206
22207trait SelectionExt {
22208 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22209 fn spanned_rows(
22210 &self,
22211 include_end_if_at_line_start: bool,
22212 map: &DisplaySnapshot,
22213 ) -> Range<MultiBufferRow>;
22214}
22215
22216impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22217 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22218 let start = self
22219 .start
22220 .to_point(&map.buffer_snapshot)
22221 .to_display_point(map);
22222 let end = self
22223 .end
22224 .to_point(&map.buffer_snapshot)
22225 .to_display_point(map);
22226 if self.reversed {
22227 end..start
22228 } else {
22229 start..end
22230 }
22231 }
22232
22233 fn spanned_rows(
22234 &self,
22235 include_end_if_at_line_start: bool,
22236 map: &DisplaySnapshot,
22237 ) -> Range<MultiBufferRow> {
22238 let start = self.start.to_point(&map.buffer_snapshot);
22239 let mut end = self.end.to_point(&map.buffer_snapshot);
22240 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22241 end.row -= 1;
22242 }
22243
22244 let buffer_start = map.prev_line_boundary(start).0;
22245 let buffer_end = map.next_line_boundary(end).0;
22246 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22247 }
22248}
22249
22250impl<T: InvalidationRegion> InvalidationStack<T> {
22251 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22252 where
22253 S: Clone + ToOffset,
22254 {
22255 while let Some(region) = self.last() {
22256 let all_selections_inside_invalidation_ranges =
22257 if selections.len() == region.ranges().len() {
22258 selections
22259 .iter()
22260 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22261 .all(|(selection, invalidation_range)| {
22262 let head = selection.head().to_offset(buffer);
22263 invalidation_range.start <= head && invalidation_range.end >= head
22264 })
22265 } else {
22266 false
22267 };
22268
22269 if all_selections_inside_invalidation_ranges {
22270 break;
22271 } else {
22272 self.pop();
22273 }
22274 }
22275 }
22276}
22277
22278impl<T> Default for InvalidationStack<T> {
22279 fn default() -> Self {
22280 Self(Default::default())
22281 }
22282}
22283
22284impl<T> Deref for InvalidationStack<T> {
22285 type Target = Vec<T>;
22286
22287 fn deref(&self) -> &Self::Target {
22288 &self.0
22289 }
22290}
22291
22292impl<T> DerefMut for InvalidationStack<T> {
22293 fn deref_mut(&mut self) -> &mut Self::Target {
22294 &mut self.0
22295 }
22296}
22297
22298impl InvalidationRegion for SnippetState {
22299 fn ranges(&self) -> &[Range<Anchor>] {
22300 &self.ranges[self.active_index]
22301 }
22302}
22303
22304fn inline_completion_edit_text(
22305 current_snapshot: &BufferSnapshot,
22306 edits: &[(Range<Anchor>, String)],
22307 edit_preview: &EditPreview,
22308 include_deletions: bool,
22309 cx: &App,
22310) -> HighlightedText {
22311 let edits = edits
22312 .iter()
22313 .map(|(anchor, text)| {
22314 (
22315 anchor.start.text_anchor..anchor.end.text_anchor,
22316 text.clone(),
22317 )
22318 })
22319 .collect::<Vec<_>>();
22320
22321 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22322}
22323
22324pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22325 match severity {
22326 lsp::DiagnosticSeverity::ERROR => colors.error,
22327 lsp::DiagnosticSeverity::WARNING => colors.warning,
22328 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22329 lsp::DiagnosticSeverity::HINT => colors.info,
22330 _ => colors.ignored,
22331 }
22332}
22333
22334pub fn styled_runs_for_code_label<'a>(
22335 label: &'a CodeLabel,
22336 syntax_theme: &'a theme::SyntaxTheme,
22337) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22338 let fade_out = HighlightStyle {
22339 fade_out: Some(0.35),
22340 ..Default::default()
22341 };
22342
22343 let mut prev_end = label.filter_range.end;
22344 label
22345 .runs
22346 .iter()
22347 .enumerate()
22348 .flat_map(move |(ix, (range, highlight_id))| {
22349 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22350 style
22351 } else {
22352 return Default::default();
22353 };
22354 let mut muted_style = style;
22355 muted_style.highlight(fade_out);
22356
22357 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22358 if range.start >= label.filter_range.end {
22359 if range.start > prev_end {
22360 runs.push((prev_end..range.start, fade_out));
22361 }
22362 runs.push((range.clone(), muted_style));
22363 } else if range.end <= label.filter_range.end {
22364 runs.push((range.clone(), style));
22365 } else {
22366 runs.push((range.start..label.filter_range.end, style));
22367 runs.push((label.filter_range.end..range.end, muted_style));
22368 }
22369 prev_end = cmp::max(prev_end, range.end);
22370
22371 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22372 runs.push((prev_end..label.text.len(), fade_out));
22373 }
22374
22375 runs
22376 })
22377}
22378
22379pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22380 let mut prev_index = 0;
22381 let mut prev_codepoint: Option<char> = None;
22382 text.char_indices()
22383 .chain([(text.len(), '\0')])
22384 .filter_map(move |(index, codepoint)| {
22385 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22386 let is_boundary = index == text.len()
22387 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22388 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22389 if is_boundary {
22390 let chunk = &text[prev_index..index];
22391 prev_index = index;
22392 Some(chunk)
22393 } else {
22394 None
22395 }
22396 })
22397}
22398
22399pub trait RangeToAnchorExt: Sized {
22400 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22401
22402 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22403 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22404 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22405 }
22406}
22407
22408impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22409 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22410 let start_offset = self.start.to_offset(snapshot);
22411 let end_offset = self.end.to_offset(snapshot);
22412 if start_offset == end_offset {
22413 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22414 } else {
22415 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22416 }
22417 }
22418}
22419
22420pub trait RowExt {
22421 fn as_f32(&self) -> f32;
22422
22423 fn next_row(&self) -> Self;
22424
22425 fn previous_row(&self) -> Self;
22426
22427 fn minus(&self, other: Self) -> u32;
22428}
22429
22430impl RowExt for DisplayRow {
22431 fn as_f32(&self) -> f32 {
22432 self.0 as f32
22433 }
22434
22435 fn next_row(&self) -> Self {
22436 Self(self.0 + 1)
22437 }
22438
22439 fn previous_row(&self) -> Self {
22440 Self(self.0.saturating_sub(1))
22441 }
22442
22443 fn minus(&self, other: Self) -> u32 {
22444 self.0 - other.0
22445 }
22446}
22447
22448impl RowExt for MultiBufferRow {
22449 fn as_f32(&self) -> f32 {
22450 self.0 as f32
22451 }
22452
22453 fn next_row(&self) -> Self {
22454 Self(self.0 + 1)
22455 }
22456
22457 fn previous_row(&self) -> Self {
22458 Self(self.0.saturating_sub(1))
22459 }
22460
22461 fn minus(&self, other: Self) -> u32 {
22462 self.0 - other.0
22463 }
22464}
22465
22466trait RowRangeExt {
22467 type Row;
22468
22469 fn len(&self) -> usize;
22470
22471 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22472}
22473
22474impl RowRangeExt for Range<MultiBufferRow> {
22475 type Row = MultiBufferRow;
22476
22477 fn len(&self) -> usize {
22478 (self.end.0 - self.start.0) as usize
22479 }
22480
22481 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22482 (self.start.0..self.end.0).map(MultiBufferRow)
22483 }
22484}
22485
22486impl RowRangeExt for Range<DisplayRow> {
22487 type Row = DisplayRow;
22488
22489 fn len(&self) -> usize {
22490 (self.end.0 - self.start.0) as usize
22491 }
22492
22493 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22494 (self.start.0..self.end.0).map(DisplayRow)
22495 }
22496}
22497
22498/// If select range has more than one line, we
22499/// just point the cursor to range.start.
22500fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22501 if range.start.row == range.end.row {
22502 range
22503 } else {
22504 range.start..range.start
22505 }
22506}
22507pub struct KillRing(ClipboardItem);
22508impl Global for KillRing {}
22509
22510const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22511
22512enum BreakpointPromptEditAction {
22513 Log,
22514 Condition,
22515 HitCondition,
22516}
22517
22518struct BreakpointPromptEditor {
22519 pub(crate) prompt: Entity<Editor>,
22520 editor: WeakEntity<Editor>,
22521 breakpoint_anchor: Anchor,
22522 breakpoint: Breakpoint,
22523 edit_action: BreakpointPromptEditAction,
22524 block_ids: HashSet<CustomBlockId>,
22525 editor_margins: Arc<Mutex<EditorMargins>>,
22526 _subscriptions: Vec<Subscription>,
22527}
22528
22529impl BreakpointPromptEditor {
22530 const MAX_LINES: u8 = 4;
22531
22532 fn new(
22533 editor: WeakEntity<Editor>,
22534 breakpoint_anchor: Anchor,
22535 breakpoint: Breakpoint,
22536 edit_action: BreakpointPromptEditAction,
22537 window: &mut Window,
22538 cx: &mut Context<Self>,
22539 ) -> Self {
22540 let base_text = match edit_action {
22541 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22542 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22543 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22544 }
22545 .map(|msg| msg.to_string())
22546 .unwrap_or_default();
22547
22548 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22550
22551 let prompt = cx.new(|cx| {
22552 let mut prompt = Editor::new(
22553 EditorMode::AutoHeight {
22554 max_lines: Self::MAX_LINES as usize,
22555 },
22556 buffer,
22557 None,
22558 window,
22559 cx,
22560 );
22561 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22562 prompt.set_show_cursor_when_unfocused(false, cx);
22563 prompt.set_placeholder_text(
22564 match edit_action {
22565 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22566 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22567 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22568 },
22569 cx,
22570 );
22571
22572 prompt
22573 });
22574
22575 Self {
22576 prompt,
22577 editor,
22578 breakpoint_anchor,
22579 breakpoint,
22580 edit_action,
22581 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22582 block_ids: Default::default(),
22583 _subscriptions: vec![],
22584 }
22585 }
22586
22587 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22588 self.block_ids.extend(block_ids)
22589 }
22590
22591 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22592 if let Some(editor) = self.editor.upgrade() {
22593 let message = self
22594 .prompt
22595 .read(cx)
22596 .buffer
22597 .read(cx)
22598 .as_singleton()
22599 .expect("A multi buffer in breakpoint prompt isn't possible")
22600 .read(cx)
22601 .as_rope()
22602 .to_string();
22603
22604 editor.update(cx, |editor, cx| {
22605 editor.edit_breakpoint_at_anchor(
22606 self.breakpoint_anchor,
22607 self.breakpoint.clone(),
22608 match self.edit_action {
22609 BreakpointPromptEditAction::Log => {
22610 BreakpointEditAction::EditLogMessage(message.into())
22611 }
22612 BreakpointPromptEditAction::Condition => {
22613 BreakpointEditAction::EditCondition(message.into())
22614 }
22615 BreakpointPromptEditAction::HitCondition => {
22616 BreakpointEditAction::EditHitCondition(message.into())
22617 }
22618 },
22619 cx,
22620 );
22621
22622 editor.remove_blocks(self.block_ids.clone(), None, cx);
22623 cx.focus_self(window);
22624 });
22625 }
22626 }
22627
22628 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22629 self.editor
22630 .update(cx, |editor, cx| {
22631 editor.remove_blocks(self.block_ids.clone(), None, cx);
22632 window.focus(&editor.focus_handle);
22633 })
22634 .log_err();
22635 }
22636
22637 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22638 let settings = ThemeSettings::get_global(cx);
22639 let text_style = TextStyle {
22640 color: if self.prompt.read(cx).read_only(cx) {
22641 cx.theme().colors().text_disabled
22642 } else {
22643 cx.theme().colors().text
22644 },
22645 font_family: settings.buffer_font.family.clone(),
22646 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22647 font_size: settings.buffer_font_size(cx).into(),
22648 font_weight: settings.buffer_font.weight,
22649 line_height: relative(settings.buffer_line_height.value()),
22650 ..Default::default()
22651 };
22652 EditorElement::new(
22653 &self.prompt,
22654 EditorStyle {
22655 background: cx.theme().colors().editor_background,
22656 local_player: cx.theme().players().local(),
22657 text: text_style,
22658 ..Default::default()
22659 },
22660 )
22661 }
22662}
22663
22664impl Render for BreakpointPromptEditor {
22665 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22666 let editor_margins = *self.editor_margins.lock();
22667 let gutter_dimensions = editor_margins.gutter;
22668 h_flex()
22669 .key_context("Editor")
22670 .bg(cx.theme().colors().editor_background)
22671 .border_y_1()
22672 .border_color(cx.theme().status().info_border)
22673 .size_full()
22674 .py(window.line_height() / 2.5)
22675 .on_action(cx.listener(Self::confirm))
22676 .on_action(cx.listener(Self::cancel))
22677 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22678 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22679 }
22680}
22681
22682impl Focusable for BreakpointPromptEditor {
22683 fn focus_handle(&self, cx: &App) -> FocusHandle {
22684 self.prompt.focus_handle(cx)
22685 }
22686}
22687
22688fn all_edits_insertions_or_deletions(
22689 edits: &Vec<(Range<Anchor>, String)>,
22690 snapshot: &MultiBufferSnapshot,
22691) -> bool {
22692 let mut all_insertions = true;
22693 let mut all_deletions = true;
22694
22695 for (range, new_text) in edits.iter() {
22696 let range_is_empty = range.to_offset(&snapshot).is_empty();
22697 let text_is_empty = new_text.is_empty();
22698
22699 if range_is_empty != text_is_empty {
22700 if range_is_empty {
22701 all_deletions = false;
22702 } else {
22703 all_insertions = false;
22704 }
22705 } else {
22706 return false;
22707 }
22708
22709 if !all_insertions && !all_deletions {
22710 return false;
22711 }
22712 }
22713 all_insertions || all_deletions
22714}
22715
22716struct MissingEditPredictionKeybindingTooltip;
22717
22718impl Render for MissingEditPredictionKeybindingTooltip {
22719 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22720 ui::tooltip_container(window, cx, |container, _, cx| {
22721 container
22722 .flex_shrink_0()
22723 .max_w_80()
22724 .min_h(rems_from_px(124.))
22725 .justify_between()
22726 .child(
22727 v_flex()
22728 .flex_1()
22729 .text_ui_sm(cx)
22730 .child(Label::new("Conflict with Accept Keybinding"))
22731 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22732 )
22733 .child(
22734 h_flex()
22735 .pb_1()
22736 .gap_1()
22737 .items_end()
22738 .w_full()
22739 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22740 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22741 }))
22742 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22743 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22744 })),
22745 )
22746 })
22747 }
22748}
22749
22750#[derive(Debug, Clone, Copy, PartialEq)]
22751pub struct LineHighlight {
22752 pub background: Background,
22753 pub border: Option<gpui::Hsla>,
22754 pub include_gutter: bool,
22755 pub type_id: Option<TypeId>,
22756}
22757
22758fn render_diff_hunk_controls(
22759 row: u32,
22760 status: &DiffHunkStatus,
22761 hunk_range: Range<Anchor>,
22762 is_created_file: bool,
22763 line_height: Pixels,
22764 editor: &Entity<Editor>,
22765 _window: &mut Window,
22766 cx: &mut App,
22767) -> AnyElement {
22768 h_flex()
22769 .h(line_height)
22770 .mr_1()
22771 .gap_1()
22772 .px_0p5()
22773 .pb_1()
22774 .border_x_1()
22775 .border_b_1()
22776 .border_color(cx.theme().colors().border_variant)
22777 .rounded_b_lg()
22778 .bg(cx.theme().colors().editor_background)
22779 .gap_1()
22780 .block_mouse_except_scroll()
22781 .shadow_md()
22782 .child(if status.has_secondary_hunk() {
22783 Button::new(("stage", row as u64), "Stage")
22784 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22785 .tooltip({
22786 let focus_handle = editor.focus_handle(cx);
22787 move |window, cx| {
22788 Tooltip::for_action_in(
22789 "Stage Hunk",
22790 &::git::ToggleStaged,
22791 &focus_handle,
22792 window,
22793 cx,
22794 )
22795 }
22796 })
22797 .on_click({
22798 let editor = editor.clone();
22799 move |_event, _window, cx| {
22800 editor.update(cx, |editor, cx| {
22801 editor.stage_or_unstage_diff_hunks(
22802 true,
22803 vec![hunk_range.start..hunk_range.start],
22804 cx,
22805 );
22806 });
22807 }
22808 })
22809 } else {
22810 Button::new(("unstage", row as u64), "Unstage")
22811 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22812 .tooltip({
22813 let focus_handle = editor.focus_handle(cx);
22814 move |window, cx| {
22815 Tooltip::for_action_in(
22816 "Unstage Hunk",
22817 &::git::ToggleStaged,
22818 &focus_handle,
22819 window,
22820 cx,
22821 )
22822 }
22823 })
22824 .on_click({
22825 let editor = editor.clone();
22826 move |_event, _window, cx| {
22827 editor.update(cx, |editor, cx| {
22828 editor.stage_or_unstage_diff_hunks(
22829 false,
22830 vec![hunk_range.start..hunk_range.start],
22831 cx,
22832 );
22833 });
22834 }
22835 })
22836 })
22837 .child(
22838 Button::new(("restore", row as u64), "Restore")
22839 .tooltip({
22840 let focus_handle = editor.focus_handle(cx);
22841 move |window, cx| {
22842 Tooltip::for_action_in(
22843 "Restore Hunk",
22844 &::git::Restore,
22845 &focus_handle,
22846 window,
22847 cx,
22848 )
22849 }
22850 })
22851 .on_click({
22852 let editor = editor.clone();
22853 move |_event, window, cx| {
22854 editor.update(cx, |editor, cx| {
22855 let snapshot = editor.snapshot(window, cx);
22856 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22857 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22858 });
22859 }
22860 })
22861 .disabled(is_created_file),
22862 )
22863 .when(
22864 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22865 |el| {
22866 el.child(
22867 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22868 .shape(IconButtonShape::Square)
22869 .icon_size(IconSize::Small)
22870 // .disabled(!has_multiple_hunks)
22871 .tooltip({
22872 let focus_handle = editor.focus_handle(cx);
22873 move |window, cx| {
22874 Tooltip::for_action_in(
22875 "Next Hunk",
22876 &GoToHunk,
22877 &focus_handle,
22878 window,
22879 cx,
22880 )
22881 }
22882 })
22883 .on_click({
22884 let editor = editor.clone();
22885 move |_event, window, cx| {
22886 editor.update(cx, |editor, cx| {
22887 let snapshot = editor.snapshot(window, cx);
22888 let position =
22889 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22890 editor.go_to_hunk_before_or_after_position(
22891 &snapshot,
22892 position,
22893 Direction::Next,
22894 window,
22895 cx,
22896 );
22897 editor.expand_selected_diff_hunks(cx);
22898 });
22899 }
22900 }),
22901 )
22902 .child(
22903 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22904 .shape(IconButtonShape::Square)
22905 .icon_size(IconSize::Small)
22906 // .disabled(!has_multiple_hunks)
22907 .tooltip({
22908 let focus_handle = editor.focus_handle(cx);
22909 move |window, cx| {
22910 Tooltip::for_action_in(
22911 "Previous Hunk",
22912 &GoToPreviousHunk,
22913 &focus_handle,
22914 window,
22915 cx,
22916 )
22917 }
22918 })
22919 .on_click({
22920 let editor = editor.clone();
22921 move |_event, window, cx| {
22922 editor.update(cx, |editor, cx| {
22923 let snapshot = editor.snapshot(window, cx);
22924 let point =
22925 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22926 editor.go_to_hunk_before_or_after_position(
22927 &snapshot,
22928 point,
22929 Direction::Prev,
22930 window,
22931 cx,
22932 );
22933 editor.expand_selected_diff_hunks(cx);
22934 });
22935 }
22936 }),
22937 )
22938 },
22939 )
22940 .into_any_element()
22941}