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, ThemeColors, 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, SaveOptions},
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 mode: ColumnarMode,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug, PartialEq)]
466pub enum ColumnarMode {
467 FromMouse,
468 FromSelection,
469}
470
471#[derive(Clone, Debug)]
472pub enum SelectMode {
473 Character,
474 Word(Range<Anchor>),
475 Line(Range<Anchor>),
476 All,
477}
478
479#[derive(Clone, PartialEq, Eq, Debug)]
480pub enum EditorMode {
481 SingleLine {
482 auto_width: bool,
483 },
484 AutoHeight {
485 min_lines: usize,
486 max_lines: usize,
487 },
488 Full {
489 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
490 scale_ui_elements_with_buffer_font_size: bool,
491 /// When set to `true`, the editor will render a background for the active line.
492 show_active_line_background: bool,
493 /// When set to `true`, the editor's height will be determined by its content.
494 sized_by_content: bool,
495 },
496 Minimap {
497 parent: WeakEntity<Editor>,
498 },
499}
500
501impl EditorMode {
502 pub fn full() -> Self {
503 Self::Full {
504 scale_ui_elements_with_buffer_font_size: true,
505 show_active_line_background: true,
506 sized_by_content: false,
507 }
508 }
509
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 fn is_minimap(&self) -> bool {
515 matches!(self, Self::Minimap { .. })
516 }
517}
518
519#[derive(Copy, Clone, Debug)]
520pub enum SoftWrap {
521 /// Prefer not to wrap at all.
522 ///
523 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
524 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
525 GitDiff,
526 /// Prefer a single line generally, unless an overly long line is encountered.
527 None,
528 /// Soft wrap lines that exceed the editor width.
529 EditorWidth,
530 /// Soft wrap lines at the preferred line length.
531 Column(u32),
532 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
533 Bounded(u32),
534}
535
536#[derive(Clone)]
537pub struct EditorStyle {
538 pub background: Hsla,
539 pub local_player: PlayerColor,
540 pub text: TextStyle,
541 pub scrollbar_width: Pixels,
542 pub syntax: Arc<SyntaxTheme>,
543 pub status: StatusColors,
544 pub inlay_hints_style: HighlightStyle,
545 pub inline_completion_styles: InlineCompletionStyles,
546 pub unnecessary_code_fade: f32,
547 pub show_underlines: bool,
548}
549
550impl Default for EditorStyle {
551 fn default() -> Self {
552 Self {
553 background: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 inline_completion_styles: InlineCompletionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 HighlightStyle {
579 color: Some(cx.theme().status().hint),
580 background_color: show_background.then(|| cx.theme().status().hint_background),
581 ..HighlightStyle::default()
582 }
583}
584
585pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
586 InlineCompletionStyles {
587 insertion: HighlightStyle {
588 color: Some(cx.theme().status().predictive),
589 ..HighlightStyle::default()
590 },
591 whitespace: HighlightStyle {
592 background_color: Some(cx.theme().status().created_background),
593 ..HighlightStyle::default()
594 },
595 }
596}
597
598type CompletionId = usize;
599
600pub(crate) enum EditDisplayMode {
601 TabAccept,
602 DiffPopover,
603 Inline,
604}
605
606enum InlineCompletion {
607 Edit {
608 edits: Vec<(Range<Anchor>, String)>,
609 edit_preview: Option<EditPreview>,
610 display_mode: EditDisplayMode,
611 snapshot: BufferSnapshot,
612 },
613 Move {
614 target: Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct InlineCompletionState {
620 inlay_ids: Vec<InlayId>,
621 completion: InlineCompletion,
622 completion_id: Option<SharedString>,
623 invalidation_range: Range<Anchor>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum InlineCompletionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuInlineCompletionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
708type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
709
710#[derive(Default)]
711struct ScrollbarMarkerState {
712 scrollbar_size: Size<Pixels>,
713 dirty: bool,
714 markers: Arc<[PaintQuad]>,
715 pending_refresh: Option<Task<Result<()>>>,
716}
717
718impl ScrollbarMarkerState {
719 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
720 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
721 }
722}
723
724#[derive(Clone, Copy, PartialEq, Eq)]
725pub enum MinimapVisibility {
726 Disabled,
727 Enabled {
728 /// The configuration currently present in the users settings.
729 setting_configuration: bool,
730 /// Whether to override the currently set visibility from the users setting.
731 toggle_override: bool,
732 },
733}
734
735impl MinimapVisibility {
736 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
737 if mode.is_full() {
738 Self::Enabled {
739 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
740 toggle_override: false,
741 }
742 } else {
743 Self::Disabled
744 }
745 }
746
747 fn hidden(&self) -> Self {
748 match *self {
749 Self::Enabled {
750 setting_configuration,
751 ..
752 } => Self::Enabled {
753 setting_configuration,
754 toggle_override: setting_configuration,
755 },
756 Self::Disabled => Self::Disabled,
757 }
758 }
759
760 fn disabled(&self) -> bool {
761 match *self {
762 Self::Disabled => true,
763 _ => false,
764 }
765 }
766
767 fn settings_visibility(&self) -> bool {
768 match *self {
769 Self::Enabled {
770 setting_configuration,
771 ..
772 } => setting_configuration,
773 _ => false,
774 }
775 }
776
777 fn visible(&self) -> bool {
778 match *self {
779 Self::Enabled {
780 setting_configuration,
781 toggle_override,
782 } => setting_configuration ^ toggle_override,
783 _ => false,
784 }
785 }
786
787 fn toggle_visibility(&self) -> Self {
788 match *self {
789 Self::Enabled {
790 toggle_override,
791 setting_configuration,
792 } => Self::Enabled {
793 setting_configuration,
794 toggle_override: !toggle_override,
795 },
796 Self::Disabled => Self::Disabled,
797 }
798 }
799}
800
801#[derive(Clone, Debug)]
802struct RunnableTasks {
803 templates: Vec<(TaskSourceKind, TaskTemplate)>,
804 offset: multi_buffer::Anchor,
805 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
806 column: u32,
807 // Values of all named captures, including those starting with '_'
808 extra_variables: HashMap<String, String>,
809 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
810 context_range: Range<BufferOffset>,
811}
812
813impl RunnableTasks {
814 fn resolve<'a>(
815 &'a self,
816 cx: &'a task::TaskContext,
817 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
818 self.templates.iter().filter_map(|(kind, template)| {
819 template
820 .resolve_task(&kind.to_id_base(), cx)
821 .map(|task| (kind.clone(), task))
822 })
823 }
824}
825
826#[derive(Clone)]
827pub struct ResolvedTasks {
828 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
829 position: Anchor,
830}
831
832#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
833struct BufferOffset(usize);
834
835// Addons allow storing per-editor state in other crates (e.g. Vim)
836pub trait Addon: 'static {
837 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
838
839 fn render_buffer_header_controls(
840 &self,
841 _: &ExcerptInfo,
842 _: &Window,
843 _: &App,
844 ) -> Option<AnyElement> {
845 None
846 }
847
848 fn to_any(&self) -> &dyn std::any::Any;
849
850 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
851 None
852 }
853}
854
855/// A set of caret positions, registered when the editor was edited.
856pub struct ChangeList {
857 changes: Vec<Vec<Anchor>>,
858 /// Currently "selected" change.
859 position: Option<usize>,
860}
861
862impl ChangeList {
863 pub fn new() -> Self {
864 Self {
865 changes: Vec::new(),
866 position: None,
867 }
868 }
869
870 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
871 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
872 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
873 if self.changes.is_empty() {
874 return None;
875 }
876
877 let prev = self.position.unwrap_or(self.changes.len());
878 let next = if direction == Direction::Prev {
879 prev.saturating_sub(count)
880 } else {
881 (prev + count).min(self.changes.len() - 1)
882 };
883 self.position = Some(next);
884 self.changes.get(next).map(|anchors| anchors.as_slice())
885 }
886
887 /// Adds a new change to the list, resetting the change list position.
888 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
889 self.position.take();
890 if pop_state {
891 self.changes.pop();
892 }
893 self.changes.push(new_positions.clone());
894 }
895
896 pub fn last(&self) -> Option<&[Anchor]> {
897 self.changes.last().map(|anchors| anchors.as_slice())
898 }
899}
900
901#[derive(Clone)]
902struct InlineBlamePopoverState {
903 scroll_handle: ScrollHandle,
904 commit_message: Option<ParsedCommitMessage>,
905 markdown: Entity<Markdown>,
906}
907
908struct InlineBlamePopover {
909 position: gpui::Point<Pixels>,
910 hide_task: Option<Task<()>>,
911 popover_bounds: Option<Bounds<Pixels>>,
912 popover_state: InlineBlamePopoverState,
913}
914
915enum SelectionDragState {
916 /// State when no drag related activity is detected.
917 None,
918 /// State when the mouse is down on a selection that is about to be dragged.
919 ReadyToDrag {
920 selection: Selection<Anchor>,
921 click_position: gpui::Point<Pixels>,
922 mouse_down_time: Instant,
923 },
924 /// State when the mouse is dragging the selection in the editor.
925 Dragging {
926 selection: Selection<Anchor>,
927 drop_cursor: Selection<Anchor>,
928 hide_drop_cursor: bool,
929 },
930}
931
932enum ColumnarSelectionState {
933 FromMouse {
934 selection_tail: Anchor,
935 display_point: Option<DisplayPoint>,
936 },
937 FromSelection {
938 selection_tail: Anchor,
939 },
940}
941
942/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
943/// a breakpoint on them.
944#[derive(Clone, Copy, Debug, PartialEq, Eq)]
945struct PhantomBreakpointIndicator {
946 display_row: DisplayRow,
947 /// There's a small debounce between hovering over the line and showing the indicator.
948 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
949 is_active: bool,
950 collides_with_existing_breakpoint: bool,
951}
952
953/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
954///
955/// See the [module level documentation](self) for more information.
956pub struct Editor {
957 focus_handle: FocusHandle,
958 last_focused_descendant: Option<WeakFocusHandle>,
959 /// The text buffer being edited
960 buffer: Entity<MultiBuffer>,
961 /// Map of how text in the buffer should be displayed.
962 /// Handles soft wraps, folds, fake inlay text insertions, etc.
963 pub display_map: Entity<DisplayMap>,
964 pub selections: SelectionsCollection,
965 pub scroll_manager: ScrollManager,
966 /// When inline assist editors are linked, they all render cursors because
967 /// typing enters text into each of them, even the ones that aren't focused.
968 pub(crate) show_cursor_when_unfocused: bool,
969 columnar_selection_state: Option<ColumnarSelectionState>,
970 add_selections_state: Option<AddSelectionsState>,
971 select_next_state: Option<SelectNextState>,
972 select_prev_state: Option<SelectNextState>,
973 selection_history: SelectionHistory,
974 defer_selection_effects: bool,
975 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
976 autoclose_regions: Vec<AutocloseRegion>,
977 snippet_stack: InvalidationStack<SnippetState>,
978 select_syntax_node_history: SelectSyntaxNodeHistory,
979 ime_transaction: Option<TransactionId>,
980 pub diagnostics_max_severity: DiagnosticSeverity,
981 active_diagnostics: ActiveDiagnostic,
982 show_inline_diagnostics: bool,
983 inline_diagnostics_update: Task<()>,
984 inline_diagnostics_enabled: bool,
985 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
986 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
987 hard_wrap: Option<usize>,
988
989 // TODO: make this a access method
990 pub project: Option<Entity<Project>>,
991 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
992 completion_provider: Option<Rc<dyn CompletionProvider>>,
993 collaboration_hub: Option<Box<dyn CollaborationHub>>,
994 blink_manager: Entity<BlinkManager>,
995 show_cursor_names: bool,
996 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
997 pub show_local_selections: bool,
998 mode: EditorMode,
999 show_breadcrumbs: bool,
1000 show_gutter: bool,
1001 show_scrollbars: ScrollbarAxes,
1002 minimap_visibility: MinimapVisibility,
1003 offset_content: bool,
1004 disable_expand_excerpt_buttons: bool,
1005 show_line_numbers: Option<bool>,
1006 use_relative_line_numbers: Option<bool>,
1007 show_git_diff_gutter: Option<bool>,
1008 show_code_actions: Option<bool>,
1009 show_runnables: Option<bool>,
1010 show_breakpoints: Option<bool>,
1011 show_wrap_guides: Option<bool>,
1012 show_indent_guides: Option<bool>,
1013 placeholder_text: Option<Arc<str>>,
1014 highlight_order: usize,
1015 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1016 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1017 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1018 scrollbar_marker_state: ScrollbarMarkerState,
1019 active_indent_guides_state: ActiveIndentGuidesState,
1020 nav_history: Option<ItemNavHistory>,
1021 context_menu: RefCell<Option<CodeContextMenu>>,
1022 context_menu_options: Option<ContextMenuOptions>,
1023 mouse_context_menu: Option<MouseContextMenu>,
1024 completion_tasks: Vec<(CompletionId, Task<()>)>,
1025 inline_blame_popover: Option<InlineBlamePopover>,
1026 inline_blame_popover_show_task: Option<Task<()>>,
1027 signature_help_state: SignatureHelpState,
1028 auto_signature_help: Option<bool>,
1029 find_all_references_task_sources: Vec<Anchor>,
1030 next_completion_id: CompletionId,
1031 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1032 code_actions_task: Option<Task<Result<()>>>,
1033 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1034 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1035 document_highlights_task: Option<Task<()>>,
1036 linked_editing_range_task: Option<Task<Option<()>>>,
1037 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1038 pending_rename: Option<RenameState>,
1039 searchable: bool,
1040 cursor_shape: CursorShape,
1041 current_line_highlight: Option<CurrentLineHighlight>,
1042 collapse_matches: bool,
1043 autoindent_mode: Option<AutoindentMode>,
1044 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1045 input_enabled: bool,
1046 use_modal_editing: bool,
1047 read_only: bool,
1048 leader_id: Option<CollaboratorId>,
1049 remote_id: Option<ViewId>,
1050 pub hover_state: HoverState,
1051 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1052 gutter_hovered: bool,
1053 hovered_link_state: Option<HoveredLinkState>,
1054 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1055 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1056 active_inline_completion: Option<InlineCompletionState>,
1057 /// Used to prevent flickering as the user types while the menu is open
1058 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1059 edit_prediction_settings: EditPredictionSettings,
1060 inline_completions_hidden_for_vim_mode: bool,
1061 show_inline_completions_override: Option<bool>,
1062 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1063 edit_prediction_preview: EditPredictionPreview,
1064 edit_prediction_indent_conflict: bool,
1065 edit_prediction_requires_modifier_in_indent_conflict: bool,
1066 inlay_hint_cache: InlayHintCache,
1067 next_inlay_id: usize,
1068 _subscriptions: Vec<Subscription>,
1069 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1070 gutter_dimensions: GutterDimensions,
1071 style: Option<EditorStyle>,
1072 text_style_refinement: Option<TextStyleRefinement>,
1073 next_editor_action_id: EditorActionId,
1074 editor_actions: Rc<
1075 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1076 >,
1077 use_autoclose: bool,
1078 use_auto_surround: bool,
1079 auto_replace_emoji_shortcode: bool,
1080 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1081 show_git_blame_gutter: bool,
1082 show_git_blame_inline: bool,
1083 show_git_blame_inline_delay_task: Option<Task<()>>,
1084 git_blame_inline_enabled: bool,
1085 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1086 serialize_dirty_buffers: bool,
1087 show_selection_menu: Option<bool>,
1088 blame: Option<Entity<GitBlame>>,
1089 blame_subscription: Option<Subscription>,
1090 custom_context_menu: Option<
1091 Box<
1092 dyn 'static
1093 + Fn(
1094 &mut Self,
1095 DisplayPoint,
1096 &mut Window,
1097 &mut Context<Self>,
1098 ) -> Option<Entity<ui::ContextMenu>>,
1099 >,
1100 >,
1101 last_bounds: Option<Bounds<Pixels>>,
1102 last_position_map: Option<Rc<PositionMap>>,
1103 expect_bounds_change: Option<Bounds<Pixels>>,
1104 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1105 tasks_update_task: Option<Task<()>>,
1106 breakpoint_store: Option<Entity<BreakpointStore>>,
1107 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1108 hovered_diff_hunk_row: Option<DisplayRow>,
1109 pull_diagnostics_task: Task<()>,
1110 in_project_search: bool,
1111 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1112 breadcrumb_header: Option<String>,
1113 focused_block: Option<FocusedBlock>,
1114 next_scroll_position: NextScrollCursorCenterTopBottom,
1115 addons: HashMap<TypeId, Box<dyn Addon>>,
1116 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1117 load_diff_task: Option<Shared<Task<()>>>,
1118 /// Whether we are temporarily displaying a diff other than git's
1119 temporary_diff_override: bool,
1120 selection_mark_mode: bool,
1121 toggle_fold_multiple_buffers: Task<()>,
1122 _scroll_cursor_center_top_bottom_task: Task<()>,
1123 serialize_selections: Task<()>,
1124 serialize_folds: Task<()>,
1125 mouse_cursor_hidden: bool,
1126 minimap: Option<Entity<Self>>,
1127 hide_mouse_mode: HideMouseMode,
1128 pub change_list: ChangeList,
1129 inline_value_cache: InlineValueCache,
1130 selection_drag_state: SelectionDragState,
1131 drag_and_drop_selection_enabled: bool,
1132}
1133
1134#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1135enum NextScrollCursorCenterTopBottom {
1136 #[default]
1137 Center,
1138 Top,
1139 Bottom,
1140}
1141
1142impl NextScrollCursorCenterTopBottom {
1143 fn next(&self) -> Self {
1144 match self {
1145 Self::Center => Self::Top,
1146 Self::Top => Self::Bottom,
1147 Self::Bottom => Self::Center,
1148 }
1149 }
1150}
1151
1152#[derive(Clone)]
1153pub struct EditorSnapshot {
1154 pub mode: EditorMode,
1155 show_gutter: bool,
1156 show_line_numbers: Option<bool>,
1157 show_git_diff_gutter: Option<bool>,
1158 show_code_actions: Option<bool>,
1159 show_runnables: Option<bool>,
1160 show_breakpoints: Option<bool>,
1161 git_blame_gutter_max_author_length: Option<usize>,
1162 pub display_snapshot: DisplaySnapshot,
1163 pub placeholder_text: Option<Arc<str>>,
1164 is_focused: bool,
1165 scroll_anchor: ScrollAnchor,
1166 ongoing_scroll: OngoingScroll,
1167 current_line_highlight: CurrentLineHighlight,
1168 gutter_hovered: bool,
1169}
1170
1171#[derive(Default, Debug, Clone, Copy)]
1172pub struct GutterDimensions {
1173 pub left_padding: Pixels,
1174 pub right_padding: Pixels,
1175 pub width: Pixels,
1176 pub margin: Pixels,
1177 pub git_blame_entries_width: Option<Pixels>,
1178}
1179
1180impl GutterDimensions {
1181 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1182 Self {
1183 margin: Self::default_gutter_margin(font_id, font_size, cx),
1184 ..Default::default()
1185 }
1186 }
1187
1188 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1189 -cx.text_system().descent(font_id, font_size)
1190 }
1191 /// The full width of the space taken up by the gutter.
1192 pub fn full_width(&self) -> Pixels {
1193 self.margin + self.width
1194 }
1195
1196 /// The width of the space reserved for the fold indicators,
1197 /// use alongside 'justify_end' and `gutter_width` to
1198 /// right align content with the line numbers
1199 pub fn fold_area_width(&self) -> Pixels {
1200 self.margin + self.right_padding
1201 }
1202}
1203
1204#[derive(Debug)]
1205pub struct RemoteSelection {
1206 pub replica_id: ReplicaId,
1207 pub selection: Selection<Anchor>,
1208 pub cursor_shape: CursorShape,
1209 pub collaborator_id: CollaboratorId,
1210 pub line_mode: bool,
1211 pub user_name: Option<SharedString>,
1212 pub color: PlayerColor,
1213}
1214
1215#[derive(Clone, Debug)]
1216struct SelectionHistoryEntry {
1217 selections: Arc<[Selection<Anchor>]>,
1218 select_next_state: Option<SelectNextState>,
1219 select_prev_state: Option<SelectNextState>,
1220 add_selections_state: Option<AddSelectionsState>,
1221}
1222
1223#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1224enum SelectionHistoryMode {
1225 Normal,
1226 Undoing,
1227 Redoing,
1228 Skipping,
1229}
1230
1231#[derive(Clone, PartialEq, Eq, Hash)]
1232struct HoveredCursor {
1233 replica_id: u16,
1234 selection_id: usize,
1235}
1236
1237impl Default for SelectionHistoryMode {
1238 fn default() -> Self {
1239 Self::Normal
1240 }
1241}
1242
1243#[derive(Debug)]
1244pub struct SelectionEffects {
1245 nav_history: bool,
1246 completions: bool,
1247 scroll: Option<Autoscroll>,
1248}
1249
1250impl Default for SelectionEffects {
1251 fn default() -> Self {
1252 Self {
1253 nav_history: true,
1254 completions: true,
1255 scroll: Some(Autoscroll::fit()),
1256 }
1257 }
1258}
1259impl SelectionEffects {
1260 pub fn scroll(scroll: Autoscroll) -> Self {
1261 Self {
1262 scroll: Some(scroll),
1263 ..Default::default()
1264 }
1265 }
1266
1267 pub fn no_scroll() -> Self {
1268 Self {
1269 scroll: None,
1270 ..Default::default()
1271 }
1272 }
1273
1274 pub fn completions(self, completions: bool) -> Self {
1275 Self {
1276 completions,
1277 ..self
1278 }
1279 }
1280
1281 pub fn nav_history(self, nav_history: bool) -> Self {
1282 Self {
1283 nav_history,
1284 ..self
1285 }
1286 }
1287}
1288
1289struct DeferredSelectionEffectsState {
1290 changed: bool,
1291 effects: SelectionEffects,
1292 old_cursor_position: Anchor,
1293 history_entry: SelectionHistoryEntry,
1294}
1295
1296#[derive(Default)]
1297struct SelectionHistory {
1298 #[allow(clippy::type_complexity)]
1299 selections_by_transaction:
1300 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1301 mode: SelectionHistoryMode,
1302 undo_stack: VecDeque<SelectionHistoryEntry>,
1303 redo_stack: VecDeque<SelectionHistoryEntry>,
1304}
1305
1306impl SelectionHistory {
1307 #[track_caller]
1308 fn insert_transaction(
1309 &mut self,
1310 transaction_id: TransactionId,
1311 selections: Arc<[Selection<Anchor>]>,
1312 ) {
1313 if selections.is_empty() {
1314 log::error!(
1315 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1316 std::panic::Location::caller()
1317 );
1318 return;
1319 }
1320 self.selections_by_transaction
1321 .insert(transaction_id, (selections, None));
1322 }
1323
1324 #[allow(clippy::type_complexity)]
1325 fn transaction(
1326 &self,
1327 transaction_id: TransactionId,
1328 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1329 self.selections_by_transaction.get(&transaction_id)
1330 }
1331
1332 #[allow(clippy::type_complexity)]
1333 fn transaction_mut(
1334 &mut self,
1335 transaction_id: TransactionId,
1336 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1337 self.selections_by_transaction.get_mut(&transaction_id)
1338 }
1339
1340 fn push(&mut self, entry: SelectionHistoryEntry) {
1341 if !entry.selections.is_empty() {
1342 match self.mode {
1343 SelectionHistoryMode::Normal => {
1344 self.push_undo(entry);
1345 self.redo_stack.clear();
1346 }
1347 SelectionHistoryMode::Undoing => self.push_redo(entry),
1348 SelectionHistoryMode::Redoing => self.push_undo(entry),
1349 SelectionHistoryMode::Skipping => {}
1350 }
1351 }
1352 }
1353
1354 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1355 if self
1356 .undo_stack
1357 .back()
1358 .map_or(true, |e| e.selections != entry.selections)
1359 {
1360 self.undo_stack.push_back(entry);
1361 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1362 self.undo_stack.pop_front();
1363 }
1364 }
1365 }
1366
1367 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1368 if self
1369 .redo_stack
1370 .back()
1371 .map_or(true, |e| e.selections != entry.selections)
1372 {
1373 self.redo_stack.push_back(entry);
1374 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1375 self.redo_stack.pop_front();
1376 }
1377 }
1378 }
1379}
1380
1381#[derive(Clone, Copy)]
1382pub struct RowHighlightOptions {
1383 pub autoscroll: bool,
1384 pub include_gutter: bool,
1385}
1386
1387impl Default for RowHighlightOptions {
1388 fn default() -> Self {
1389 Self {
1390 autoscroll: Default::default(),
1391 include_gutter: true,
1392 }
1393 }
1394}
1395
1396struct RowHighlight {
1397 index: usize,
1398 range: Range<Anchor>,
1399 color: Hsla,
1400 options: RowHighlightOptions,
1401 type_id: TypeId,
1402}
1403
1404#[derive(Clone, Debug)]
1405struct AddSelectionsState {
1406 groups: Vec<AddSelectionsGroup>,
1407}
1408
1409#[derive(Clone, Debug)]
1410struct AddSelectionsGroup {
1411 above: bool,
1412 stack: Vec<usize>,
1413}
1414
1415#[derive(Clone)]
1416struct SelectNextState {
1417 query: AhoCorasick,
1418 wordwise: bool,
1419 done: bool,
1420}
1421
1422impl std::fmt::Debug for SelectNextState {
1423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1424 f.debug_struct(std::any::type_name::<Self>())
1425 .field("wordwise", &self.wordwise)
1426 .field("done", &self.done)
1427 .finish()
1428 }
1429}
1430
1431#[derive(Debug)]
1432struct AutocloseRegion {
1433 selection_id: usize,
1434 range: Range<Anchor>,
1435 pair: BracketPair,
1436}
1437
1438#[derive(Debug)]
1439struct SnippetState {
1440 ranges: Vec<Vec<Range<Anchor>>>,
1441 active_index: usize,
1442 choices: Vec<Option<Vec<String>>>,
1443}
1444
1445#[doc(hidden)]
1446pub struct RenameState {
1447 pub range: Range<Anchor>,
1448 pub old_name: Arc<str>,
1449 pub editor: Entity<Editor>,
1450 block_id: CustomBlockId,
1451}
1452
1453struct InvalidationStack<T>(Vec<T>);
1454
1455struct RegisteredInlineCompletionProvider {
1456 provider: Arc<dyn InlineCompletionProviderHandle>,
1457 _subscription: Subscription,
1458}
1459
1460#[derive(Debug, PartialEq, Eq)]
1461pub struct ActiveDiagnosticGroup {
1462 pub active_range: Range<Anchor>,
1463 pub active_message: String,
1464 pub group_id: usize,
1465 pub blocks: HashSet<CustomBlockId>,
1466}
1467
1468#[derive(Debug, PartialEq, Eq)]
1469
1470pub(crate) enum ActiveDiagnostic {
1471 None,
1472 All,
1473 Group(ActiveDiagnosticGroup),
1474}
1475
1476#[derive(Serialize, Deserialize, Clone, Debug)]
1477pub struct ClipboardSelection {
1478 /// The number of bytes in this selection.
1479 pub len: usize,
1480 /// Whether this was a full-line selection.
1481 pub is_entire_line: bool,
1482 /// The indentation of the first line when this content was originally copied.
1483 pub first_line_indent: u32,
1484}
1485
1486// selections, scroll behavior, was newest selection reversed
1487type SelectSyntaxNodeHistoryState = (
1488 Box<[Selection<usize>]>,
1489 SelectSyntaxNodeScrollBehavior,
1490 bool,
1491);
1492
1493#[derive(Default)]
1494struct SelectSyntaxNodeHistory {
1495 stack: Vec<SelectSyntaxNodeHistoryState>,
1496 // disable temporarily to allow changing selections without losing the stack
1497 pub disable_clearing: bool,
1498}
1499
1500impl SelectSyntaxNodeHistory {
1501 pub fn try_clear(&mut self) {
1502 if !self.disable_clearing {
1503 self.stack.clear();
1504 }
1505 }
1506
1507 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1508 self.stack.push(selection);
1509 }
1510
1511 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1512 self.stack.pop()
1513 }
1514}
1515
1516enum SelectSyntaxNodeScrollBehavior {
1517 CursorTop,
1518 FitSelection,
1519 CursorBottom,
1520}
1521
1522#[derive(Debug)]
1523pub(crate) struct NavigationData {
1524 cursor_anchor: Anchor,
1525 cursor_position: Point,
1526 scroll_anchor: ScrollAnchor,
1527 scroll_top_row: u32,
1528}
1529
1530#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1531pub enum GotoDefinitionKind {
1532 Symbol,
1533 Declaration,
1534 Type,
1535 Implementation,
1536}
1537
1538#[derive(Debug, Clone)]
1539enum InlayHintRefreshReason {
1540 ModifiersChanged(bool),
1541 Toggle(bool),
1542 SettingsChange(InlayHintSettings),
1543 NewLinesShown,
1544 BufferEdited(HashSet<Arc<Language>>),
1545 RefreshRequested,
1546 ExcerptsRemoved(Vec<ExcerptId>),
1547}
1548
1549impl InlayHintRefreshReason {
1550 fn description(&self) -> &'static str {
1551 match self {
1552 Self::ModifiersChanged(_) => "modifiers changed",
1553 Self::Toggle(_) => "toggle",
1554 Self::SettingsChange(_) => "settings change",
1555 Self::NewLinesShown => "new lines shown",
1556 Self::BufferEdited(_) => "buffer edited",
1557 Self::RefreshRequested => "refresh requested",
1558 Self::ExcerptsRemoved(_) => "excerpts removed",
1559 }
1560 }
1561}
1562
1563pub enum FormatTarget {
1564 Buffers(HashSet<Entity<Buffer>>),
1565 Ranges(Vec<Range<MultiBufferPoint>>),
1566}
1567
1568pub(crate) struct FocusedBlock {
1569 id: BlockId,
1570 focus_handle: WeakFocusHandle,
1571}
1572
1573#[derive(Clone)]
1574enum JumpData {
1575 MultiBufferRow {
1576 row: MultiBufferRow,
1577 line_offset_from_top: u32,
1578 },
1579 MultiBufferPoint {
1580 excerpt_id: ExcerptId,
1581 position: Point,
1582 anchor: text::Anchor,
1583 line_offset_from_top: u32,
1584 },
1585}
1586
1587pub enum MultibufferSelectionMode {
1588 First,
1589 All,
1590}
1591
1592#[derive(Clone, Copy, Debug, Default)]
1593pub struct RewrapOptions {
1594 pub override_language_settings: bool,
1595 pub preserve_existing_whitespace: bool,
1596}
1597
1598impl Editor {
1599 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1600 let buffer = cx.new(|cx| Buffer::local("", cx));
1601 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1602 Self::new(
1603 EditorMode::SingleLine { auto_width: false },
1604 buffer,
1605 None,
1606 window,
1607 cx,
1608 )
1609 }
1610
1611 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1612 let buffer = cx.new(|cx| Buffer::local("", cx));
1613 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1614 Self::new(EditorMode::full(), buffer, None, window, cx)
1615 }
1616
1617 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1618 let buffer = cx.new(|cx| Buffer::local("", cx));
1619 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1620 Self::new(
1621 EditorMode::SingleLine { auto_width: true },
1622 buffer,
1623 None,
1624 window,
1625 cx,
1626 )
1627 }
1628
1629 pub fn auto_height(
1630 min_lines: usize,
1631 max_lines: usize,
1632 window: &mut Window,
1633 cx: &mut Context<Self>,
1634 ) -> Self {
1635 let buffer = cx.new(|cx| Buffer::local("", cx));
1636 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1637 Self::new(
1638 EditorMode::AutoHeight {
1639 min_lines,
1640 max_lines,
1641 },
1642 buffer,
1643 None,
1644 window,
1645 cx,
1646 )
1647 }
1648
1649 pub fn for_buffer(
1650 buffer: Entity<Buffer>,
1651 project: Option<Entity<Project>>,
1652 window: &mut Window,
1653 cx: &mut Context<Self>,
1654 ) -> Self {
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(EditorMode::full(), buffer, project, window, cx)
1657 }
1658
1659 pub fn for_multibuffer(
1660 buffer: Entity<MultiBuffer>,
1661 project: Option<Entity<Project>>,
1662 window: &mut Window,
1663 cx: &mut Context<Self>,
1664 ) -> Self {
1665 Self::new(EditorMode::full(), buffer, project, window, cx)
1666 }
1667
1668 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let mut clone = Self::new(
1670 self.mode.clone(),
1671 self.buffer.clone(),
1672 self.project.clone(),
1673 window,
1674 cx,
1675 );
1676 self.display_map.update(cx, |display_map, cx| {
1677 let snapshot = display_map.snapshot(cx);
1678 clone.display_map.update(cx, |display_map, cx| {
1679 display_map.set_state(&snapshot, cx);
1680 });
1681 });
1682 clone.folds_did_change(cx);
1683 clone.selections.clone_state(&self.selections);
1684 clone.scroll_manager.clone_state(&self.scroll_manager);
1685 clone.searchable = self.searchable;
1686 clone.read_only = self.read_only;
1687 clone
1688 }
1689
1690 pub fn new(
1691 mode: EditorMode,
1692 buffer: Entity<MultiBuffer>,
1693 project: Option<Entity<Project>>,
1694 window: &mut Window,
1695 cx: &mut Context<Self>,
1696 ) -> Self {
1697 Editor::new_internal(mode, buffer, project, None, window, cx)
1698 }
1699
1700 fn new_internal(
1701 mode: EditorMode,
1702 buffer: Entity<MultiBuffer>,
1703 project: Option<Entity<Project>>,
1704 display_map: Option<Entity<DisplayMap>>,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 debug_assert!(
1709 display_map.is_none() || mode.is_minimap(),
1710 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1711 );
1712
1713 let full_mode = mode.is_full();
1714 let diagnostics_max_severity = if full_mode {
1715 EditorSettings::get_global(cx)
1716 .diagnostics_max_severity
1717 .unwrap_or(DiagnosticSeverity::Hint)
1718 } else {
1719 DiagnosticSeverity::Off
1720 };
1721 let style = window.text_style();
1722 let font_size = style.font_size.to_pixels(window.rem_size());
1723 let editor = cx.entity().downgrade();
1724 let fold_placeholder = FoldPlaceholder {
1725 constrain_width: true,
1726 render: Arc::new(move |fold_id, fold_range, cx| {
1727 let editor = editor.clone();
1728 div()
1729 .id(fold_id)
1730 .bg(cx.theme().colors().ghost_element_background)
1731 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1732 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1733 .rounded_xs()
1734 .size_full()
1735 .cursor_pointer()
1736 .child("⋯")
1737 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1738 .on_click(move |_, _window, cx| {
1739 editor
1740 .update(cx, |editor, cx| {
1741 editor.unfold_ranges(
1742 &[fold_range.start..fold_range.end],
1743 true,
1744 false,
1745 cx,
1746 );
1747 cx.stop_propagation();
1748 })
1749 .ok();
1750 })
1751 .into_any()
1752 }),
1753 merge_adjacent: true,
1754 ..FoldPlaceholder::default()
1755 };
1756 let display_map = display_map.unwrap_or_else(|| {
1757 cx.new(|cx| {
1758 DisplayMap::new(
1759 buffer.clone(),
1760 style.font(),
1761 font_size,
1762 None,
1763 FILE_HEADER_HEIGHT,
1764 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1765 fold_placeholder,
1766 diagnostics_max_severity,
1767 cx,
1768 )
1769 })
1770 });
1771
1772 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1773
1774 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1775
1776 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1777 .then(|| language_settings::SoftWrap::None);
1778
1779 let mut project_subscriptions = Vec::new();
1780 if mode.is_full() {
1781 if let Some(project) = project.as_ref() {
1782 project_subscriptions.push(cx.subscribe_in(
1783 project,
1784 window,
1785 |editor, _, event, window, cx| match event {
1786 project::Event::RefreshCodeLens => {
1787 // we always query lens with actions, without storing them, always refreshing them
1788 }
1789 project::Event::RefreshInlayHints => {
1790 editor
1791 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1792 }
1793 project::Event::LanguageServerAdded(..)
1794 | project::Event::LanguageServerRemoved(..) => {
1795 if editor.tasks_update_task.is_none() {
1796 editor.tasks_update_task =
1797 Some(editor.refresh_runnables(window, cx));
1798 }
1799 editor.pull_diagnostics(None, window, cx);
1800 }
1801 project::Event::SnippetEdit(id, snippet_edits) => {
1802 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1803 let focus_handle = editor.focus_handle(cx);
1804 if focus_handle.is_focused(window) {
1805 let snapshot = buffer.read(cx).snapshot();
1806 for (range, snippet) in snippet_edits {
1807 let editor_range =
1808 language::range_from_lsp(*range).to_offset(&snapshot);
1809 editor
1810 .insert_snippet(
1811 &[editor_range],
1812 snippet.clone(),
1813 window,
1814 cx,
1815 )
1816 .ok();
1817 }
1818 }
1819 }
1820 }
1821 _ => {}
1822 },
1823 ));
1824 if let Some(task_inventory) = project
1825 .read(cx)
1826 .task_store()
1827 .read(cx)
1828 .task_inventory()
1829 .cloned()
1830 {
1831 project_subscriptions.push(cx.observe_in(
1832 &task_inventory,
1833 window,
1834 |editor, _, window, cx| {
1835 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1836 },
1837 ));
1838 };
1839
1840 project_subscriptions.push(cx.subscribe_in(
1841 &project.read(cx).breakpoint_store(),
1842 window,
1843 |editor, _, event, window, cx| match event {
1844 BreakpointStoreEvent::ClearDebugLines => {
1845 editor.clear_row_highlights::<ActiveDebugLine>();
1846 editor.refresh_inline_values(cx);
1847 }
1848 BreakpointStoreEvent::SetDebugLine => {
1849 if editor.go_to_active_debug_line(window, cx) {
1850 cx.stop_propagation();
1851 }
1852
1853 editor.refresh_inline_values(cx);
1854 }
1855 _ => {}
1856 },
1857 ));
1858 }
1859 }
1860
1861 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1862
1863 let inlay_hint_settings =
1864 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1865 let focus_handle = cx.focus_handle();
1866 cx.on_focus(&focus_handle, window, Self::handle_focus)
1867 .detach();
1868 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1869 .detach();
1870 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1871 .detach();
1872 cx.on_blur(&focus_handle, window, Self::handle_blur)
1873 .detach();
1874 cx.observe_pending_input(window, Self::observe_pending_input)
1875 .detach();
1876
1877 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1878 Some(false)
1879 } else {
1880 None
1881 };
1882
1883 let breakpoint_store = match (&mode, project.as_ref()) {
1884 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1885 _ => None,
1886 };
1887
1888 let mut code_action_providers = Vec::new();
1889 let mut load_uncommitted_diff = None;
1890 if let Some(project) = project.clone() {
1891 load_uncommitted_diff = Some(
1892 update_uncommitted_diff_for_buffer(
1893 cx.entity(),
1894 &project,
1895 buffer.read(cx).all_buffers(),
1896 buffer.clone(),
1897 cx,
1898 )
1899 .shared(),
1900 );
1901 code_action_providers.push(Rc::new(project) as Rc<_>);
1902 }
1903
1904 let mut editor = Self {
1905 focus_handle,
1906 show_cursor_when_unfocused: false,
1907 last_focused_descendant: None,
1908 buffer: buffer.clone(),
1909 display_map: display_map.clone(),
1910 selections,
1911 scroll_manager: ScrollManager::new(cx),
1912 columnar_selection_state: None,
1913 add_selections_state: None,
1914 select_next_state: None,
1915 select_prev_state: None,
1916 selection_history: SelectionHistory::default(),
1917 defer_selection_effects: false,
1918 deferred_selection_effects_state: None,
1919 autoclose_regions: Vec::new(),
1920 snippet_stack: InvalidationStack::default(),
1921 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1922 ime_transaction: None,
1923 active_diagnostics: ActiveDiagnostic::None,
1924 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1925 inline_diagnostics_update: Task::ready(()),
1926 inline_diagnostics: Vec::new(),
1927 soft_wrap_mode_override,
1928 diagnostics_max_severity,
1929 hard_wrap: None,
1930 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1931 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1932 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1933 project,
1934 blink_manager: blink_manager.clone(),
1935 show_local_selections: true,
1936 show_scrollbars: ScrollbarAxes {
1937 horizontal: full_mode,
1938 vertical: full_mode,
1939 },
1940 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1941 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1942 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1943 show_gutter: mode.is_full(),
1944 show_line_numbers: None,
1945 use_relative_line_numbers: None,
1946 disable_expand_excerpt_buttons: false,
1947 show_git_diff_gutter: None,
1948 show_code_actions: None,
1949 show_runnables: None,
1950 show_breakpoints: None,
1951 show_wrap_guides: None,
1952 show_indent_guides,
1953 placeholder_text: None,
1954 highlight_order: 0,
1955 highlighted_rows: HashMap::default(),
1956 background_highlights: TreeMap::default(),
1957 gutter_highlights: TreeMap::default(),
1958 scrollbar_marker_state: ScrollbarMarkerState::default(),
1959 active_indent_guides_state: ActiveIndentGuidesState::default(),
1960 nav_history: None,
1961 context_menu: RefCell::new(None),
1962 context_menu_options: None,
1963 mouse_context_menu: None,
1964 completion_tasks: Vec::new(),
1965 inline_blame_popover: None,
1966 inline_blame_popover_show_task: None,
1967 signature_help_state: SignatureHelpState::default(),
1968 auto_signature_help: None,
1969 find_all_references_task_sources: Vec::new(),
1970 next_completion_id: 0,
1971 next_inlay_id: 0,
1972 code_action_providers,
1973 available_code_actions: None,
1974 code_actions_task: None,
1975 quick_selection_highlight_task: None,
1976 debounced_selection_highlight_task: None,
1977 document_highlights_task: None,
1978 linked_editing_range_task: None,
1979 pending_rename: None,
1980 searchable: true,
1981 cursor_shape: EditorSettings::get_global(cx)
1982 .cursor_shape
1983 .unwrap_or_default(),
1984 current_line_highlight: None,
1985 autoindent_mode: Some(AutoindentMode::EachLine),
1986 collapse_matches: false,
1987 workspace: None,
1988 input_enabled: true,
1989 use_modal_editing: mode.is_full(),
1990 read_only: mode.is_minimap(),
1991 use_autoclose: true,
1992 use_auto_surround: true,
1993 auto_replace_emoji_shortcode: false,
1994 jsx_tag_auto_close_enabled_in_any_buffer: false,
1995 leader_id: None,
1996 remote_id: None,
1997 hover_state: HoverState::default(),
1998 pending_mouse_down: None,
1999 hovered_link_state: None,
2000 edit_prediction_provider: None,
2001 active_inline_completion: None,
2002 stale_inline_completion_in_menu: None,
2003 edit_prediction_preview: EditPredictionPreview::Inactive {
2004 released_too_fast: false,
2005 },
2006 inline_diagnostics_enabled: mode.is_full(),
2007 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2008 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2009
2010 gutter_hovered: false,
2011 pixel_position_of_newest_cursor: None,
2012 last_bounds: None,
2013 last_position_map: None,
2014 expect_bounds_change: None,
2015 gutter_dimensions: GutterDimensions::default(),
2016 style: None,
2017 show_cursor_names: false,
2018 hovered_cursors: HashMap::default(),
2019 next_editor_action_id: EditorActionId::default(),
2020 editor_actions: Rc::default(),
2021 inline_completions_hidden_for_vim_mode: false,
2022 show_inline_completions_override: None,
2023 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2024 edit_prediction_settings: EditPredictionSettings::Disabled,
2025 edit_prediction_indent_conflict: false,
2026 edit_prediction_requires_modifier_in_indent_conflict: true,
2027 custom_context_menu: None,
2028 show_git_blame_gutter: false,
2029 show_git_blame_inline: false,
2030 show_selection_menu: None,
2031 show_git_blame_inline_delay_task: None,
2032 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2033 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2034 serialize_dirty_buffers: !mode.is_minimap()
2035 && ProjectSettings::get_global(cx)
2036 .session
2037 .restore_unsaved_buffers,
2038 blame: None,
2039 blame_subscription: None,
2040 tasks: BTreeMap::default(),
2041
2042 breakpoint_store,
2043 gutter_breakpoint_indicator: (None, None),
2044 hovered_diff_hunk_row: None,
2045 _subscriptions: vec![
2046 cx.observe(&buffer, Self::on_buffer_changed),
2047 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2048 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2049 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2050 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2051 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2052 cx.observe_window_activation(window, |editor, window, cx| {
2053 let active = window.is_window_active();
2054 editor.blink_manager.update(cx, |blink_manager, cx| {
2055 if active {
2056 blink_manager.enable(cx);
2057 } else {
2058 blink_manager.disable(cx);
2059 }
2060 });
2061 if active {
2062 editor.show_mouse_cursor();
2063 }
2064 }),
2065 ],
2066 tasks_update_task: None,
2067 pull_diagnostics_task: Task::ready(()),
2068 linked_edit_ranges: Default::default(),
2069 in_project_search: false,
2070 previous_search_ranges: None,
2071 breadcrumb_header: None,
2072 focused_block: None,
2073 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2074 addons: HashMap::default(),
2075 registered_buffers: HashMap::default(),
2076 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2077 selection_mark_mode: false,
2078 toggle_fold_multiple_buffers: Task::ready(()),
2079 serialize_selections: Task::ready(()),
2080 serialize_folds: Task::ready(()),
2081 text_style_refinement: None,
2082 load_diff_task: load_uncommitted_diff,
2083 temporary_diff_override: false,
2084 mouse_cursor_hidden: false,
2085 minimap: None,
2086 hide_mouse_mode: EditorSettings::get_global(cx)
2087 .hide_mouse
2088 .unwrap_or_default(),
2089 change_list: ChangeList::new(),
2090 mode,
2091 selection_drag_state: SelectionDragState::None,
2092 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2093 };
2094 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2095 editor
2096 ._subscriptions
2097 .push(cx.observe(breakpoints, |_, _, cx| {
2098 cx.notify();
2099 }));
2100 }
2101 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2102 editor._subscriptions.extend(project_subscriptions);
2103
2104 editor._subscriptions.push(cx.subscribe_in(
2105 &cx.entity(),
2106 window,
2107 |editor, _, e: &EditorEvent, window, cx| match e {
2108 EditorEvent::ScrollPositionChanged { local, .. } => {
2109 if *local {
2110 let new_anchor = editor.scroll_manager.anchor();
2111 let snapshot = editor.snapshot(window, cx);
2112 editor.update_restoration_data(cx, move |data| {
2113 data.scroll_position = (
2114 new_anchor.top_row(&snapshot.buffer_snapshot),
2115 new_anchor.offset,
2116 );
2117 });
2118 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2119 editor.inline_blame_popover.take();
2120 }
2121 }
2122 EditorEvent::Edited { .. } => {
2123 if !vim_enabled(cx) {
2124 let (map, selections) = editor.selections.all_adjusted_display(cx);
2125 let pop_state = editor
2126 .change_list
2127 .last()
2128 .map(|previous| {
2129 previous.len() == selections.len()
2130 && previous.iter().enumerate().all(|(ix, p)| {
2131 p.to_display_point(&map).row()
2132 == selections[ix].head().row()
2133 })
2134 })
2135 .unwrap_or(false);
2136 let new_positions = selections
2137 .into_iter()
2138 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2139 .collect();
2140 editor
2141 .change_list
2142 .push_to_change_list(pop_state, new_positions);
2143 }
2144 }
2145 _ => (),
2146 },
2147 ));
2148
2149 if let Some(dap_store) = editor
2150 .project
2151 .as_ref()
2152 .map(|project| project.read(cx).dap_store())
2153 {
2154 let weak_editor = cx.weak_entity();
2155
2156 editor
2157 ._subscriptions
2158 .push(
2159 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2160 let session_entity = cx.entity();
2161 weak_editor
2162 .update(cx, |editor, cx| {
2163 editor._subscriptions.push(
2164 cx.subscribe(&session_entity, Self::on_debug_session_event),
2165 );
2166 })
2167 .ok();
2168 }),
2169 );
2170
2171 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2172 editor
2173 ._subscriptions
2174 .push(cx.subscribe(&session, Self::on_debug_session_event));
2175 }
2176 }
2177
2178 // skip adding the initial selection to selection history
2179 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2180 editor.end_selection(window, cx);
2181 editor.selection_history.mode = SelectionHistoryMode::Normal;
2182
2183 editor.scroll_manager.show_scrollbars(window, cx);
2184 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2185
2186 if full_mode {
2187 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2188 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2189
2190 if editor.git_blame_inline_enabled {
2191 editor.start_git_blame_inline(false, window, cx);
2192 }
2193
2194 editor.go_to_active_debug_line(window, cx);
2195
2196 if let Some(buffer) = buffer.read(cx).as_singleton() {
2197 if let Some(project) = editor.project.as_ref() {
2198 let handle = project.update(cx, |project, cx| {
2199 project.register_buffer_with_language_servers(&buffer, cx)
2200 });
2201 editor
2202 .registered_buffers
2203 .insert(buffer.read(cx).remote_id(), handle);
2204 }
2205 }
2206
2207 editor.minimap =
2208 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2209 editor.pull_diagnostics(None, window, cx);
2210 }
2211
2212 editor.report_editor_event("Editor Opened", None, cx);
2213 editor
2214 }
2215
2216 pub fn deploy_mouse_context_menu(
2217 &mut self,
2218 position: gpui::Point<Pixels>,
2219 context_menu: Entity<ContextMenu>,
2220 window: &mut Window,
2221 cx: &mut Context<Self>,
2222 ) {
2223 self.mouse_context_menu = Some(MouseContextMenu::new(
2224 self,
2225 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2226 context_menu,
2227 window,
2228 cx,
2229 ));
2230 }
2231
2232 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2233 self.mouse_context_menu
2234 .as_ref()
2235 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2236 }
2237
2238 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2239 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2240 }
2241
2242 fn key_context_internal(
2243 &self,
2244 has_active_edit_prediction: bool,
2245 window: &Window,
2246 cx: &App,
2247 ) -> KeyContext {
2248 let mut key_context = KeyContext::new_with_defaults();
2249 key_context.add("Editor");
2250 let mode = match self.mode {
2251 EditorMode::SingleLine { .. } => "single_line",
2252 EditorMode::AutoHeight { .. } => "auto_height",
2253 EditorMode::Minimap { .. } => "minimap",
2254 EditorMode::Full { .. } => "full",
2255 };
2256
2257 if EditorSettings::jupyter_enabled(cx) {
2258 key_context.add("jupyter");
2259 }
2260
2261 key_context.set("mode", mode);
2262 if self.pending_rename.is_some() {
2263 key_context.add("renaming");
2264 }
2265
2266 match self.context_menu.borrow().as_ref() {
2267 Some(CodeContextMenu::Completions(_)) => {
2268 key_context.add("menu");
2269 key_context.add("showing_completions");
2270 }
2271 Some(CodeContextMenu::CodeActions(_)) => {
2272 key_context.add("menu");
2273 key_context.add("showing_code_actions")
2274 }
2275 None => {}
2276 }
2277
2278 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2279 if !self.focus_handle(cx).contains_focused(window, cx)
2280 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2281 {
2282 for addon in self.addons.values() {
2283 addon.extend_key_context(&mut key_context, cx)
2284 }
2285 }
2286
2287 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2288 if let Some(extension) = singleton_buffer
2289 .read(cx)
2290 .file()
2291 .and_then(|file| file.path().extension()?.to_str())
2292 {
2293 key_context.set("extension", extension.to_string());
2294 }
2295 } else {
2296 key_context.add("multibuffer");
2297 }
2298
2299 if has_active_edit_prediction {
2300 if self.edit_prediction_in_conflict() {
2301 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2302 } else {
2303 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2304 key_context.add("copilot_suggestion");
2305 }
2306 }
2307
2308 if self.selection_mark_mode {
2309 key_context.add("selection_mode");
2310 }
2311
2312 key_context
2313 }
2314
2315 fn show_mouse_cursor(&mut self) {
2316 self.mouse_cursor_hidden = false;
2317 }
2318
2319 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2320 self.mouse_cursor_hidden = match origin {
2321 HideMouseCursorOrigin::TypingAction => {
2322 matches!(
2323 self.hide_mouse_mode,
2324 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2325 )
2326 }
2327 HideMouseCursorOrigin::MovementAction => {
2328 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2329 }
2330 };
2331 }
2332
2333 pub fn edit_prediction_in_conflict(&self) -> bool {
2334 if !self.show_edit_predictions_in_menu() {
2335 return false;
2336 }
2337
2338 let showing_completions = self
2339 .context_menu
2340 .borrow()
2341 .as_ref()
2342 .map_or(false, |context| {
2343 matches!(context, CodeContextMenu::Completions(_))
2344 });
2345
2346 showing_completions
2347 || self.edit_prediction_requires_modifier()
2348 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2349 // bindings to insert tab characters.
2350 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2351 }
2352
2353 pub fn accept_edit_prediction_keybind(
2354 &self,
2355 accept_partial: bool,
2356 window: &Window,
2357 cx: &App,
2358 ) -> AcceptEditPredictionBinding {
2359 let key_context = self.key_context_internal(true, window, cx);
2360 let in_conflict = self.edit_prediction_in_conflict();
2361
2362 let bindings = if accept_partial {
2363 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2364 } else {
2365 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2366 };
2367
2368 // TODO: if the binding contains multiple keystrokes, display all of them, not
2369 // just the first one.
2370 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2371 !in_conflict
2372 || binding
2373 .keystrokes()
2374 .first()
2375 .map_or(false, |keystroke| keystroke.modifiers.modified())
2376 }))
2377 }
2378
2379 pub fn new_file(
2380 workspace: &mut Workspace,
2381 _: &workspace::NewFile,
2382 window: &mut Window,
2383 cx: &mut Context<Workspace>,
2384 ) {
2385 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2386 "Failed to create buffer",
2387 window,
2388 cx,
2389 |e, _, _| match e.error_code() {
2390 ErrorCode::RemoteUpgradeRequired => Some(format!(
2391 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2392 e.error_tag("required").unwrap_or("the latest version")
2393 )),
2394 _ => None,
2395 },
2396 );
2397 }
2398
2399 pub fn new_in_workspace(
2400 workspace: &mut Workspace,
2401 window: &mut Window,
2402 cx: &mut Context<Workspace>,
2403 ) -> Task<Result<Entity<Editor>>> {
2404 let project = workspace.project().clone();
2405 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2406
2407 cx.spawn_in(window, async move |workspace, cx| {
2408 let buffer = create.await?;
2409 workspace.update_in(cx, |workspace, window, cx| {
2410 let editor =
2411 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2412 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2413 editor
2414 })
2415 })
2416 }
2417
2418 fn new_file_vertical(
2419 workspace: &mut Workspace,
2420 _: &workspace::NewFileSplitVertical,
2421 window: &mut Window,
2422 cx: &mut Context<Workspace>,
2423 ) {
2424 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2425 }
2426
2427 fn new_file_horizontal(
2428 workspace: &mut Workspace,
2429 _: &workspace::NewFileSplitHorizontal,
2430 window: &mut Window,
2431 cx: &mut Context<Workspace>,
2432 ) {
2433 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2434 }
2435
2436 fn new_file_in_direction(
2437 workspace: &mut Workspace,
2438 direction: SplitDirection,
2439 window: &mut Window,
2440 cx: &mut Context<Workspace>,
2441 ) {
2442 let project = workspace.project().clone();
2443 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2444
2445 cx.spawn_in(window, async move |workspace, cx| {
2446 let buffer = create.await?;
2447 workspace.update_in(cx, move |workspace, window, cx| {
2448 workspace.split_item(
2449 direction,
2450 Box::new(
2451 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2452 ),
2453 window,
2454 cx,
2455 )
2456 })?;
2457 anyhow::Ok(())
2458 })
2459 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2460 match e.error_code() {
2461 ErrorCode::RemoteUpgradeRequired => Some(format!(
2462 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2463 e.error_tag("required").unwrap_or("the latest version")
2464 )),
2465 _ => None,
2466 }
2467 });
2468 }
2469
2470 pub fn leader_id(&self) -> Option<CollaboratorId> {
2471 self.leader_id
2472 }
2473
2474 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2475 &self.buffer
2476 }
2477
2478 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2479 self.workspace.as_ref()?.0.upgrade()
2480 }
2481
2482 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2483 self.buffer().read(cx).title(cx)
2484 }
2485
2486 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2487 let git_blame_gutter_max_author_length = self
2488 .render_git_blame_gutter(cx)
2489 .then(|| {
2490 if let Some(blame) = self.blame.as_ref() {
2491 let max_author_length =
2492 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2493 Some(max_author_length)
2494 } else {
2495 None
2496 }
2497 })
2498 .flatten();
2499
2500 EditorSnapshot {
2501 mode: self.mode.clone(),
2502 show_gutter: self.show_gutter,
2503 show_line_numbers: self.show_line_numbers,
2504 show_git_diff_gutter: self.show_git_diff_gutter,
2505 show_code_actions: self.show_code_actions,
2506 show_runnables: self.show_runnables,
2507 show_breakpoints: self.show_breakpoints,
2508 git_blame_gutter_max_author_length,
2509 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2510 scroll_anchor: self.scroll_manager.anchor(),
2511 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2512 placeholder_text: self.placeholder_text.clone(),
2513 is_focused: self.focus_handle.is_focused(window),
2514 current_line_highlight: self
2515 .current_line_highlight
2516 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2517 gutter_hovered: self.gutter_hovered,
2518 }
2519 }
2520
2521 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2522 self.buffer.read(cx).language_at(point, cx)
2523 }
2524
2525 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2526 self.buffer.read(cx).read(cx).file_at(point).cloned()
2527 }
2528
2529 pub fn active_excerpt(
2530 &self,
2531 cx: &App,
2532 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2533 self.buffer
2534 .read(cx)
2535 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2536 }
2537
2538 pub fn mode(&self) -> &EditorMode {
2539 &self.mode
2540 }
2541
2542 pub fn set_mode(&mut self, mode: EditorMode) {
2543 self.mode = mode;
2544 }
2545
2546 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2547 self.collaboration_hub.as_deref()
2548 }
2549
2550 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2551 self.collaboration_hub = Some(hub);
2552 }
2553
2554 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2555 self.in_project_search = in_project_search;
2556 }
2557
2558 pub fn set_custom_context_menu(
2559 &mut self,
2560 f: impl 'static
2561 + Fn(
2562 &mut Self,
2563 DisplayPoint,
2564 &mut Window,
2565 &mut Context<Self>,
2566 ) -> Option<Entity<ui::ContextMenu>>,
2567 ) {
2568 self.custom_context_menu = Some(Box::new(f))
2569 }
2570
2571 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2572 self.completion_provider = provider;
2573 }
2574
2575 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2576 self.semantics_provider.clone()
2577 }
2578
2579 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2580 self.semantics_provider = provider;
2581 }
2582
2583 pub fn set_edit_prediction_provider<T>(
2584 &mut self,
2585 provider: Option<Entity<T>>,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) where
2589 T: EditPredictionProvider,
2590 {
2591 self.edit_prediction_provider =
2592 provider.map(|provider| RegisteredInlineCompletionProvider {
2593 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2594 if this.focus_handle.is_focused(window) {
2595 this.update_visible_inline_completion(window, cx);
2596 }
2597 }),
2598 provider: Arc::new(provider),
2599 });
2600 self.update_edit_prediction_settings(cx);
2601 self.refresh_inline_completion(false, false, window, cx);
2602 }
2603
2604 pub fn placeholder_text(&self) -> Option<&str> {
2605 self.placeholder_text.as_deref()
2606 }
2607
2608 pub fn set_placeholder_text(
2609 &mut self,
2610 placeholder_text: impl Into<Arc<str>>,
2611 cx: &mut Context<Self>,
2612 ) {
2613 let placeholder_text = Some(placeholder_text.into());
2614 if self.placeholder_text != placeholder_text {
2615 self.placeholder_text = placeholder_text;
2616 cx.notify();
2617 }
2618 }
2619
2620 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2621 self.cursor_shape = cursor_shape;
2622
2623 // Disrupt blink for immediate user feedback that the cursor shape has changed
2624 self.blink_manager.update(cx, BlinkManager::show_cursor);
2625
2626 cx.notify();
2627 }
2628
2629 pub fn set_current_line_highlight(
2630 &mut self,
2631 current_line_highlight: Option<CurrentLineHighlight>,
2632 ) {
2633 self.current_line_highlight = current_line_highlight;
2634 }
2635
2636 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2637 self.collapse_matches = collapse_matches;
2638 }
2639
2640 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2641 let buffers = self.buffer.read(cx).all_buffers();
2642 let Some(project) = self.project.as_ref() else {
2643 return;
2644 };
2645 project.update(cx, |project, cx| {
2646 for buffer in buffers {
2647 self.registered_buffers
2648 .entry(buffer.read(cx).remote_id())
2649 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2650 }
2651 })
2652 }
2653
2654 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2655 if self.collapse_matches {
2656 return range.start..range.start;
2657 }
2658 range.clone()
2659 }
2660
2661 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2662 if self.display_map.read(cx).clip_at_line_ends != clip {
2663 self.display_map
2664 .update(cx, |map, _| map.clip_at_line_ends = clip);
2665 }
2666 }
2667
2668 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2669 self.input_enabled = input_enabled;
2670 }
2671
2672 pub fn set_inline_completions_hidden_for_vim_mode(
2673 &mut self,
2674 hidden: bool,
2675 window: &mut Window,
2676 cx: &mut Context<Self>,
2677 ) {
2678 if hidden != self.inline_completions_hidden_for_vim_mode {
2679 self.inline_completions_hidden_for_vim_mode = hidden;
2680 if hidden {
2681 self.update_visible_inline_completion(window, cx);
2682 } else {
2683 self.refresh_inline_completion(true, false, window, cx);
2684 }
2685 }
2686 }
2687
2688 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2689 self.menu_inline_completions_policy = value;
2690 }
2691
2692 pub fn set_autoindent(&mut self, autoindent: bool) {
2693 if autoindent {
2694 self.autoindent_mode = Some(AutoindentMode::EachLine);
2695 } else {
2696 self.autoindent_mode = None;
2697 }
2698 }
2699
2700 pub fn read_only(&self, cx: &App) -> bool {
2701 self.read_only || self.buffer.read(cx).read_only()
2702 }
2703
2704 pub fn set_read_only(&mut self, read_only: bool) {
2705 self.read_only = read_only;
2706 }
2707
2708 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2709 self.use_autoclose = autoclose;
2710 }
2711
2712 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2713 self.use_auto_surround = auto_surround;
2714 }
2715
2716 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2717 self.auto_replace_emoji_shortcode = auto_replace;
2718 }
2719
2720 pub fn toggle_edit_predictions(
2721 &mut self,
2722 _: &ToggleEditPrediction,
2723 window: &mut Window,
2724 cx: &mut Context<Self>,
2725 ) {
2726 if self.show_inline_completions_override.is_some() {
2727 self.set_show_edit_predictions(None, window, cx);
2728 } else {
2729 let show_edit_predictions = !self.edit_predictions_enabled();
2730 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2731 }
2732 }
2733
2734 pub fn set_show_edit_predictions(
2735 &mut self,
2736 show_edit_predictions: Option<bool>,
2737 window: &mut Window,
2738 cx: &mut Context<Self>,
2739 ) {
2740 self.show_inline_completions_override = show_edit_predictions;
2741 self.update_edit_prediction_settings(cx);
2742
2743 if let Some(false) = show_edit_predictions {
2744 self.discard_inline_completion(false, cx);
2745 } else {
2746 self.refresh_inline_completion(false, true, window, cx);
2747 }
2748 }
2749
2750 fn inline_completions_disabled_in_scope(
2751 &self,
2752 buffer: &Entity<Buffer>,
2753 buffer_position: language::Anchor,
2754 cx: &App,
2755 ) -> bool {
2756 let snapshot = buffer.read(cx).snapshot();
2757 let settings = snapshot.settings_at(buffer_position, cx);
2758
2759 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2760 return false;
2761 };
2762
2763 scope.override_name().map_or(false, |scope_name| {
2764 settings
2765 .edit_predictions_disabled_in
2766 .iter()
2767 .any(|s| s == scope_name)
2768 })
2769 }
2770
2771 pub fn set_use_modal_editing(&mut self, to: bool) {
2772 self.use_modal_editing = to;
2773 }
2774
2775 pub fn use_modal_editing(&self) -> bool {
2776 self.use_modal_editing
2777 }
2778
2779 fn selections_did_change(
2780 &mut self,
2781 local: bool,
2782 old_cursor_position: &Anchor,
2783 effects: SelectionEffects,
2784 window: &mut Window,
2785 cx: &mut Context<Self>,
2786 ) {
2787 window.invalidate_character_coordinates();
2788
2789 // Copy selections to primary selection buffer
2790 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2791 if local {
2792 let selections = self.selections.all::<usize>(cx);
2793 let buffer_handle = self.buffer.read(cx).read(cx);
2794
2795 let mut text = String::new();
2796 for (index, selection) in selections.iter().enumerate() {
2797 let text_for_selection = buffer_handle
2798 .text_for_range(selection.start..selection.end)
2799 .collect::<String>();
2800
2801 text.push_str(&text_for_selection);
2802 if index != selections.len() - 1 {
2803 text.push('\n');
2804 }
2805 }
2806
2807 if !text.is_empty() {
2808 cx.write_to_primary(ClipboardItem::new_string(text));
2809 }
2810 }
2811
2812 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2813 self.buffer.update(cx, |buffer, cx| {
2814 buffer.set_active_selections(
2815 &self.selections.disjoint_anchors(),
2816 self.selections.line_mode,
2817 self.cursor_shape,
2818 cx,
2819 )
2820 });
2821 }
2822 let display_map = self
2823 .display_map
2824 .update(cx, |display_map, cx| display_map.snapshot(cx));
2825 let buffer = &display_map.buffer_snapshot;
2826 if self.selections.count() == 1 {
2827 self.add_selections_state = None;
2828 }
2829 self.select_next_state = None;
2830 self.select_prev_state = None;
2831 self.select_syntax_node_history.try_clear();
2832 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2833 self.snippet_stack
2834 .invalidate(&self.selections.disjoint_anchors(), buffer);
2835 self.take_rename(false, window, cx);
2836
2837 let newest_selection = self.selections.newest_anchor();
2838 let new_cursor_position = newest_selection.head();
2839 let selection_start = newest_selection.start;
2840
2841 if effects.nav_history {
2842 self.push_to_nav_history(
2843 *old_cursor_position,
2844 Some(new_cursor_position.to_point(buffer)),
2845 false,
2846 cx,
2847 );
2848 }
2849
2850 if local {
2851 if let Some(buffer_id) = new_cursor_position.buffer_id {
2852 if !self.registered_buffers.contains_key(&buffer_id) {
2853 if let Some(project) = self.project.as_ref() {
2854 project.update(cx, |project, cx| {
2855 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2856 return;
2857 };
2858 self.registered_buffers.insert(
2859 buffer_id,
2860 project.register_buffer_with_language_servers(&buffer, cx),
2861 );
2862 })
2863 }
2864 }
2865 }
2866
2867 let mut context_menu = self.context_menu.borrow_mut();
2868 let completion_menu = match context_menu.as_ref() {
2869 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2870 Some(CodeContextMenu::CodeActions(_)) => {
2871 *context_menu = None;
2872 None
2873 }
2874 None => None,
2875 };
2876 let completion_position = completion_menu.map(|menu| menu.initial_position);
2877 drop(context_menu);
2878
2879 if effects.completions {
2880 if let Some(completion_position) = completion_position {
2881 let start_offset = selection_start.to_offset(buffer);
2882 let position_matches = start_offset == completion_position.to_offset(buffer);
2883 let continue_showing = if position_matches {
2884 if self.snippet_stack.is_empty() {
2885 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2886 } else {
2887 // Snippet choices can be shown even when the cursor is in whitespace.
2888 // Dismissing the menu with actions like backspace is handled by
2889 // invalidation regions.
2890 true
2891 }
2892 } else {
2893 false
2894 };
2895
2896 if continue_showing {
2897 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2898 } else {
2899 self.hide_context_menu(window, cx);
2900 }
2901 }
2902 }
2903
2904 hide_hover(self, cx);
2905
2906 if old_cursor_position.to_display_point(&display_map).row()
2907 != new_cursor_position.to_display_point(&display_map).row()
2908 {
2909 self.available_code_actions.take();
2910 }
2911 self.refresh_code_actions(window, cx);
2912 self.refresh_document_highlights(cx);
2913 self.refresh_selected_text_highlights(false, window, cx);
2914 refresh_matching_bracket_highlights(self, window, cx);
2915 self.update_visible_inline_completion(window, cx);
2916 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2917 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2918 self.inline_blame_popover.take();
2919 if self.git_blame_inline_enabled {
2920 self.start_inline_blame_timer(window, cx);
2921 }
2922 }
2923
2924 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2925 cx.emit(EditorEvent::SelectionsChanged { local });
2926
2927 let selections = &self.selections.disjoint;
2928 if selections.len() == 1 {
2929 cx.emit(SearchEvent::ActiveMatchChanged)
2930 }
2931 if local {
2932 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2933 let inmemory_selections = selections
2934 .iter()
2935 .map(|s| {
2936 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2937 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2938 })
2939 .collect();
2940 self.update_restoration_data(cx, |data| {
2941 data.selections = inmemory_selections;
2942 });
2943
2944 if WorkspaceSettings::get(None, cx).restore_on_startup
2945 != RestoreOnStartupBehavior::None
2946 {
2947 if let Some(workspace_id) =
2948 self.workspace.as_ref().and_then(|workspace| workspace.1)
2949 {
2950 let snapshot = self.buffer().read(cx).snapshot(cx);
2951 let selections = selections.clone();
2952 let background_executor = cx.background_executor().clone();
2953 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2954 self.serialize_selections = cx.background_spawn(async move {
2955 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2956 let db_selections = selections
2957 .iter()
2958 .map(|selection| {
2959 (
2960 selection.start.to_offset(&snapshot),
2961 selection.end.to_offset(&snapshot),
2962 )
2963 })
2964 .collect();
2965
2966 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2967 .await
2968 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2969 .log_err();
2970 });
2971 }
2972 }
2973 }
2974 }
2975
2976 cx.notify();
2977 }
2978
2979 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2980 use text::ToOffset as _;
2981 use text::ToPoint as _;
2982
2983 if self.mode.is_minimap()
2984 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2985 {
2986 return;
2987 }
2988
2989 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2990 return;
2991 };
2992
2993 let snapshot = singleton.read(cx).snapshot();
2994 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2995 let display_snapshot = display_map.snapshot(cx);
2996
2997 display_snapshot
2998 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2999 .map(|fold| {
3000 fold.range.start.text_anchor.to_point(&snapshot)
3001 ..fold.range.end.text_anchor.to_point(&snapshot)
3002 })
3003 .collect()
3004 });
3005 self.update_restoration_data(cx, |data| {
3006 data.folds = inmemory_folds;
3007 });
3008
3009 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3010 return;
3011 };
3012 let background_executor = cx.background_executor().clone();
3013 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3014 let db_folds = self.display_map.update(cx, |display_map, cx| {
3015 display_map
3016 .snapshot(cx)
3017 .folds_in_range(0..snapshot.len())
3018 .map(|fold| {
3019 (
3020 fold.range.start.text_anchor.to_offset(&snapshot),
3021 fold.range.end.text_anchor.to_offset(&snapshot),
3022 )
3023 })
3024 .collect()
3025 });
3026 self.serialize_folds = cx.background_spawn(async move {
3027 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3028 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3029 .await
3030 .with_context(|| {
3031 format!(
3032 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3033 )
3034 })
3035 .log_err();
3036 });
3037 }
3038
3039 pub fn sync_selections(
3040 &mut self,
3041 other: Entity<Editor>,
3042 cx: &mut Context<Self>,
3043 ) -> gpui::Subscription {
3044 let other_selections = other.read(cx).selections.disjoint.to_vec();
3045 self.selections.change_with(cx, |selections| {
3046 selections.select_anchors(other_selections);
3047 });
3048
3049 let other_subscription =
3050 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3051 EditorEvent::SelectionsChanged { local: true } => {
3052 let other_selections = other.read(cx).selections.disjoint.to_vec();
3053 if other_selections.is_empty() {
3054 return;
3055 }
3056 this.selections.change_with(cx, |selections| {
3057 selections.select_anchors(other_selections);
3058 });
3059 }
3060 _ => {}
3061 });
3062
3063 let this_subscription =
3064 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3065 EditorEvent::SelectionsChanged { local: true } => {
3066 let these_selections = this.selections.disjoint.to_vec();
3067 if these_selections.is_empty() {
3068 return;
3069 }
3070 other.update(cx, |other_editor, cx| {
3071 other_editor.selections.change_with(cx, |selections| {
3072 selections.select_anchors(these_selections);
3073 })
3074 });
3075 }
3076 _ => {}
3077 });
3078
3079 Subscription::join(other_subscription, this_subscription)
3080 }
3081
3082 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3083 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3084 /// effects of selection change occur at the end of the transaction.
3085 pub fn change_selections<R>(
3086 &mut self,
3087 effects: impl Into<SelectionEffects>,
3088 window: &mut Window,
3089 cx: &mut Context<Self>,
3090 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3091 ) -> R {
3092 let effects = effects.into();
3093 if let Some(state) = &mut self.deferred_selection_effects_state {
3094 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3095 state.effects.completions = effects.completions;
3096 state.effects.nav_history |= effects.nav_history;
3097 let (changed, result) = self.selections.change_with(cx, change);
3098 state.changed |= changed;
3099 return result;
3100 }
3101 let mut state = DeferredSelectionEffectsState {
3102 changed: false,
3103 effects,
3104 old_cursor_position: self.selections.newest_anchor().head(),
3105 history_entry: SelectionHistoryEntry {
3106 selections: self.selections.disjoint_anchors(),
3107 select_next_state: self.select_next_state.clone(),
3108 select_prev_state: self.select_prev_state.clone(),
3109 add_selections_state: self.add_selections_state.clone(),
3110 },
3111 };
3112 let (changed, result) = self.selections.change_with(cx, change);
3113 state.changed = state.changed || changed;
3114 if self.defer_selection_effects {
3115 self.deferred_selection_effects_state = Some(state);
3116 } else {
3117 self.apply_selection_effects(state, window, cx);
3118 }
3119 result
3120 }
3121
3122 /// Defers the effects of selection change, so that the effects of multiple calls to
3123 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3124 /// to selection history and the state of popovers based on selection position aren't
3125 /// erroneously updated.
3126 pub fn with_selection_effects_deferred<R>(
3127 &mut self,
3128 window: &mut Window,
3129 cx: &mut Context<Self>,
3130 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3131 ) -> R {
3132 let already_deferred = self.defer_selection_effects;
3133 self.defer_selection_effects = true;
3134 let result = update(self, window, cx);
3135 if !already_deferred {
3136 self.defer_selection_effects = false;
3137 if let Some(state) = self.deferred_selection_effects_state.take() {
3138 self.apply_selection_effects(state, window, cx);
3139 }
3140 }
3141 result
3142 }
3143
3144 fn apply_selection_effects(
3145 &mut self,
3146 state: DeferredSelectionEffectsState,
3147 window: &mut Window,
3148 cx: &mut Context<Self>,
3149 ) {
3150 if state.changed {
3151 self.selection_history.push(state.history_entry);
3152
3153 if let Some(autoscroll) = state.effects.scroll {
3154 self.request_autoscroll(autoscroll, cx);
3155 }
3156
3157 let old_cursor_position = &state.old_cursor_position;
3158
3159 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3160
3161 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3162 self.show_signature_help(&ShowSignatureHelp, window, cx);
3163 }
3164 }
3165 }
3166
3167 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3168 where
3169 I: IntoIterator<Item = (Range<S>, T)>,
3170 S: ToOffset,
3171 T: Into<Arc<str>>,
3172 {
3173 if self.read_only(cx) {
3174 return;
3175 }
3176
3177 self.buffer
3178 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3179 }
3180
3181 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3182 where
3183 I: IntoIterator<Item = (Range<S>, T)>,
3184 S: ToOffset,
3185 T: Into<Arc<str>>,
3186 {
3187 if self.read_only(cx) {
3188 return;
3189 }
3190
3191 self.buffer.update(cx, |buffer, cx| {
3192 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3193 });
3194 }
3195
3196 pub fn edit_with_block_indent<I, S, T>(
3197 &mut self,
3198 edits: I,
3199 original_indent_columns: Vec<Option<u32>>,
3200 cx: &mut Context<Self>,
3201 ) where
3202 I: IntoIterator<Item = (Range<S>, T)>,
3203 S: ToOffset,
3204 T: Into<Arc<str>>,
3205 {
3206 if self.read_only(cx) {
3207 return;
3208 }
3209
3210 self.buffer.update(cx, |buffer, cx| {
3211 buffer.edit(
3212 edits,
3213 Some(AutoindentMode::Block {
3214 original_indent_columns,
3215 }),
3216 cx,
3217 )
3218 });
3219 }
3220
3221 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3222 self.hide_context_menu(window, cx);
3223
3224 match phase {
3225 SelectPhase::Begin {
3226 position,
3227 add,
3228 click_count,
3229 } => self.begin_selection(position, add, click_count, window, cx),
3230 SelectPhase::BeginColumnar {
3231 position,
3232 goal_column,
3233 reset,
3234 mode,
3235 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3236 SelectPhase::Extend {
3237 position,
3238 click_count,
3239 } => self.extend_selection(position, click_count, window, cx),
3240 SelectPhase::Update {
3241 position,
3242 goal_column,
3243 scroll_delta,
3244 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3245 SelectPhase::End => self.end_selection(window, cx),
3246 }
3247 }
3248
3249 fn extend_selection(
3250 &mut self,
3251 position: DisplayPoint,
3252 click_count: usize,
3253 window: &mut Window,
3254 cx: &mut Context<Self>,
3255 ) {
3256 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3257 let tail = self.selections.newest::<usize>(cx).tail();
3258 self.begin_selection(position, false, click_count, window, cx);
3259
3260 let position = position.to_offset(&display_map, Bias::Left);
3261 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3262
3263 let mut pending_selection = self
3264 .selections
3265 .pending_anchor()
3266 .expect("extend_selection not called with pending selection");
3267 if position >= tail {
3268 pending_selection.start = tail_anchor;
3269 } else {
3270 pending_selection.end = tail_anchor;
3271 pending_selection.reversed = true;
3272 }
3273
3274 let mut pending_mode = self.selections.pending_mode().unwrap();
3275 match &mut pending_mode {
3276 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3277 _ => {}
3278 }
3279
3280 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3281 SelectionEffects::scroll(Autoscroll::fit())
3282 } else {
3283 SelectionEffects::no_scroll()
3284 };
3285
3286 self.change_selections(effects, window, cx, |s| {
3287 s.set_pending(pending_selection, pending_mode)
3288 });
3289 }
3290
3291 fn begin_selection(
3292 &mut self,
3293 position: DisplayPoint,
3294 add: bool,
3295 click_count: usize,
3296 window: &mut Window,
3297 cx: &mut Context<Self>,
3298 ) {
3299 if !self.focus_handle.is_focused(window) {
3300 self.last_focused_descendant = None;
3301 window.focus(&self.focus_handle);
3302 }
3303
3304 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3305 let buffer = &display_map.buffer_snapshot;
3306 let position = display_map.clip_point(position, Bias::Left);
3307
3308 let start;
3309 let end;
3310 let mode;
3311 let mut auto_scroll;
3312 match click_count {
3313 1 => {
3314 start = buffer.anchor_before(position.to_point(&display_map));
3315 end = start;
3316 mode = SelectMode::Character;
3317 auto_scroll = true;
3318 }
3319 2 => {
3320 let range = movement::surrounding_word(&display_map, position);
3321 start = buffer.anchor_before(range.start.to_point(&display_map));
3322 end = buffer.anchor_before(range.end.to_point(&display_map));
3323 mode = SelectMode::Word(start..end);
3324 auto_scroll = true;
3325 }
3326 3 => {
3327 let position = display_map
3328 .clip_point(position, Bias::Left)
3329 .to_point(&display_map);
3330 let line_start = display_map.prev_line_boundary(position).0;
3331 let next_line_start = buffer.clip_point(
3332 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3333 Bias::Left,
3334 );
3335 start = buffer.anchor_before(line_start);
3336 end = buffer.anchor_before(next_line_start);
3337 mode = SelectMode::Line(start..end);
3338 auto_scroll = true;
3339 }
3340 _ => {
3341 start = buffer.anchor_before(0);
3342 end = buffer.anchor_before(buffer.len());
3343 mode = SelectMode::All;
3344 auto_scroll = false;
3345 }
3346 }
3347 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3348
3349 let point_to_delete: Option<usize> = {
3350 let selected_points: Vec<Selection<Point>> =
3351 self.selections.disjoint_in_range(start..end, cx);
3352
3353 if !add || click_count > 1 {
3354 None
3355 } else if !selected_points.is_empty() {
3356 Some(selected_points[0].id)
3357 } else {
3358 let clicked_point_already_selected =
3359 self.selections.disjoint.iter().find(|selection| {
3360 selection.start.to_point(buffer) == start.to_point(buffer)
3361 || selection.end.to_point(buffer) == end.to_point(buffer)
3362 });
3363
3364 clicked_point_already_selected.map(|selection| selection.id)
3365 }
3366 };
3367
3368 let selections_count = self.selections.count();
3369
3370 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3371 if let Some(point_to_delete) = point_to_delete {
3372 s.delete(point_to_delete);
3373
3374 if selections_count == 1 {
3375 s.set_pending_anchor_range(start..end, mode);
3376 }
3377 } else {
3378 if !add {
3379 s.clear_disjoint();
3380 }
3381
3382 s.set_pending_anchor_range(start..end, mode);
3383 }
3384 });
3385 }
3386
3387 fn begin_columnar_selection(
3388 &mut self,
3389 position: DisplayPoint,
3390 goal_column: u32,
3391 reset: bool,
3392 mode: ColumnarMode,
3393 window: &mut Window,
3394 cx: &mut Context<Self>,
3395 ) {
3396 if !self.focus_handle.is_focused(window) {
3397 self.last_focused_descendant = None;
3398 window.focus(&self.focus_handle);
3399 }
3400
3401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3402
3403 if reset {
3404 let pointer_position = display_map
3405 .buffer_snapshot
3406 .anchor_before(position.to_point(&display_map));
3407
3408 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3409 s.clear_disjoint();
3410 s.set_pending_anchor_range(
3411 pointer_position..pointer_position,
3412 SelectMode::Character,
3413 );
3414 });
3415 };
3416
3417 let tail = self.selections.newest::<Point>(cx).tail();
3418 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3419 self.columnar_selection_state = match mode {
3420 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3421 selection_tail: selection_anchor,
3422 display_point: if reset {
3423 if position.column() != goal_column {
3424 Some(DisplayPoint::new(position.row(), goal_column))
3425 } else {
3426 None
3427 }
3428 } else {
3429 None
3430 },
3431 }),
3432 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3433 selection_tail: selection_anchor,
3434 }),
3435 };
3436
3437 if !reset {
3438 self.select_columns(position, goal_column, &display_map, window, cx);
3439 }
3440 }
3441
3442 fn update_selection(
3443 &mut self,
3444 position: DisplayPoint,
3445 goal_column: u32,
3446 scroll_delta: gpui::Point<f32>,
3447 window: &mut Window,
3448 cx: &mut Context<Self>,
3449 ) {
3450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3451
3452 if self.columnar_selection_state.is_some() {
3453 self.select_columns(position, goal_column, &display_map, window, cx);
3454 } else if let Some(mut pending) = self.selections.pending_anchor() {
3455 let buffer = self.buffer.read(cx).snapshot(cx);
3456 let head;
3457 let tail;
3458 let mode = self.selections.pending_mode().unwrap();
3459 match &mode {
3460 SelectMode::Character => {
3461 head = position.to_point(&display_map);
3462 tail = pending.tail().to_point(&buffer);
3463 }
3464 SelectMode::Word(original_range) => {
3465 let original_display_range = original_range.start.to_display_point(&display_map)
3466 ..original_range.end.to_display_point(&display_map);
3467 let original_buffer_range = original_display_range.start.to_point(&display_map)
3468 ..original_display_range.end.to_point(&display_map);
3469 if movement::is_inside_word(&display_map, position)
3470 || original_display_range.contains(&position)
3471 {
3472 let word_range = movement::surrounding_word(&display_map, position);
3473 if word_range.start < original_display_range.start {
3474 head = word_range.start.to_point(&display_map);
3475 } else {
3476 head = word_range.end.to_point(&display_map);
3477 }
3478 } else {
3479 head = position.to_point(&display_map);
3480 }
3481
3482 if head <= original_buffer_range.start {
3483 tail = original_buffer_range.end;
3484 } else {
3485 tail = original_buffer_range.start;
3486 }
3487 }
3488 SelectMode::Line(original_range) => {
3489 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3490
3491 let position = display_map
3492 .clip_point(position, Bias::Left)
3493 .to_point(&display_map);
3494 let line_start = display_map.prev_line_boundary(position).0;
3495 let next_line_start = buffer.clip_point(
3496 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3497 Bias::Left,
3498 );
3499
3500 if line_start < original_range.start {
3501 head = line_start
3502 } else {
3503 head = next_line_start
3504 }
3505
3506 if head <= original_range.start {
3507 tail = original_range.end;
3508 } else {
3509 tail = original_range.start;
3510 }
3511 }
3512 SelectMode::All => {
3513 return;
3514 }
3515 };
3516
3517 if head < tail {
3518 pending.start = buffer.anchor_before(head);
3519 pending.end = buffer.anchor_before(tail);
3520 pending.reversed = true;
3521 } else {
3522 pending.start = buffer.anchor_before(tail);
3523 pending.end = buffer.anchor_before(head);
3524 pending.reversed = false;
3525 }
3526
3527 self.change_selections(None, window, cx, |s| {
3528 s.set_pending(pending, mode);
3529 });
3530 } else {
3531 log::error!("update_selection dispatched with no pending selection");
3532 return;
3533 }
3534
3535 self.apply_scroll_delta(scroll_delta, window, cx);
3536 cx.notify();
3537 }
3538
3539 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3540 self.columnar_selection_state.take();
3541 if self.selections.pending_anchor().is_some() {
3542 let selections = self.selections.all::<usize>(cx);
3543 self.change_selections(None, window, cx, |s| {
3544 s.select(selections);
3545 s.clear_pending();
3546 });
3547 }
3548 }
3549
3550 fn select_columns(
3551 &mut self,
3552 head: DisplayPoint,
3553 goal_column: u32,
3554 display_map: &DisplaySnapshot,
3555 window: &mut Window,
3556 cx: &mut Context<Self>,
3557 ) {
3558 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3559 return;
3560 };
3561
3562 let tail = match columnar_state {
3563 ColumnarSelectionState::FromMouse {
3564 selection_tail,
3565 display_point,
3566 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3567 ColumnarSelectionState::FromSelection { selection_tail } => {
3568 selection_tail.to_display_point(&display_map)
3569 }
3570 };
3571
3572 let start_row = cmp::min(tail.row(), head.row());
3573 let end_row = cmp::max(tail.row(), head.row());
3574 let start_column = cmp::min(tail.column(), goal_column);
3575 let end_column = cmp::max(tail.column(), goal_column);
3576 let reversed = start_column < tail.column();
3577
3578 let selection_ranges = (start_row.0..=end_row.0)
3579 .map(DisplayRow)
3580 .filter_map(|row| {
3581 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3582 || start_column <= display_map.line_len(row))
3583 && !display_map.is_block_line(row)
3584 {
3585 let start = display_map
3586 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3587 .to_point(display_map);
3588 let end = display_map
3589 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3590 .to_point(display_map);
3591 if reversed {
3592 Some(end..start)
3593 } else {
3594 Some(start..end)
3595 }
3596 } else {
3597 None
3598 }
3599 })
3600 .collect::<Vec<_>>();
3601
3602 let ranges = match columnar_state {
3603 ColumnarSelectionState::FromMouse { .. } => {
3604 let mut non_empty_ranges = selection_ranges
3605 .iter()
3606 .filter(|selection_range| selection_range.start != selection_range.end)
3607 .peekable();
3608 if non_empty_ranges.peek().is_some() {
3609 non_empty_ranges.cloned().collect()
3610 } else {
3611 selection_ranges
3612 }
3613 }
3614 _ => selection_ranges,
3615 };
3616
3617 self.change_selections(None, window, cx, |s| {
3618 s.select_ranges(ranges);
3619 });
3620 cx.notify();
3621 }
3622
3623 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3624 self.selections
3625 .all_adjusted(cx)
3626 .iter()
3627 .any(|selection| !selection.is_empty())
3628 }
3629
3630 pub fn has_pending_nonempty_selection(&self) -> bool {
3631 let pending_nonempty_selection = match self.selections.pending_anchor() {
3632 Some(Selection { start, end, .. }) => start != end,
3633 None => false,
3634 };
3635
3636 pending_nonempty_selection
3637 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3638 }
3639
3640 pub fn has_pending_selection(&self) -> bool {
3641 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3642 }
3643
3644 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3645 self.selection_mark_mode = false;
3646 self.selection_drag_state = SelectionDragState::None;
3647
3648 if self.clear_expanded_diff_hunks(cx) {
3649 cx.notify();
3650 return;
3651 }
3652 if self.dismiss_menus_and_popups(true, window, cx) {
3653 return;
3654 }
3655
3656 if self.mode.is_full()
3657 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3658 {
3659 return;
3660 }
3661
3662 cx.propagate();
3663 }
3664
3665 pub fn dismiss_menus_and_popups(
3666 &mut self,
3667 is_user_requested: bool,
3668 window: &mut Window,
3669 cx: &mut Context<Self>,
3670 ) -> bool {
3671 if self.take_rename(false, window, cx).is_some() {
3672 return true;
3673 }
3674
3675 if hide_hover(self, cx) {
3676 return true;
3677 }
3678
3679 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3680 return true;
3681 }
3682
3683 if self.hide_context_menu(window, cx).is_some() {
3684 return true;
3685 }
3686
3687 if self.mouse_context_menu.take().is_some() {
3688 return true;
3689 }
3690
3691 if is_user_requested && self.discard_inline_completion(true, cx) {
3692 return true;
3693 }
3694
3695 if self.snippet_stack.pop().is_some() {
3696 return true;
3697 }
3698
3699 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3700 self.dismiss_diagnostics(cx);
3701 return true;
3702 }
3703
3704 false
3705 }
3706
3707 fn linked_editing_ranges_for(
3708 &self,
3709 selection: Range<text::Anchor>,
3710 cx: &App,
3711 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3712 if self.linked_edit_ranges.is_empty() {
3713 return None;
3714 }
3715 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3716 selection.end.buffer_id.and_then(|end_buffer_id| {
3717 if selection.start.buffer_id != Some(end_buffer_id) {
3718 return None;
3719 }
3720 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3721 let snapshot = buffer.read(cx).snapshot();
3722 self.linked_edit_ranges
3723 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3724 .map(|ranges| (ranges, snapshot, buffer))
3725 })?;
3726 use text::ToOffset as TO;
3727 // find offset from the start of current range to current cursor position
3728 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3729
3730 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3731 let start_difference = start_offset - start_byte_offset;
3732 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3733 let end_difference = end_offset - start_byte_offset;
3734 // Current range has associated linked ranges.
3735 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3736 for range in linked_ranges.iter() {
3737 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3738 let end_offset = start_offset + end_difference;
3739 let start_offset = start_offset + start_difference;
3740 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3741 continue;
3742 }
3743 if self.selections.disjoint_anchor_ranges().any(|s| {
3744 if s.start.buffer_id != selection.start.buffer_id
3745 || s.end.buffer_id != selection.end.buffer_id
3746 {
3747 return false;
3748 }
3749 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3750 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3751 }) {
3752 continue;
3753 }
3754 let start = buffer_snapshot.anchor_after(start_offset);
3755 let end = buffer_snapshot.anchor_after(end_offset);
3756 linked_edits
3757 .entry(buffer.clone())
3758 .or_default()
3759 .push(start..end);
3760 }
3761 Some(linked_edits)
3762 }
3763
3764 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3765 let text: Arc<str> = text.into();
3766
3767 if self.read_only(cx) {
3768 return;
3769 }
3770
3771 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3772
3773 let selections = self.selections.all_adjusted(cx);
3774 let mut bracket_inserted = false;
3775 let mut edits = Vec::new();
3776 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3777 let mut new_selections = Vec::with_capacity(selections.len());
3778 let mut new_autoclose_regions = Vec::new();
3779 let snapshot = self.buffer.read(cx).read(cx);
3780 let mut clear_linked_edit_ranges = false;
3781
3782 for (selection, autoclose_region) in
3783 self.selections_with_autoclose_regions(selections, &snapshot)
3784 {
3785 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3786 // Determine if the inserted text matches the opening or closing
3787 // bracket of any of this language's bracket pairs.
3788 let mut bracket_pair = None;
3789 let mut is_bracket_pair_start = false;
3790 let mut is_bracket_pair_end = false;
3791 if !text.is_empty() {
3792 let mut bracket_pair_matching_end = None;
3793 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3794 // and they are removing the character that triggered IME popup.
3795 for (pair, enabled) in scope.brackets() {
3796 if !pair.close && !pair.surround {
3797 continue;
3798 }
3799
3800 if enabled && pair.start.ends_with(text.as_ref()) {
3801 let prefix_len = pair.start.len() - text.len();
3802 let preceding_text_matches_prefix = prefix_len == 0
3803 || (selection.start.column >= (prefix_len as u32)
3804 && snapshot.contains_str_at(
3805 Point::new(
3806 selection.start.row,
3807 selection.start.column - (prefix_len as u32),
3808 ),
3809 &pair.start[..prefix_len],
3810 ));
3811 if preceding_text_matches_prefix {
3812 bracket_pair = Some(pair.clone());
3813 is_bracket_pair_start = true;
3814 break;
3815 }
3816 }
3817 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3818 {
3819 // take first bracket pair matching end, but don't break in case a later bracket
3820 // pair matches start
3821 bracket_pair_matching_end = Some(pair.clone());
3822 }
3823 }
3824 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3825 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3826 is_bracket_pair_end = true;
3827 }
3828 }
3829
3830 if let Some(bracket_pair) = bracket_pair {
3831 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3832 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3833 let auto_surround =
3834 self.use_auto_surround && snapshot_settings.use_auto_surround;
3835 if selection.is_empty() {
3836 if is_bracket_pair_start {
3837 // If the inserted text is a suffix of an opening bracket and the
3838 // selection is preceded by the rest of the opening bracket, then
3839 // insert the closing bracket.
3840 let following_text_allows_autoclose = snapshot
3841 .chars_at(selection.start)
3842 .next()
3843 .map_or(true, |c| scope.should_autoclose_before(c));
3844
3845 let preceding_text_allows_autoclose = selection.start.column == 0
3846 || snapshot.reversed_chars_at(selection.start).next().map_or(
3847 true,
3848 |c| {
3849 bracket_pair.start != bracket_pair.end
3850 || !snapshot
3851 .char_classifier_at(selection.start)
3852 .is_word(c)
3853 },
3854 );
3855
3856 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3857 && bracket_pair.start.len() == 1
3858 {
3859 let target = bracket_pair.start.chars().next().unwrap();
3860 let current_line_count = snapshot
3861 .reversed_chars_at(selection.start)
3862 .take_while(|&c| c != '\n')
3863 .filter(|&c| c == target)
3864 .count();
3865 current_line_count % 2 == 1
3866 } else {
3867 false
3868 };
3869
3870 if autoclose
3871 && bracket_pair.close
3872 && following_text_allows_autoclose
3873 && preceding_text_allows_autoclose
3874 && !is_closing_quote
3875 {
3876 let anchor = snapshot.anchor_before(selection.end);
3877 new_selections.push((selection.map(|_| anchor), text.len()));
3878 new_autoclose_regions.push((
3879 anchor,
3880 text.len(),
3881 selection.id,
3882 bracket_pair.clone(),
3883 ));
3884 edits.push((
3885 selection.range(),
3886 format!("{}{}", text, bracket_pair.end).into(),
3887 ));
3888 bracket_inserted = true;
3889 continue;
3890 }
3891 }
3892
3893 if let Some(region) = autoclose_region {
3894 // If the selection is followed by an auto-inserted closing bracket,
3895 // then don't insert that closing bracket again; just move the selection
3896 // past the closing bracket.
3897 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3898 && text.as_ref() == region.pair.end.as_str();
3899 if should_skip {
3900 let anchor = snapshot.anchor_after(selection.end);
3901 new_selections
3902 .push((selection.map(|_| anchor), region.pair.end.len()));
3903 continue;
3904 }
3905 }
3906
3907 let always_treat_brackets_as_autoclosed = snapshot
3908 .language_settings_at(selection.start, cx)
3909 .always_treat_brackets_as_autoclosed;
3910 if always_treat_brackets_as_autoclosed
3911 && is_bracket_pair_end
3912 && snapshot.contains_str_at(selection.end, text.as_ref())
3913 {
3914 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3915 // and the inserted text is a closing bracket and the selection is followed
3916 // by the closing bracket then move the selection past the closing bracket.
3917 let anchor = snapshot.anchor_after(selection.end);
3918 new_selections.push((selection.map(|_| anchor), text.len()));
3919 continue;
3920 }
3921 }
3922 // If an opening bracket is 1 character long and is typed while
3923 // text is selected, then surround that text with the bracket pair.
3924 else if auto_surround
3925 && bracket_pair.surround
3926 && is_bracket_pair_start
3927 && bracket_pair.start.chars().count() == 1
3928 {
3929 edits.push((selection.start..selection.start, text.clone()));
3930 edits.push((
3931 selection.end..selection.end,
3932 bracket_pair.end.as_str().into(),
3933 ));
3934 bracket_inserted = true;
3935 new_selections.push((
3936 Selection {
3937 id: selection.id,
3938 start: snapshot.anchor_after(selection.start),
3939 end: snapshot.anchor_before(selection.end),
3940 reversed: selection.reversed,
3941 goal: selection.goal,
3942 },
3943 0,
3944 ));
3945 continue;
3946 }
3947 }
3948 }
3949
3950 if self.auto_replace_emoji_shortcode
3951 && selection.is_empty()
3952 && text.as_ref().ends_with(':')
3953 {
3954 if let Some(possible_emoji_short_code) =
3955 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3956 {
3957 if !possible_emoji_short_code.is_empty() {
3958 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3959 let emoji_shortcode_start = Point::new(
3960 selection.start.row,
3961 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3962 );
3963
3964 // Remove shortcode from buffer
3965 edits.push((
3966 emoji_shortcode_start..selection.start,
3967 "".to_string().into(),
3968 ));
3969 new_selections.push((
3970 Selection {
3971 id: selection.id,
3972 start: snapshot.anchor_after(emoji_shortcode_start),
3973 end: snapshot.anchor_before(selection.start),
3974 reversed: selection.reversed,
3975 goal: selection.goal,
3976 },
3977 0,
3978 ));
3979
3980 // Insert emoji
3981 let selection_start_anchor = snapshot.anchor_after(selection.start);
3982 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3983 edits.push((selection.start..selection.end, emoji.to_string().into()));
3984
3985 continue;
3986 }
3987 }
3988 }
3989 }
3990
3991 // If not handling any auto-close operation, then just replace the selected
3992 // text with the given input and move the selection to the end of the
3993 // newly inserted text.
3994 let anchor = snapshot.anchor_after(selection.end);
3995 if !self.linked_edit_ranges.is_empty() {
3996 let start_anchor = snapshot.anchor_before(selection.start);
3997
3998 let is_word_char = text.chars().next().map_or(true, |char| {
3999 let classifier = snapshot
4000 .char_classifier_at(start_anchor.to_offset(&snapshot))
4001 .ignore_punctuation(true);
4002 classifier.is_word(char)
4003 });
4004
4005 if is_word_char {
4006 if let Some(ranges) = self
4007 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4008 {
4009 for (buffer, edits) in ranges {
4010 linked_edits
4011 .entry(buffer.clone())
4012 .or_default()
4013 .extend(edits.into_iter().map(|range| (range, text.clone())));
4014 }
4015 }
4016 } else {
4017 clear_linked_edit_ranges = true;
4018 }
4019 }
4020
4021 new_selections.push((selection.map(|_| anchor), 0));
4022 edits.push((selection.start..selection.end, text.clone()));
4023 }
4024
4025 drop(snapshot);
4026
4027 self.transact(window, cx, |this, window, cx| {
4028 if clear_linked_edit_ranges {
4029 this.linked_edit_ranges.clear();
4030 }
4031 let initial_buffer_versions =
4032 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4033
4034 this.buffer.update(cx, |buffer, cx| {
4035 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4036 });
4037 for (buffer, edits) in linked_edits {
4038 buffer.update(cx, |buffer, cx| {
4039 let snapshot = buffer.snapshot();
4040 let edits = edits
4041 .into_iter()
4042 .map(|(range, text)| {
4043 use text::ToPoint as TP;
4044 let end_point = TP::to_point(&range.end, &snapshot);
4045 let start_point = TP::to_point(&range.start, &snapshot);
4046 (start_point..end_point, text)
4047 })
4048 .sorted_by_key(|(range, _)| range.start);
4049 buffer.edit(edits, None, cx);
4050 })
4051 }
4052 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4053 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4054 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4055 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4056 .zip(new_selection_deltas)
4057 .map(|(selection, delta)| Selection {
4058 id: selection.id,
4059 start: selection.start + delta,
4060 end: selection.end + delta,
4061 reversed: selection.reversed,
4062 goal: SelectionGoal::None,
4063 })
4064 .collect::<Vec<_>>();
4065
4066 let mut i = 0;
4067 for (position, delta, selection_id, pair) in new_autoclose_regions {
4068 let position = position.to_offset(&map.buffer_snapshot) + delta;
4069 let start = map.buffer_snapshot.anchor_before(position);
4070 let end = map.buffer_snapshot.anchor_after(position);
4071 while let Some(existing_state) = this.autoclose_regions.get(i) {
4072 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4073 Ordering::Less => i += 1,
4074 Ordering::Greater => break,
4075 Ordering::Equal => {
4076 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4077 Ordering::Less => i += 1,
4078 Ordering::Equal => break,
4079 Ordering::Greater => break,
4080 }
4081 }
4082 }
4083 }
4084 this.autoclose_regions.insert(
4085 i,
4086 AutocloseRegion {
4087 selection_id,
4088 range: start..end,
4089 pair,
4090 },
4091 );
4092 }
4093
4094 let had_active_inline_completion = this.has_active_inline_completion();
4095 this.change_selections(
4096 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4097 window,
4098 cx,
4099 |s| s.select(new_selections),
4100 );
4101
4102 if !bracket_inserted {
4103 if let Some(on_type_format_task) =
4104 this.trigger_on_type_formatting(text.to_string(), window, cx)
4105 {
4106 on_type_format_task.detach_and_log_err(cx);
4107 }
4108 }
4109
4110 let editor_settings = EditorSettings::get_global(cx);
4111 if bracket_inserted
4112 && (editor_settings.auto_signature_help
4113 || editor_settings.show_signature_help_after_edits)
4114 {
4115 this.show_signature_help(&ShowSignatureHelp, window, cx);
4116 }
4117
4118 let trigger_in_words =
4119 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4120 if this.hard_wrap.is_some() {
4121 let latest: Range<Point> = this.selections.newest(cx).range();
4122 if latest.is_empty()
4123 && this
4124 .buffer()
4125 .read(cx)
4126 .snapshot(cx)
4127 .line_len(MultiBufferRow(latest.start.row))
4128 == latest.start.column
4129 {
4130 this.rewrap_impl(
4131 RewrapOptions {
4132 override_language_settings: true,
4133 preserve_existing_whitespace: true,
4134 },
4135 cx,
4136 )
4137 }
4138 }
4139 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4140 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4141 this.refresh_inline_completion(true, false, window, cx);
4142 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4143 });
4144 }
4145
4146 fn find_possible_emoji_shortcode_at_position(
4147 snapshot: &MultiBufferSnapshot,
4148 position: Point,
4149 ) -> Option<String> {
4150 let mut chars = Vec::new();
4151 let mut found_colon = false;
4152 for char in snapshot.reversed_chars_at(position).take(100) {
4153 // Found a possible emoji shortcode in the middle of the buffer
4154 if found_colon {
4155 if char.is_whitespace() {
4156 chars.reverse();
4157 return Some(chars.iter().collect());
4158 }
4159 // If the previous character is not a whitespace, we are in the middle of a word
4160 // and we only want to complete the shortcode if the word is made up of other emojis
4161 let mut containing_word = String::new();
4162 for ch in snapshot
4163 .reversed_chars_at(position)
4164 .skip(chars.len() + 1)
4165 .take(100)
4166 {
4167 if ch.is_whitespace() {
4168 break;
4169 }
4170 containing_word.push(ch);
4171 }
4172 let containing_word = containing_word.chars().rev().collect::<String>();
4173 if util::word_consists_of_emojis(containing_word.as_str()) {
4174 chars.reverse();
4175 return Some(chars.iter().collect());
4176 }
4177 }
4178
4179 if char.is_whitespace() || !char.is_ascii() {
4180 return None;
4181 }
4182 if char == ':' {
4183 found_colon = true;
4184 } else {
4185 chars.push(char);
4186 }
4187 }
4188 // Found a possible emoji shortcode at the beginning of the buffer
4189 chars.reverse();
4190 Some(chars.iter().collect())
4191 }
4192
4193 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4194 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4195 self.transact(window, cx, |this, window, cx| {
4196 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4197 let selections = this.selections.all::<usize>(cx);
4198 let multi_buffer = this.buffer.read(cx);
4199 let buffer = multi_buffer.snapshot(cx);
4200 selections
4201 .iter()
4202 .map(|selection| {
4203 let start_point = selection.start.to_point(&buffer);
4204 let mut existing_indent =
4205 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4206 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4207 let start = selection.start;
4208 let end = selection.end;
4209 let selection_is_empty = start == end;
4210 let language_scope = buffer.language_scope_at(start);
4211 let (
4212 comment_delimiter,
4213 doc_delimiter,
4214 insert_extra_newline,
4215 indent_on_newline,
4216 indent_on_extra_newline,
4217 ) = if let Some(language) = &language_scope {
4218 let mut insert_extra_newline =
4219 insert_extra_newline_brackets(&buffer, start..end, language)
4220 || insert_extra_newline_tree_sitter(&buffer, start..end);
4221
4222 // Comment extension on newline is allowed only for cursor selections
4223 let comment_delimiter = maybe!({
4224 if !selection_is_empty {
4225 return None;
4226 }
4227
4228 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4229 return None;
4230 }
4231
4232 let delimiters = language.line_comment_prefixes();
4233 let max_len_of_delimiter =
4234 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4235 let (snapshot, range) =
4236 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4237
4238 let num_of_whitespaces = snapshot
4239 .chars_for_range(range.clone())
4240 .take_while(|c| c.is_whitespace())
4241 .count();
4242 let comment_candidate = snapshot
4243 .chars_for_range(range)
4244 .skip(num_of_whitespaces)
4245 .take(max_len_of_delimiter)
4246 .collect::<String>();
4247 let (delimiter, trimmed_len) = delimiters
4248 .iter()
4249 .filter_map(|delimiter| {
4250 let prefix = delimiter.trim_end();
4251 if comment_candidate.starts_with(prefix) {
4252 Some((delimiter, prefix.len()))
4253 } else {
4254 None
4255 }
4256 })
4257 .max_by_key(|(_, len)| *len)?;
4258
4259 let cursor_is_placed_after_comment_marker =
4260 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4261 if cursor_is_placed_after_comment_marker {
4262 Some(delimiter.clone())
4263 } else {
4264 None
4265 }
4266 });
4267
4268 let mut indent_on_newline = IndentSize::spaces(0);
4269 let mut indent_on_extra_newline = IndentSize::spaces(0);
4270
4271 let doc_delimiter = maybe!({
4272 if !selection_is_empty {
4273 return None;
4274 }
4275
4276 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4277 return None;
4278 }
4279
4280 let DocumentationConfig {
4281 start: start_tag,
4282 end: end_tag,
4283 prefix: delimiter,
4284 tab_size: len,
4285 } = language.documentation()?;
4286
4287 let is_within_block_comment = buffer
4288 .language_scope_at(start_point)
4289 .is_some_and(|scope| scope.override_name() == Some("comment"));
4290 if !is_within_block_comment {
4291 return None;
4292 }
4293
4294 let (snapshot, range) =
4295 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4296
4297 let num_of_whitespaces = snapshot
4298 .chars_for_range(range.clone())
4299 .take_while(|c| c.is_whitespace())
4300 .count();
4301
4302 // 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.
4303 let column = start_point.column;
4304 let cursor_is_after_start_tag = {
4305 let start_tag_len = start_tag.len();
4306 let start_tag_line = snapshot
4307 .chars_for_range(range.clone())
4308 .skip(num_of_whitespaces)
4309 .take(start_tag_len)
4310 .collect::<String>();
4311 if start_tag_line.starts_with(start_tag.as_ref()) {
4312 num_of_whitespaces + start_tag_len <= column as usize
4313 } else {
4314 false
4315 }
4316 };
4317
4318 let cursor_is_after_delimiter = {
4319 let delimiter_trim = delimiter.trim_end();
4320 let delimiter_line = snapshot
4321 .chars_for_range(range.clone())
4322 .skip(num_of_whitespaces)
4323 .take(delimiter_trim.len())
4324 .collect::<String>();
4325 if delimiter_line.starts_with(delimiter_trim) {
4326 num_of_whitespaces + delimiter_trim.len() <= column as usize
4327 } else {
4328 false
4329 }
4330 };
4331
4332 let cursor_is_before_end_tag_if_exists = {
4333 let mut char_position = 0u32;
4334 let mut end_tag_offset = None;
4335
4336 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4337 if let Some(byte_pos) = chunk.find(&**end_tag) {
4338 let chars_before_match =
4339 chunk[..byte_pos].chars().count() as u32;
4340 end_tag_offset =
4341 Some(char_position + chars_before_match);
4342 break 'outer;
4343 }
4344 char_position += chunk.chars().count() as u32;
4345 }
4346
4347 if let Some(end_tag_offset) = end_tag_offset {
4348 let cursor_is_before_end_tag = column <= end_tag_offset;
4349 if cursor_is_after_start_tag {
4350 if cursor_is_before_end_tag {
4351 insert_extra_newline = true;
4352 }
4353 let cursor_is_at_start_of_end_tag =
4354 column == end_tag_offset;
4355 if cursor_is_at_start_of_end_tag {
4356 indent_on_extra_newline.len = (*len).into();
4357 }
4358 }
4359 cursor_is_before_end_tag
4360 } else {
4361 true
4362 }
4363 };
4364
4365 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4366 && cursor_is_before_end_tag_if_exists
4367 {
4368 if cursor_is_after_start_tag {
4369 indent_on_newline.len = (*len).into();
4370 }
4371 Some(delimiter.clone())
4372 } else {
4373 None
4374 }
4375 });
4376
4377 (
4378 comment_delimiter,
4379 doc_delimiter,
4380 insert_extra_newline,
4381 indent_on_newline,
4382 indent_on_extra_newline,
4383 )
4384 } else {
4385 (
4386 None,
4387 None,
4388 false,
4389 IndentSize::default(),
4390 IndentSize::default(),
4391 )
4392 };
4393
4394 let prevent_auto_indent = doc_delimiter.is_some();
4395 let delimiter = comment_delimiter.or(doc_delimiter);
4396
4397 let capacity_for_delimiter =
4398 delimiter.as_deref().map(str::len).unwrap_or_default();
4399 let mut new_text = String::with_capacity(
4400 1 + capacity_for_delimiter
4401 + existing_indent.len as usize
4402 + indent_on_newline.len as usize
4403 + indent_on_extra_newline.len as usize,
4404 );
4405 new_text.push('\n');
4406 new_text.extend(existing_indent.chars());
4407 new_text.extend(indent_on_newline.chars());
4408
4409 if let Some(delimiter) = &delimiter {
4410 new_text.push_str(delimiter);
4411 }
4412
4413 if insert_extra_newline {
4414 new_text.push('\n');
4415 new_text.extend(existing_indent.chars());
4416 new_text.extend(indent_on_extra_newline.chars());
4417 }
4418
4419 let anchor = buffer.anchor_after(end);
4420 let new_selection = selection.map(|_| anchor);
4421 (
4422 ((start..end, new_text), prevent_auto_indent),
4423 (insert_extra_newline, new_selection),
4424 )
4425 })
4426 .unzip()
4427 };
4428
4429 let mut auto_indent_edits = Vec::new();
4430 let mut edits = Vec::new();
4431 for (edit, prevent_auto_indent) in edits_with_flags {
4432 if prevent_auto_indent {
4433 edits.push(edit);
4434 } else {
4435 auto_indent_edits.push(edit);
4436 }
4437 }
4438 if !edits.is_empty() {
4439 this.edit(edits, cx);
4440 }
4441 if !auto_indent_edits.is_empty() {
4442 this.edit_with_autoindent(auto_indent_edits, cx);
4443 }
4444
4445 let buffer = this.buffer.read(cx).snapshot(cx);
4446 let new_selections = selection_info
4447 .into_iter()
4448 .map(|(extra_newline_inserted, new_selection)| {
4449 let mut cursor = new_selection.end.to_point(&buffer);
4450 if extra_newline_inserted {
4451 cursor.row -= 1;
4452 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4453 }
4454 new_selection.map(|_| cursor)
4455 })
4456 .collect();
4457
4458 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4459 s.select(new_selections)
4460 });
4461 this.refresh_inline_completion(true, false, window, cx);
4462 });
4463 }
4464
4465 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4466 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4467
4468 let buffer = self.buffer.read(cx);
4469 let snapshot = buffer.snapshot(cx);
4470
4471 let mut edits = Vec::new();
4472 let mut rows = Vec::new();
4473
4474 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4475 let cursor = selection.head();
4476 let row = cursor.row;
4477
4478 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4479
4480 let newline = "\n".to_string();
4481 edits.push((start_of_line..start_of_line, newline));
4482
4483 rows.push(row + rows_inserted as u32);
4484 }
4485
4486 self.transact(window, cx, |editor, window, cx| {
4487 editor.edit(edits, cx);
4488
4489 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4490 let mut index = 0;
4491 s.move_cursors_with(|map, _, _| {
4492 let row = rows[index];
4493 index += 1;
4494
4495 let point = Point::new(row, 0);
4496 let boundary = map.next_line_boundary(point).1;
4497 let clipped = map.clip_point(boundary, Bias::Left);
4498
4499 (clipped, SelectionGoal::None)
4500 });
4501 });
4502
4503 let mut indent_edits = Vec::new();
4504 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4505 for row in rows {
4506 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4507 for (row, indent) in indents {
4508 if indent.len == 0 {
4509 continue;
4510 }
4511
4512 let text = match indent.kind {
4513 IndentKind::Space => " ".repeat(indent.len as usize),
4514 IndentKind::Tab => "\t".repeat(indent.len as usize),
4515 };
4516 let point = Point::new(row.0, 0);
4517 indent_edits.push((point..point, text));
4518 }
4519 }
4520 editor.edit(indent_edits, cx);
4521 });
4522 }
4523
4524 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4526
4527 let buffer = self.buffer.read(cx);
4528 let snapshot = buffer.snapshot(cx);
4529
4530 let mut edits = Vec::new();
4531 let mut rows = Vec::new();
4532 let mut rows_inserted = 0;
4533
4534 for selection in self.selections.all_adjusted(cx) {
4535 let cursor = selection.head();
4536 let row = cursor.row;
4537
4538 let point = Point::new(row + 1, 0);
4539 let start_of_line = snapshot.clip_point(point, Bias::Left);
4540
4541 let newline = "\n".to_string();
4542 edits.push((start_of_line..start_of_line, newline));
4543
4544 rows_inserted += 1;
4545 rows.push(row + rows_inserted);
4546 }
4547
4548 self.transact(window, cx, |editor, window, cx| {
4549 editor.edit(edits, cx);
4550
4551 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4552 let mut index = 0;
4553 s.move_cursors_with(|map, _, _| {
4554 let row = rows[index];
4555 index += 1;
4556
4557 let point = Point::new(row, 0);
4558 let boundary = map.next_line_boundary(point).1;
4559 let clipped = map.clip_point(boundary, Bias::Left);
4560
4561 (clipped, SelectionGoal::None)
4562 });
4563 });
4564
4565 let mut indent_edits = Vec::new();
4566 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4567 for row in rows {
4568 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4569 for (row, indent) in indents {
4570 if indent.len == 0 {
4571 continue;
4572 }
4573
4574 let text = match indent.kind {
4575 IndentKind::Space => " ".repeat(indent.len as usize),
4576 IndentKind::Tab => "\t".repeat(indent.len as usize),
4577 };
4578 let point = Point::new(row.0, 0);
4579 indent_edits.push((point..point, text));
4580 }
4581 }
4582 editor.edit(indent_edits, cx);
4583 });
4584 }
4585
4586 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4587 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4588 original_indent_columns: Vec::new(),
4589 });
4590 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4591 }
4592
4593 fn insert_with_autoindent_mode(
4594 &mut self,
4595 text: &str,
4596 autoindent_mode: Option<AutoindentMode>,
4597 window: &mut Window,
4598 cx: &mut Context<Self>,
4599 ) {
4600 if self.read_only(cx) {
4601 return;
4602 }
4603
4604 let text: Arc<str> = text.into();
4605 self.transact(window, cx, |this, window, cx| {
4606 let old_selections = this.selections.all_adjusted(cx);
4607 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4608 let anchors = {
4609 let snapshot = buffer.read(cx);
4610 old_selections
4611 .iter()
4612 .map(|s| {
4613 let anchor = snapshot.anchor_after(s.head());
4614 s.map(|_| anchor)
4615 })
4616 .collect::<Vec<_>>()
4617 };
4618 buffer.edit(
4619 old_selections
4620 .iter()
4621 .map(|s| (s.start..s.end, text.clone())),
4622 autoindent_mode,
4623 cx,
4624 );
4625 anchors
4626 });
4627
4628 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4629 s.select_anchors(selection_anchors);
4630 });
4631
4632 cx.notify();
4633 });
4634 }
4635
4636 fn trigger_completion_on_input(
4637 &mut self,
4638 text: &str,
4639 trigger_in_words: bool,
4640 window: &mut Window,
4641 cx: &mut Context<Self>,
4642 ) {
4643 let completions_source = self
4644 .context_menu
4645 .borrow()
4646 .as_ref()
4647 .and_then(|menu| match menu {
4648 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4649 CodeContextMenu::CodeActions(_) => None,
4650 });
4651
4652 match completions_source {
4653 Some(CompletionsMenuSource::Words) => {
4654 self.show_word_completions(&ShowWordCompletions, window, cx)
4655 }
4656 Some(CompletionsMenuSource::Normal)
4657 | Some(CompletionsMenuSource::SnippetChoices)
4658 | None
4659 if self.is_completion_trigger(
4660 text,
4661 trigger_in_words,
4662 completions_source.is_some(),
4663 cx,
4664 ) =>
4665 {
4666 self.show_completions(
4667 &ShowCompletions {
4668 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4669 },
4670 window,
4671 cx,
4672 )
4673 }
4674 _ => {
4675 self.hide_context_menu(window, cx);
4676 }
4677 }
4678 }
4679
4680 fn is_completion_trigger(
4681 &self,
4682 text: &str,
4683 trigger_in_words: bool,
4684 menu_is_open: bool,
4685 cx: &mut Context<Self>,
4686 ) -> bool {
4687 let position = self.selections.newest_anchor().head();
4688 let multibuffer = self.buffer.read(cx);
4689 let Some(buffer) = position
4690 .buffer_id
4691 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4692 else {
4693 return false;
4694 };
4695
4696 if let Some(completion_provider) = &self.completion_provider {
4697 completion_provider.is_completion_trigger(
4698 &buffer,
4699 position.text_anchor,
4700 text,
4701 trigger_in_words,
4702 menu_is_open,
4703 cx,
4704 )
4705 } else {
4706 false
4707 }
4708 }
4709
4710 /// If any empty selections is touching the start of its innermost containing autoclose
4711 /// region, expand it to select the brackets.
4712 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4713 let selections = self.selections.all::<usize>(cx);
4714 let buffer = self.buffer.read(cx).read(cx);
4715 let new_selections = self
4716 .selections_with_autoclose_regions(selections, &buffer)
4717 .map(|(mut selection, region)| {
4718 if !selection.is_empty() {
4719 return selection;
4720 }
4721
4722 if let Some(region) = region {
4723 let mut range = region.range.to_offset(&buffer);
4724 if selection.start == range.start && range.start >= region.pair.start.len() {
4725 range.start -= region.pair.start.len();
4726 if buffer.contains_str_at(range.start, ®ion.pair.start)
4727 && buffer.contains_str_at(range.end, ®ion.pair.end)
4728 {
4729 range.end += region.pair.end.len();
4730 selection.start = range.start;
4731 selection.end = range.end;
4732
4733 return selection;
4734 }
4735 }
4736 }
4737
4738 let always_treat_brackets_as_autoclosed = buffer
4739 .language_settings_at(selection.start, cx)
4740 .always_treat_brackets_as_autoclosed;
4741
4742 if !always_treat_brackets_as_autoclosed {
4743 return selection;
4744 }
4745
4746 if let Some(scope) = buffer.language_scope_at(selection.start) {
4747 for (pair, enabled) in scope.brackets() {
4748 if !enabled || !pair.close {
4749 continue;
4750 }
4751
4752 if buffer.contains_str_at(selection.start, &pair.end) {
4753 let pair_start_len = pair.start.len();
4754 if buffer.contains_str_at(
4755 selection.start.saturating_sub(pair_start_len),
4756 &pair.start,
4757 ) {
4758 selection.start -= pair_start_len;
4759 selection.end += pair.end.len();
4760
4761 return selection;
4762 }
4763 }
4764 }
4765 }
4766
4767 selection
4768 })
4769 .collect();
4770
4771 drop(buffer);
4772 self.change_selections(None, window, cx, |selections| {
4773 selections.select(new_selections)
4774 });
4775 }
4776
4777 /// Iterate the given selections, and for each one, find the smallest surrounding
4778 /// autoclose region. This uses the ordering of the selections and the autoclose
4779 /// regions to avoid repeated comparisons.
4780 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4781 &'a self,
4782 selections: impl IntoIterator<Item = Selection<D>>,
4783 buffer: &'a MultiBufferSnapshot,
4784 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4785 let mut i = 0;
4786 let mut regions = self.autoclose_regions.as_slice();
4787 selections.into_iter().map(move |selection| {
4788 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4789
4790 let mut enclosing = None;
4791 while let Some(pair_state) = regions.get(i) {
4792 if pair_state.range.end.to_offset(buffer) < range.start {
4793 regions = ®ions[i + 1..];
4794 i = 0;
4795 } else if pair_state.range.start.to_offset(buffer) > range.end {
4796 break;
4797 } else {
4798 if pair_state.selection_id == selection.id {
4799 enclosing = Some(pair_state);
4800 }
4801 i += 1;
4802 }
4803 }
4804
4805 (selection, enclosing)
4806 })
4807 }
4808
4809 /// Remove any autoclose regions that no longer contain their selection.
4810 fn invalidate_autoclose_regions(
4811 &mut self,
4812 mut selections: &[Selection<Anchor>],
4813 buffer: &MultiBufferSnapshot,
4814 ) {
4815 self.autoclose_regions.retain(|state| {
4816 let mut i = 0;
4817 while let Some(selection) = selections.get(i) {
4818 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4819 selections = &selections[1..];
4820 continue;
4821 }
4822 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4823 break;
4824 }
4825 if selection.id == state.selection_id {
4826 return true;
4827 } else {
4828 i += 1;
4829 }
4830 }
4831 false
4832 });
4833 }
4834
4835 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4836 let offset = position.to_offset(buffer);
4837 let (word_range, kind) = buffer.surrounding_word(offset, true);
4838 if offset > word_range.start && kind == Some(CharKind::Word) {
4839 Some(
4840 buffer
4841 .text_for_range(word_range.start..offset)
4842 .collect::<String>(),
4843 )
4844 } else {
4845 None
4846 }
4847 }
4848
4849 pub fn toggle_inline_values(
4850 &mut self,
4851 _: &ToggleInlineValues,
4852 _: &mut Window,
4853 cx: &mut Context<Self>,
4854 ) {
4855 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4856
4857 self.refresh_inline_values(cx);
4858 }
4859
4860 pub fn toggle_inlay_hints(
4861 &mut self,
4862 _: &ToggleInlayHints,
4863 _: &mut Window,
4864 cx: &mut Context<Self>,
4865 ) {
4866 self.refresh_inlay_hints(
4867 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4868 cx,
4869 );
4870 }
4871
4872 pub fn inlay_hints_enabled(&self) -> bool {
4873 self.inlay_hint_cache.enabled
4874 }
4875
4876 pub fn inline_values_enabled(&self) -> bool {
4877 self.inline_value_cache.enabled
4878 }
4879
4880 #[cfg(any(test, feature = "test-support"))]
4881 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4882 self.display_map
4883 .read(cx)
4884 .current_inlays()
4885 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4886 .cloned()
4887 .collect()
4888 }
4889
4890 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4891 if self.semantics_provider.is_none() || !self.mode.is_full() {
4892 return;
4893 }
4894
4895 let reason_description = reason.description();
4896 let ignore_debounce = matches!(
4897 reason,
4898 InlayHintRefreshReason::SettingsChange(_)
4899 | InlayHintRefreshReason::Toggle(_)
4900 | InlayHintRefreshReason::ExcerptsRemoved(_)
4901 | InlayHintRefreshReason::ModifiersChanged(_)
4902 );
4903 let (invalidate_cache, required_languages) = match reason {
4904 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4905 match self.inlay_hint_cache.modifiers_override(enabled) {
4906 Some(enabled) => {
4907 if enabled {
4908 (InvalidationStrategy::RefreshRequested, None)
4909 } else {
4910 self.splice_inlays(
4911 &self
4912 .visible_inlay_hints(cx)
4913 .iter()
4914 .map(|inlay| inlay.id)
4915 .collect::<Vec<InlayId>>(),
4916 Vec::new(),
4917 cx,
4918 );
4919 return;
4920 }
4921 }
4922 None => return,
4923 }
4924 }
4925 InlayHintRefreshReason::Toggle(enabled) => {
4926 if self.inlay_hint_cache.toggle(enabled) {
4927 if enabled {
4928 (InvalidationStrategy::RefreshRequested, None)
4929 } else {
4930 self.splice_inlays(
4931 &self
4932 .visible_inlay_hints(cx)
4933 .iter()
4934 .map(|inlay| inlay.id)
4935 .collect::<Vec<InlayId>>(),
4936 Vec::new(),
4937 cx,
4938 );
4939 return;
4940 }
4941 } else {
4942 return;
4943 }
4944 }
4945 InlayHintRefreshReason::SettingsChange(new_settings) => {
4946 match self.inlay_hint_cache.update_settings(
4947 &self.buffer,
4948 new_settings,
4949 self.visible_inlay_hints(cx),
4950 cx,
4951 ) {
4952 ControlFlow::Break(Some(InlaySplice {
4953 to_remove,
4954 to_insert,
4955 })) => {
4956 self.splice_inlays(&to_remove, to_insert, cx);
4957 return;
4958 }
4959 ControlFlow::Break(None) => return,
4960 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4961 }
4962 }
4963 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4964 if let Some(InlaySplice {
4965 to_remove,
4966 to_insert,
4967 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4968 {
4969 self.splice_inlays(&to_remove, to_insert, cx);
4970 }
4971 self.display_map.update(cx, |display_map, _| {
4972 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4973 });
4974 return;
4975 }
4976 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4977 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4978 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4979 }
4980 InlayHintRefreshReason::RefreshRequested => {
4981 (InvalidationStrategy::RefreshRequested, None)
4982 }
4983 };
4984
4985 if let Some(InlaySplice {
4986 to_remove,
4987 to_insert,
4988 }) = self.inlay_hint_cache.spawn_hint_refresh(
4989 reason_description,
4990 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4991 invalidate_cache,
4992 ignore_debounce,
4993 cx,
4994 ) {
4995 self.splice_inlays(&to_remove, to_insert, cx);
4996 }
4997 }
4998
4999 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5000 self.display_map
5001 .read(cx)
5002 .current_inlays()
5003 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5004 .cloned()
5005 .collect()
5006 }
5007
5008 pub fn excerpts_for_inlay_hints_query(
5009 &self,
5010 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5011 cx: &mut Context<Editor>,
5012 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5013 let Some(project) = self.project.as_ref() else {
5014 return HashMap::default();
5015 };
5016 let project = project.read(cx);
5017 let multi_buffer = self.buffer().read(cx);
5018 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5019 let multi_buffer_visible_start = self
5020 .scroll_manager
5021 .anchor()
5022 .anchor
5023 .to_point(&multi_buffer_snapshot);
5024 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5025 multi_buffer_visible_start
5026 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5027 Bias::Left,
5028 );
5029 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5030 multi_buffer_snapshot
5031 .range_to_buffer_ranges(multi_buffer_visible_range)
5032 .into_iter()
5033 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5034 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5035 let buffer_file = project::File::from_dyn(buffer.file())?;
5036 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5037 let worktree_entry = buffer_worktree
5038 .read(cx)
5039 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5040 if worktree_entry.is_ignored {
5041 return None;
5042 }
5043
5044 let language = buffer.language()?;
5045 if let Some(restrict_to_languages) = restrict_to_languages {
5046 if !restrict_to_languages.contains(language) {
5047 return None;
5048 }
5049 }
5050 Some((
5051 excerpt_id,
5052 (
5053 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5054 buffer.version().clone(),
5055 excerpt_visible_range,
5056 ),
5057 ))
5058 })
5059 .collect()
5060 }
5061
5062 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5063 TextLayoutDetails {
5064 text_system: window.text_system().clone(),
5065 editor_style: self.style.clone().unwrap(),
5066 rem_size: window.rem_size(),
5067 scroll_anchor: self.scroll_manager.anchor(),
5068 visible_rows: self.visible_line_count(),
5069 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5070 }
5071 }
5072
5073 pub fn splice_inlays(
5074 &self,
5075 to_remove: &[InlayId],
5076 to_insert: Vec<Inlay>,
5077 cx: &mut Context<Self>,
5078 ) {
5079 self.display_map.update(cx, |display_map, cx| {
5080 display_map.splice_inlays(to_remove, to_insert, cx)
5081 });
5082 cx.notify();
5083 }
5084
5085 fn trigger_on_type_formatting(
5086 &self,
5087 input: String,
5088 window: &mut Window,
5089 cx: &mut Context<Self>,
5090 ) -> Option<Task<Result<()>>> {
5091 if input.len() != 1 {
5092 return None;
5093 }
5094
5095 let project = self.project.as_ref()?;
5096 let position = self.selections.newest_anchor().head();
5097 let (buffer, buffer_position) = self
5098 .buffer
5099 .read(cx)
5100 .text_anchor_for_position(position, cx)?;
5101
5102 let settings = language_settings::language_settings(
5103 buffer
5104 .read(cx)
5105 .language_at(buffer_position)
5106 .map(|l| l.name()),
5107 buffer.read(cx).file(),
5108 cx,
5109 );
5110 if !settings.use_on_type_format {
5111 return None;
5112 }
5113
5114 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5115 // hence we do LSP request & edit on host side only — add formats to host's history.
5116 let push_to_lsp_host_history = true;
5117 // If this is not the host, append its history with new edits.
5118 let push_to_client_history = project.read(cx).is_via_collab();
5119
5120 let on_type_formatting = project.update(cx, |project, cx| {
5121 project.on_type_format(
5122 buffer.clone(),
5123 buffer_position,
5124 input,
5125 push_to_lsp_host_history,
5126 cx,
5127 )
5128 });
5129 Some(cx.spawn_in(window, async move |editor, cx| {
5130 if let Some(transaction) = on_type_formatting.await? {
5131 if push_to_client_history {
5132 buffer
5133 .update(cx, |buffer, _| {
5134 buffer.push_transaction(transaction, Instant::now());
5135 buffer.finalize_last_transaction();
5136 })
5137 .ok();
5138 }
5139 editor.update(cx, |editor, cx| {
5140 editor.refresh_document_highlights(cx);
5141 })?;
5142 }
5143 Ok(())
5144 }))
5145 }
5146
5147 pub fn show_word_completions(
5148 &mut self,
5149 _: &ShowWordCompletions,
5150 window: &mut Window,
5151 cx: &mut Context<Self>,
5152 ) {
5153 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5154 }
5155
5156 pub fn show_completions(
5157 &mut self,
5158 options: &ShowCompletions,
5159 window: &mut Window,
5160 cx: &mut Context<Self>,
5161 ) {
5162 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5163 }
5164
5165 fn open_or_update_completions_menu(
5166 &mut self,
5167 requested_source: Option<CompletionsMenuSource>,
5168 trigger: Option<&str>,
5169 window: &mut Window,
5170 cx: &mut Context<Self>,
5171 ) {
5172 if self.pending_rename.is_some() {
5173 return;
5174 }
5175
5176 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5177
5178 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5179 // inserted and selected. To handle that case, the start of the selection is used so that
5180 // the menu starts with all choices.
5181 let position = self
5182 .selections
5183 .newest_anchor()
5184 .start
5185 .bias_right(&multibuffer_snapshot);
5186 if position.diff_base_anchor.is_some() {
5187 return;
5188 }
5189 let (buffer, buffer_position) =
5190 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5191 output
5192 } else {
5193 return;
5194 };
5195 let buffer_snapshot = buffer.read(cx).snapshot();
5196
5197 let query: Option<Arc<String>> =
5198 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5199
5200 drop(multibuffer_snapshot);
5201
5202 let provider = match requested_source {
5203 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5204 Some(CompletionsMenuSource::Words) => None,
5205 Some(CompletionsMenuSource::SnippetChoices) => {
5206 log::error!("bug: SnippetChoices requested_source is not handled");
5207 None
5208 }
5209 };
5210
5211 let sort_completions = provider
5212 .as_ref()
5213 .map_or(false, |provider| provider.sort_completions());
5214
5215 let filter_completions = provider
5216 .as_ref()
5217 .map_or(true, |provider| provider.filter_completions());
5218
5219 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5220 if filter_completions {
5221 menu.filter(query.clone(), provider.clone(), window, cx);
5222 }
5223 // When `is_incomplete` is false, no need to re-query completions when the current query
5224 // is a suffix of the initial query.
5225 if !menu.is_incomplete {
5226 // If the new query is a suffix of the old query (typing more characters) and
5227 // the previous result was complete, the existing completions can be filtered.
5228 //
5229 // Note that this is always true for snippet completions.
5230 let query_matches = match (&menu.initial_query, &query) {
5231 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5232 (None, _) => true,
5233 _ => false,
5234 };
5235 if query_matches {
5236 let position_matches = if menu.initial_position == position {
5237 true
5238 } else {
5239 let snapshot = self.buffer.read(cx).read(cx);
5240 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5241 };
5242 if position_matches {
5243 return;
5244 }
5245 }
5246 }
5247 };
5248
5249 let trigger_kind = match trigger {
5250 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5251 CompletionTriggerKind::TRIGGER_CHARACTER
5252 }
5253 _ => CompletionTriggerKind::INVOKED,
5254 };
5255 let completion_context = CompletionContext {
5256 trigger_character: trigger.and_then(|trigger| {
5257 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5258 Some(String::from(trigger))
5259 } else {
5260 None
5261 }
5262 }),
5263 trigger_kind,
5264 };
5265
5266 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5267 buffer_snapshot.surrounding_word(buffer_position)
5268 {
5269 let word_to_exclude = buffer_snapshot
5270 .text_for_range(word_range.clone())
5271 .collect::<String>();
5272 (
5273 buffer_snapshot.anchor_before(word_range.start)
5274 ..buffer_snapshot.anchor_after(buffer_position),
5275 Some(word_to_exclude),
5276 )
5277 } else {
5278 (buffer_position..buffer_position, None)
5279 };
5280
5281 let language = buffer_snapshot
5282 .language_at(buffer_position)
5283 .map(|language| language.name());
5284
5285 let completion_settings =
5286 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5287
5288 let show_completion_documentation = buffer_snapshot
5289 .settings_at(buffer_position, cx)
5290 .show_completion_documentation;
5291
5292 // The document can be large, so stay in reasonable bounds when searching for words,
5293 // otherwise completion pop-up might be slow to appear.
5294 const WORD_LOOKUP_ROWS: u32 = 5_000;
5295 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5296 let min_word_search = buffer_snapshot.clip_point(
5297 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5298 Bias::Left,
5299 );
5300 let max_word_search = buffer_snapshot.clip_point(
5301 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5302 Bias::Right,
5303 );
5304 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5305 ..buffer_snapshot.point_to_offset(max_word_search);
5306
5307 let skip_digits = query
5308 .as_ref()
5309 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5310
5311 let (mut words, provider_responses) = match &provider {
5312 Some(provider) => {
5313 let provider_responses = provider.completions(
5314 position.excerpt_id,
5315 &buffer,
5316 buffer_position,
5317 completion_context,
5318 window,
5319 cx,
5320 );
5321
5322 let words = match completion_settings.words {
5323 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5324 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5325 .background_spawn(async move {
5326 buffer_snapshot.words_in_range(WordsQuery {
5327 fuzzy_contents: None,
5328 range: word_search_range,
5329 skip_digits,
5330 })
5331 }),
5332 };
5333
5334 (words, provider_responses)
5335 }
5336 None => (
5337 cx.background_spawn(async move {
5338 buffer_snapshot.words_in_range(WordsQuery {
5339 fuzzy_contents: None,
5340 range: word_search_range,
5341 skip_digits,
5342 })
5343 }),
5344 Task::ready(Ok(Vec::new())),
5345 ),
5346 };
5347
5348 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5349
5350 let id = post_inc(&mut self.next_completion_id);
5351 let task = cx.spawn_in(window, async move |editor, cx| {
5352 let Ok(()) = editor.update(cx, |this, _| {
5353 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5354 }) else {
5355 return;
5356 };
5357
5358 // TODO: Ideally completions from different sources would be selectively re-queried, so
5359 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5360 let mut completions = Vec::new();
5361 let mut is_incomplete = false;
5362 if let Some(provider_responses) = provider_responses.await.log_err() {
5363 if !provider_responses.is_empty() {
5364 for response in provider_responses {
5365 completions.extend(response.completions);
5366 is_incomplete = is_incomplete || response.is_incomplete;
5367 }
5368 if completion_settings.words == WordsCompletionMode::Fallback {
5369 words = Task::ready(BTreeMap::default());
5370 }
5371 }
5372 }
5373
5374 let mut words = words.await;
5375 if let Some(word_to_exclude) = &word_to_exclude {
5376 words.remove(word_to_exclude);
5377 }
5378 for lsp_completion in &completions {
5379 words.remove(&lsp_completion.new_text);
5380 }
5381 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5382 replace_range: word_replace_range.clone(),
5383 new_text: word.clone(),
5384 label: CodeLabel::plain(word, None),
5385 icon_path: None,
5386 documentation: None,
5387 source: CompletionSource::BufferWord {
5388 word_range,
5389 resolved: false,
5390 },
5391 insert_text_mode: Some(InsertTextMode::AS_IS),
5392 confirm: None,
5393 }));
5394
5395 let menu = if completions.is_empty() {
5396 None
5397 } else {
5398 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5399 let languages = editor
5400 .workspace
5401 .as_ref()
5402 .and_then(|(workspace, _)| workspace.upgrade())
5403 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5404 let menu = CompletionsMenu::new(
5405 id,
5406 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5407 sort_completions,
5408 show_completion_documentation,
5409 position,
5410 query.clone(),
5411 is_incomplete,
5412 buffer.clone(),
5413 completions.into(),
5414 snippet_sort_order,
5415 languages,
5416 language,
5417 cx,
5418 );
5419
5420 let query = if filter_completions { query } else { None };
5421 let matches_task = if let Some(query) = query {
5422 menu.do_async_filtering(query, cx)
5423 } else {
5424 Task::ready(menu.unfiltered_matches())
5425 };
5426 (menu, matches_task)
5427 }) else {
5428 return;
5429 };
5430
5431 let matches = matches_task.await;
5432
5433 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5434 // Newer menu already set, so exit.
5435 match editor.context_menu.borrow().as_ref() {
5436 Some(CodeContextMenu::Completions(prev_menu)) => {
5437 if prev_menu.id > id {
5438 return;
5439 }
5440 }
5441 _ => {}
5442 };
5443
5444 // Only valid to take prev_menu because it the new menu is immediately set
5445 // below, or the menu is hidden.
5446 match editor.context_menu.borrow_mut().take() {
5447 Some(CodeContextMenu::Completions(prev_menu)) => {
5448 let position_matches =
5449 if prev_menu.initial_position == menu.initial_position {
5450 true
5451 } else {
5452 let snapshot = editor.buffer.read(cx).read(cx);
5453 prev_menu.initial_position.to_offset(&snapshot)
5454 == menu.initial_position.to_offset(&snapshot)
5455 };
5456 if position_matches {
5457 // Preserve markdown cache before `set_filter_results` because it will
5458 // try to populate the documentation cache.
5459 menu.preserve_markdown_cache(prev_menu);
5460 }
5461 }
5462 _ => {}
5463 };
5464
5465 menu.set_filter_results(matches, provider, window, cx);
5466 }) else {
5467 return;
5468 };
5469
5470 menu.visible().then_some(menu)
5471 };
5472
5473 editor
5474 .update_in(cx, |editor, window, cx| {
5475 if editor.focus_handle.is_focused(window) {
5476 if let Some(menu) = menu {
5477 *editor.context_menu.borrow_mut() =
5478 Some(CodeContextMenu::Completions(menu));
5479
5480 crate::hover_popover::hide_hover(editor, cx);
5481 if editor.show_edit_predictions_in_menu() {
5482 editor.update_visible_inline_completion(window, cx);
5483 } else {
5484 editor.discard_inline_completion(false, cx);
5485 }
5486
5487 cx.notify();
5488 return;
5489 }
5490 }
5491
5492 if editor.completion_tasks.len() <= 1 {
5493 // If there are no more completion tasks and the last menu was empty, we should hide it.
5494 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5495 // If it was already hidden and we don't show inline completions in the menu, we should
5496 // also show the inline-completion when available.
5497 if was_hidden && editor.show_edit_predictions_in_menu() {
5498 editor.update_visible_inline_completion(window, cx);
5499 }
5500 }
5501 })
5502 .ok();
5503 });
5504
5505 self.completion_tasks.push((id, task));
5506 }
5507
5508 #[cfg(feature = "test-support")]
5509 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5510 let menu = self.context_menu.borrow();
5511 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5512 let completions = menu.completions.borrow();
5513 Some(completions.to_vec())
5514 } else {
5515 None
5516 }
5517 }
5518
5519 pub fn with_completions_menu_matching_id<R>(
5520 &self,
5521 id: CompletionId,
5522 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5523 ) -> R {
5524 let mut context_menu = self.context_menu.borrow_mut();
5525 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5526 return f(None);
5527 };
5528 if completions_menu.id != id {
5529 return f(None);
5530 }
5531 f(Some(completions_menu))
5532 }
5533
5534 pub fn confirm_completion(
5535 &mut self,
5536 action: &ConfirmCompletion,
5537 window: &mut Window,
5538 cx: &mut Context<Self>,
5539 ) -> Option<Task<Result<()>>> {
5540 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5541 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5542 }
5543
5544 pub fn confirm_completion_insert(
5545 &mut self,
5546 _: &ConfirmCompletionInsert,
5547 window: &mut Window,
5548 cx: &mut Context<Self>,
5549 ) -> Option<Task<Result<()>>> {
5550 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5551 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5552 }
5553
5554 pub fn confirm_completion_replace(
5555 &mut self,
5556 _: &ConfirmCompletionReplace,
5557 window: &mut Window,
5558 cx: &mut Context<Self>,
5559 ) -> Option<Task<Result<()>>> {
5560 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5561 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5562 }
5563
5564 pub fn compose_completion(
5565 &mut self,
5566 action: &ComposeCompletion,
5567 window: &mut Window,
5568 cx: &mut Context<Self>,
5569 ) -> Option<Task<Result<()>>> {
5570 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5571 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5572 }
5573
5574 fn do_completion(
5575 &mut self,
5576 item_ix: Option<usize>,
5577 intent: CompletionIntent,
5578 window: &mut Window,
5579 cx: &mut Context<Editor>,
5580 ) -> Option<Task<Result<()>>> {
5581 use language::ToOffset as _;
5582
5583 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5584 else {
5585 return None;
5586 };
5587
5588 let candidate_id = {
5589 let entries = completions_menu.entries.borrow();
5590 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5591 if self.show_edit_predictions_in_menu() {
5592 self.discard_inline_completion(true, cx);
5593 }
5594 mat.candidate_id
5595 };
5596
5597 let completion = completions_menu
5598 .completions
5599 .borrow()
5600 .get(candidate_id)?
5601 .clone();
5602 cx.stop_propagation();
5603
5604 let buffer_handle = completions_menu.buffer.clone();
5605
5606 let CompletionEdit {
5607 new_text,
5608 snippet,
5609 replace_range,
5610 } = process_completion_for_edit(
5611 &completion,
5612 intent,
5613 &buffer_handle,
5614 &completions_menu.initial_position.text_anchor,
5615 cx,
5616 );
5617
5618 let buffer = buffer_handle.read(cx);
5619 let snapshot = self.buffer.read(cx).snapshot(cx);
5620 let newest_anchor = self.selections.newest_anchor();
5621 let replace_range_multibuffer = {
5622 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5623 let multibuffer_anchor = snapshot
5624 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5625 .unwrap()
5626 ..snapshot
5627 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5628 .unwrap();
5629 multibuffer_anchor.start.to_offset(&snapshot)
5630 ..multibuffer_anchor.end.to_offset(&snapshot)
5631 };
5632 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5633 return None;
5634 }
5635
5636 let old_text = buffer
5637 .text_for_range(replace_range.clone())
5638 .collect::<String>();
5639 let lookbehind = newest_anchor
5640 .start
5641 .text_anchor
5642 .to_offset(buffer)
5643 .saturating_sub(replace_range.start);
5644 let lookahead = replace_range
5645 .end
5646 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5647 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5648 let suffix = &old_text[lookbehind.min(old_text.len())..];
5649
5650 let selections = self.selections.all::<usize>(cx);
5651 let mut ranges = Vec::new();
5652 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5653
5654 for selection in &selections {
5655 let range = if selection.id == newest_anchor.id {
5656 replace_range_multibuffer.clone()
5657 } else {
5658 let mut range = selection.range();
5659
5660 // if prefix is present, don't duplicate it
5661 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5662 range.start = range.start.saturating_sub(lookbehind);
5663
5664 // if suffix is also present, mimic the newest cursor and replace it
5665 if selection.id != newest_anchor.id
5666 && snapshot.contains_str_at(range.end, suffix)
5667 {
5668 range.end += lookahead;
5669 }
5670 }
5671 range
5672 };
5673
5674 ranges.push(range.clone());
5675
5676 if !self.linked_edit_ranges.is_empty() {
5677 let start_anchor = snapshot.anchor_before(range.start);
5678 let end_anchor = snapshot.anchor_after(range.end);
5679 if let Some(ranges) = self
5680 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5681 {
5682 for (buffer, edits) in ranges {
5683 linked_edits
5684 .entry(buffer.clone())
5685 .or_default()
5686 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5687 }
5688 }
5689 }
5690 }
5691
5692 let common_prefix_len = old_text
5693 .chars()
5694 .zip(new_text.chars())
5695 .take_while(|(a, b)| a == b)
5696 .map(|(a, _)| a.len_utf8())
5697 .sum::<usize>();
5698
5699 cx.emit(EditorEvent::InputHandled {
5700 utf16_range_to_replace: None,
5701 text: new_text[common_prefix_len..].into(),
5702 });
5703
5704 self.transact(window, cx, |this, window, cx| {
5705 if let Some(mut snippet) = snippet {
5706 snippet.text = new_text.to_string();
5707 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5708 } else {
5709 this.buffer.update(cx, |buffer, cx| {
5710 let auto_indent = match completion.insert_text_mode {
5711 Some(InsertTextMode::AS_IS) => None,
5712 _ => this.autoindent_mode.clone(),
5713 };
5714 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5715 buffer.edit(edits, auto_indent, cx);
5716 });
5717 }
5718 for (buffer, edits) in linked_edits {
5719 buffer.update(cx, |buffer, cx| {
5720 let snapshot = buffer.snapshot();
5721 let edits = edits
5722 .into_iter()
5723 .map(|(range, text)| {
5724 use text::ToPoint as TP;
5725 let end_point = TP::to_point(&range.end, &snapshot);
5726 let start_point = TP::to_point(&range.start, &snapshot);
5727 (start_point..end_point, text)
5728 })
5729 .sorted_by_key(|(range, _)| range.start);
5730 buffer.edit(edits, None, cx);
5731 })
5732 }
5733
5734 this.refresh_inline_completion(true, false, window, cx);
5735 });
5736
5737 let show_new_completions_on_confirm = completion
5738 .confirm
5739 .as_ref()
5740 .map_or(false, |confirm| confirm(intent, window, cx));
5741 if show_new_completions_on_confirm {
5742 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5743 }
5744
5745 let provider = self.completion_provider.as_ref()?;
5746 drop(completion);
5747 let apply_edits = provider.apply_additional_edits_for_completion(
5748 buffer_handle,
5749 completions_menu.completions.clone(),
5750 candidate_id,
5751 true,
5752 cx,
5753 );
5754
5755 let editor_settings = EditorSettings::get_global(cx);
5756 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5757 // After the code completion is finished, users often want to know what signatures are needed.
5758 // so we should automatically call signature_help
5759 self.show_signature_help(&ShowSignatureHelp, window, cx);
5760 }
5761
5762 Some(cx.foreground_executor().spawn(async move {
5763 apply_edits.await?;
5764 Ok(())
5765 }))
5766 }
5767
5768 pub fn toggle_code_actions(
5769 &mut self,
5770 action: &ToggleCodeActions,
5771 window: &mut Window,
5772 cx: &mut Context<Self>,
5773 ) {
5774 let quick_launch = action.quick_launch;
5775 let mut context_menu = self.context_menu.borrow_mut();
5776 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5777 if code_actions.deployed_from == action.deployed_from {
5778 // Toggle if we're selecting the same one
5779 *context_menu = None;
5780 cx.notify();
5781 return;
5782 } else {
5783 // Otherwise, clear it and start a new one
5784 *context_menu = None;
5785 cx.notify();
5786 }
5787 }
5788 drop(context_menu);
5789 let snapshot = self.snapshot(window, cx);
5790 let deployed_from = action.deployed_from.clone();
5791 let action = action.clone();
5792 self.completion_tasks.clear();
5793 self.discard_inline_completion(false, cx);
5794
5795 let multibuffer_point = match &action.deployed_from {
5796 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5797 DisplayPoint::new(*row, 0).to_point(&snapshot)
5798 }
5799 _ => self.selections.newest::<Point>(cx).head(),
5800 };
5801 let Some((buffer, buffer_row)) = snapshot
5802 .buffer_snapshot
5803 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5804 .and_then(|(buffer_snapshot, range)| {
5805 self.buffer()
5806 .read(cx)
5807 .buffer(buffer_snapshot.remote_id())
5808 .map(|buffer| (buffer, range.start.row))
5809 })
5810 else {
5811 return;
5812 };
5813 let buffer_id = buffer.read(cx).remote_id();
5814 let tasks = self
5815 .tasks
5816 .get(&(buffer_id, buffer_row))
5817 .map(|t| Arc::new(t.to_owned()));
5818
5819 if !self.focus_handle.is_focused(window) {
5820 return;
5821 }
5822 let project = self.project.clone();
5823
5824 let code_actions_task = match deployed_from {
5825 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5826 _ => self.code_actions(buffer_row, window, cx),
5827 };
5828
5829 let runnable_task = match deployed_from {
5830 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5831 _ => {
5832 let mut task_context_task = Task::ready(None);
5833 if let Some(tasks) = &tasks {
5834 if let Some(project) = project {
5835 task_context_task =
5836 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5837 }
5838 }
5839
5840 cx.spawn_in(window, {
5841 let buffer = buffer.clone();
5842 async move |editor, cx| {
5843 let task_context = task_context_task.await;
5844
5845 let resolved_tasks =
5846 tasks
5847 .zip(task_context.clone())
5848 .map(|(tasks, task_context)| ResolvedTasks {
5849 templates: tasks.resolve(&task_context).collect(),
5850 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5851 multibuffer_point.row,
5852 tasks.column,
5853 )),
5854 });
5855 let debug_scenarios = editor
5856 .update(cx, |editor, cx| {
5857 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5858 })?
5859 .await;
5860 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5861 }
5862 })
5863 }
5864 };
5865
5866 cx.spawn_in(window, async move |editor, cx| {
5867 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5868 let code_actions = code_actions_task.await;
5869 let spawn_straight_away = quick_launch
5870 && resolved_tasks
5871 .as_ref()
5872 .map_or(false, |tasks| tasks.templates.len() == 1)
5873 && code_actions
5874 .as_ref()
5875 .map_or(true, |actions| actions.is_empty())
5876 && debug_scenarios.is_empty();
5877
5878 editor.update_in(cx, |editor, window, cx| {
5879 crate::hover_popover::hide_hover(editor, cx);
5880 *editor.context_menu.borrow_mut() =
5881 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5882 buffer,
5883 actions: CodeActionContents::new(
5884 resolved_tasks,
5885 code_actions,
5886 debug_scenarios,
5887 task_context.unwrap_or_default(),
5888 ),
5889 selected_item: Default::default(),
5890 scroll_handle: UniformListScrollHandle::default(),
5891 deployed_from,
5892 }));
5893 if spawn_straight_away {
5894 if let Some(task) = editor.confirm_code_action(
5895 &ConfirmCodeAction { item_ix: Some(0) },
5896 window,
5897 cx,
5898 ) {
5899 cx.notify();
5900 return task;
5901 }
5902 }
5903
5904 Task::ready(Ok(()))
5905 })
5906 })
5907 .detach_and_log_err(cx);
5908 }
5909
5910 fn debug_scenarios(
5911 &mut self,
5912 resolved_tasks: &Option<ResolvedTasks>,
5913 buffer: &Entity<Buffer>,
5914 cx: &mut App,
5915 ) -> Task<Vec<task::DebugScenario>> {
5916 if cx.has_flag::<DebuggerFeatureFlag>() {
5917 maybe!({
5918 let project = self.project.as_ref()?;
5919 let dap_store = project.read(cx).dap_store();
5920 let mut scenarios = vec![];
5921 let resolved_tasks = resolved_tasks.as_ref()?;
5922 let buffer = buffer.read(cx);
5923 let language = buffer.language()?;
5924 let file = buffer.file();
5925 let debug_adapter = language_settings(language.name().into(), file, cx)
5926 .debuggers
5927 .first()
5928 .map(SharedString::from)
5929 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5930
5931 dap_store.update(cx, |dap_store, cx| {
5932 for (_, task) in &resolved_tasks.templates {
5933 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5934 task.original_task().clone(),
5935 debug_adapter.clone().into(),
5936 task.display_label().to_owned().into(),
5937 cx,
5938 );
5939 scenarios.push(maybe_scenario);
5940 }
5941 });
5942 Some(cx.background_spawn(async move {
5943 let scenarios = futures::future::join_all(scenarios)
5944 .await
5945 .into_iter()
5946 .flatten()
5947 .collect::<Vec<_>>();
5948 scenarios
5949 }))
5950 })
5951 .unwrap_or_else(|| Task::ready(vec![]))
5952 } else {
5953 Task::ready(vec![])
5954 }
5955 }
5956
5957 fn code_actions(
5958 &mut self,
5959 buffer_row: u32,
5960 window: &mut Window,
5961 cx: &mut Context<Self>,
5962 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5963 let mut task = self.code_actions_task.take();
5964 cx.spawn_in(window, async move |editor, cx| {
5965 while let Some(prev_task) = task {
5966 prev_task.await.log_err();
5967 task = editor
5968 .update(cx, |this, _| this.code_actions_task.take())
5969 .ok()?;
5970 }
5971
5972 editor
5973 .update(cx, |editor, cx| {
5974 editor
5975 .available_code_actions
5976 .clone()
5977 .and_then(|(location, code_actions)| {
5978 let snapshot = location.buffer.read(cx).snapshot();
5979 let point_range = location.range.to_point(&snapshot);
5980 let point_range = point_range.start.row..=point_range.end.row;
5981 if point_range.contains(&buffer_row) {
5982 Some(code_actions)
5983 } else {
5984 None
5985 }
5986 })
5987 })
5988 .ok()
5989 .flatten()
5990 })
5991 }
5992
5993 pub fn confirm_code_action(
5994 &mut self,
5995 action: &ConfirmCodeAction,
5996 window: &mut Window,
5997 cx: &mut Context<Self>,
5998 ) -> Option<Task<Result<()>>> {
5999 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
6000
6001 let actions_menu =
6002 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6003 menu
6004 } else {
6005 return None;
6006 };
6007
6008 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6009 let action = actions_menu.actions.get(action_ix)?;
6010 let title = action.label();
6011 let buffer = actions_menu.buffer;
6012 let workspace = self.workspace()?;
6013
6014 match action {
6015 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6016 workspace.update(cx, |workspace, cx| {
6017 workspace.schedule_resolved_task(
6018 task_source_kind,
6019 resolved_task,
6020 false,
6021 window,
6022 cx,
6023 );
6024
6025 Some(Task::ready(Ok(())))
6026 })
6027 }
6028 CodeActionsItem::CodeAction {
6029 excerpt_id,
6030 action,
6031 provider,
6032 } => {
6033 let apply_code_action =
6034 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6035 let workspace = workspace.downgrade();
6036 Some(cx.spawn_in(window, async move |editor, cx| {
6037 let project_transaction = apply_code_action.await?;
6038 Self::open_project_transaction(
6039 &editor,
6040 workspace,
6041 project_transaction,
6042 title,
6043 cx,
6044 )
6045 .await
6046 }))
6047 }
6048 CodeActionsItem::DebugScenario(scenario) => {
6049 let context = actions_menu.actions.context.clone();
6050
6051 workspace.update(cx, |workspace, cx| {
6052 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6053 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6054 });
6055 Some(Task::ready(Ok(())))
6056 }
6057 }
6058 }
6059
6060 pub async fn open_project_transaction(
6061 this: &WeakEntity<Editor>,
6062 workspace: WeakEntity<Workspace>,
6063 transaction: ProjectTransaction,
6064 title: String,
6065 cx: &mut AsyncWindowContext,
6066 ) -> Result<()> {
6067 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6068 cx.update(|_, cx| {
6069 entries.sort_unstable_by_key(|(buffer, _)| {
6070 buffer.read(cx).file().map(|f| f.path().clone())
6071 });
6072 })?;
6073
6074 // If the project transaction's edits are all contained within this editor, then
6075 // avoid opening a new editor to display them.
6076
6077 if let Some((buffer, transaction)) = entries.first() {
6078 if entries.len() == 1 {
6079 let excerpt = this.update(cx, |editor, cx| {
6080 editor
6081 .buffer()
6082 .read(cx)
6083 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6084 })?;
6085 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6086 if excerpted_buffer == *buffer {
6087 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6088 let excerpt_range = excerpt_range.to_offset(buffer);
6089 buffer
6090 .edited_ranges_for_transaction::<usize>(transaction)
6091 .all(|range| {
6092 excerpt_range.start <= range.start
6093 && excerpt_range.end >= range.end
6094 })
6095 })?;
6096
6097 if all_edits_within_excerpt {
6098 return Ok(());
6099 }
6100 }
6101 }
6102 }
6103 } else {
6104 return Ok(());
6105 }
6106
6107 let mut ranges_to_highlight = Vec::new();
6108 let excerpt_buffer = cx.new(|cx| {
6109 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6110 for (buffer_handle, transaction) in &entries {
6111 let edited_ranges = buffer_handle
6112 .read(cx)
6113 .edited_ranges_for_transaction::<Point>(transaction)
6114 .collect::<Vec<_>>();
6115 let (ranges, _) = multibuffer.set_excerpts_for_path(
6116 PathKey::for_buffer(buffer_handle, cx),
6117 buffer_handle.clone(),
6118 edited_ranges,
6119 DEFAULT_MULTIBUFFER_CONTEXT,
6120 cx,
6121 );
6122
6123 ranges_to_highlight.extend(ranges);
6124 }
6125 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6126 multibuffer
6127 })?;
6128
6129 workspace.update_in(cx, |workspace, window, cx| {
6130 let project = workspace.project().clone();
6131 let editor =
6132 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6133 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6134 editor.update(cx, |editor, cx| {
6135 editor.highlight_background::<Self>(
6136 &ranges_to_highlight,
6137 |theme| theme.editor_highlighted_line_background,
6138 cx,
6139 );
6140 });
6141 })?;
6142
6143 Ok(())
6144 }
6145
6146 pub fn clear_code_action_providers(&mut self) {
6147 self.code_action_providers.clear();
6148 self.available_code_actions.take();
6149 }
6150
6151 pub fn add_code_action_provider(
6152 &mut self,
6153 provider: Rc<dyn CodeActionProvider>,
6154 window: &mut Window,
6155 cx: &mut Context<Self>,
6156 ) {
6157 if self
6158 .code_action_providers
6159 .iter()
6160 .any(|existing_provider| existing_provider.id() == provider.id())
6161 {
6162 return;
6163 }
6164
6165 self.code_action_providers.push(provider);
6166 self.refresh_code_actions(window, cx);
6167 }
6168
6169 pub fn remove_code_action_provider(
6170 &mut self,
6171 id: Arc<str>,
6172 window: &mut Window,
6173 cx: &mut Context<Self>,
6174 ) {
6175 self.code_action_providers
6176 .retain(|provider| provider.id() != id);
6177 self.refresh_code_actions(window, cx);
6178 }
6179
6180 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6181 !self.code_action_providers.is_empty()
6182 && EditorSettings::get_global(cx).toolbar.code_actions
6183 }
6184
6185 pub fn has_available_code_actions(&self) -> bool {
6186 self.available_code_actions
6187 .as_ref()
6188 .is_some_and(|(_, actions)| !actions.is_empty())
6189 }
6190
6191 fn render_inline_code_actions(
6192 &self,
6193 icon_size: ui::IconSize,
6194 display_row: DisplayRow,
6195 is_active: bool,
6196 cx: &mut Context<Self>,
6197 ) -> AnyElement {
6198 let show_tooltip = !self.context_menu_visible();
6199 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6200 .icon_size(icon_size)
6201 .shape(ui::IconButtonShape::Square)
6202 .style(ButtonStyle::Transparent)
6203 .icon_color(ui::Color::Hidden)
6204 .toggle_state(is_active)
6205 .when(show_tooltip, |this| {
6206 this.tooltip({
6207 let focus_handle = self.focus_handle.clone();
6208 move |window, cx| {
6209 Tooltip::for_action_in(
6210 "Toggle Code Actions",
6211 &ToggleCodeActions {
6212 deployed_from: None,
6213 quick_launch: false,
6214 },
6215 &focus_handle,
6216 window,
6217 cx,
6218 )
6219 }
6220 })
6221 })
6222 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6223 window.focus(&editor.focus_handle(cx));
6224 editor.toggle_code_actions(
6225 &crate::actions::ToggleCodeActions {
6226 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6227 display_row,
6228 )),
6229 quick_launch: false,
6230 },
6231 window,
6232 cx,
6233 );
6234 }))
6235 .into_any_element()
6236 }
6237
6238 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6239 &self.context_menu
6240 }
6241
6242 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6243 let newest_selection = self.selections.newest_anchor().clone();
6244 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6245 let buffer = self.buffer.read(cx);
6246 if newest_selection.head().diff_base_anchor.is_some() {
6247 return None;
6248 }
6249 let (start_buffer, start) =
6250 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6251 let (end_buffer, end) =
6252 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6253 if start_buffer != end_buffer {
6254 return None;
6255 }
6256
6257 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6258 cx.background_executor()
6259 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6260 .await;
6261
6262 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6263 let providers = this.code_action_providers.clone();
6264 let tasks = this
6265 .code_action_providers
6266 .iter()
6267 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6268 .collect::<Vec<_>>();
6269 (providers, tasks)
6270 })?;
6271
6272 let mut actions = Vec::new();
6273 for (provider, provider_actions) in
6274 providers.into_iter().zip(future::join_all(tasks).await)
6275 {
6276 if let Some(provider_actions) = provider_actions.log_err() {
6277 actions.extend(provider_actions.into_iter().map(|action| {
6278 AvailableCodeAction {
6279 excerpt_id: newest_selection.start.excerpt_id,
6280 action,
6281 provider: provider.clone(),
6282 }
6283 }));
6284 }
6285 }
6286
6287 this.update(cx, |this, cx| {
6288 this.available_code_actions = if actions.is_empty() {
6289 None
6290 } else {
6291 Some((
6292 Location {
6293 buffer: start_buffer,
6294 range: start..end,
6295 },
6296 actions.into(),
6297 ))
6298 };
6299 cx.notify();
6300 })
6301 }));
6302 None
6303 }
6304
6305 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6306 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6307 self.show_git_blame_inline = false;
6308
6309 self.show_git_blame_inline_delay_task =
6310 Some(cx.spawn_in(window, async move |this, cx| {
6311 cx.background_executor().timer(delay).await;
6312
6313 this.update(cx, |this, cx| {
6314 this.show_git_blame_inline = true;
6315 cx.notify();
6316 })
6317 .log_err();
6318 }));
6319 }
6320 }
6321
6322 fn show_blame_popover(
6323 &mut self,
6324 blame_entry: &BlameEntry,
6325 position: gpui::Point<Pixels>,
6326 cx: &mut Context<Self>,
6327 ) {
6328 if let Some(state) = &mut self.inline_blame_popover {
6329 state.hide_task.take();
6330 } else {
6331 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6332 let blame_entry = blame_entry.clone();
6333 let show_task = cx.spawn(async move |editor, cx| {
6334 cx.background_executor()
6335 .timer(std::time::Duration::from_millis(delay))
6336 .await;
6337 editor
6338 .update(cx, |editor, cx| {
6339 editor.inline_blame_popover_show_task.take();
6340 let Some(blame) = editor.blame.as_ref() else {
6341 return;
6342 };
6343 let blame = blame.read(cx);
6344 let details = blame.details_for_entry(&blame_entry);
6345 let markdown = cx.new(|cx| {
6346 Markdown::new(
6347 details
6348 .as_ref()
6349 .map(|message| message.message.clone())
6350 .unwrap_or_default(),
6351 None,
6352 None,
6353 cx,
6354 )
6355 });
6356 editor.inline_blame_popover = Some(InlineBlamePopover {
6357 position,
6358 hide_task: None,
6359 popover_bounds: None,
6360 popover_state: InlineBlamePopoverState {
6361 scroll_handle: ScrollHandle::new(),
6362 commit_message: details,
6363 markdown,
6364 },
6365 });
6366 cx.notify();
6367 })
6368 .ok();
6369 });
6370 self.inline_blame_popover_show_task = Some(show_task);
6371 }
6372 }
6373
6374 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6375 self.inline_blame_popover_show_task.take();
6376 if let Some(state) = &mut self.inline_blame_popover {
6377 let hide_task = cx.spawn(async move |editor, cx| {
6378 cx.background_executor()
6379 .timer(std::time::Duration::from_millis(100))
6380 .await;
6381 editor
6382 .update(cx, |editor, cx| {
6383 editor.inline_blame_popover.take();
6384 cx.notify();
6385 })
6386 .ok();
6387 });
6388 state.hide_task = Some(hide_task);
6389 }
6390 }
6391
6392 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6393 if self.pending_rename.is_some() {
6394 return None;
6395 }
6396
6397 let provider = self.semantics_provider.clone()?;
6398 let buffer = self.buffer.read(cx);
6399 let newest_selection = self.selections.newest_anchor().clone();
6400 let cursor_position = newest_selection.head();
6401 let (cursor_buffer, cursor_buffer_position) =
6402 buffer.text_anchor_for_position(cursor_position, cx)?;
6403 let (tail_buffer, tail_buffer_position) =
6404 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6405 if cursor_buffer != tail_buffer {
6406 return None;
6407 }
6408
6409 let snapshot = cursor_buffer.read(cx).snapshot();
6410 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6411 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6412 if start_word_range != end_word_range {
6413 self.document_highlights_task.take();
6414 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6415 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6416 return None;
6417 }
6418
6419 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6420 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6421 cx.background_executor()
6422 .timer(Duration::from_millis(debounce))
6423 .await;
6424
6425 let highlights = if let Some(highlights) = cx
6426 .update(|cx| {
6427 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6428 })
6429 .ok()
6430 .flatten()
6431 {
6432 highlights.await.log_err()
6433 } else {
6434 None
6435 };
6436
6437 if let Some(highlights) = highlights {
6438 this.update(cx, |this, cx| {
6439 if this.pending_rename.is_some() {
6440 return;
6441 }
6442
6443 let buffer_id = cursor_position.buffer_id;
6444 let buffer = this.buffer.read(cx);
6445 if !buffer
6446 .text_anchor_for_position(cursor_position, cx)
6447 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6448 {
6449 return;
6450 }
6451
6452 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6453 let mut write_ranges = Vec::new();
6454 let mut read_ranges = Vec::new();
6455 for highlight in highlights {
6456 for (excerpt_id, excerpt_range) in
6457 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6458 {
6459 let start = highlight
6460 .range
6461 .start
6462 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6463 let end = highlight
6464 .range
6465 .end
6466 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6467 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6468 continue;
6469 }
6470
6471 let range = Anchor {
6472 buffer_id,
6473 excerpt_id,
6474 text_anchor: start,
6475 diff_base_anchor: None,
6476 }..Anchor {
6477 buffer_id,
6478 excerpt_id,
6479 text_anchor: end,
6480 diff_base_anchor: None,
6481 };
6482 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6483 write_ranges.push(range);
6484 } else {
6485 read_ranges.push(range);
6486 }
6487 }
6488 }
6489
6490 this.highlight_background::<DocumentHighlightRead>(
6491 &read_ranges,
6492 |theme| theme.editor_document_highlight_read_background,
6493 cx,
6494 );
6495 this.highlight_background::<DocumentHighlightWrite>(
6496 &write_ranges,
6497 |theme| theme.editor_document_highlight_write_background,
6498 cx,
6499 );
6500 cx.notify();
6501 })
6502 .log_err();
6503 }
6504 }));
6505 None
6506 }
6507
6508 fn prepare_highlight_query_from_selection(
6509 &mut self,
6510 cx: &mut Context<Editor>,
6511 ) -> Option<(String, Range<Anchor>)> {
6512 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6513 return None;
6514 }
6515 if !EditorSettings::get_global(cx).selection_highlight {
6516 return None;
6517 }
6518 if self.selections.count() != 1 || self.selections.line_mode {
6519 return None;
6520 }
6521 let selection = self.selections.newest::<Point>(cx);
6522 if selection.is_empty() || selection.start.row != selection.end.row {
6523 return None;
6524 }
6525 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6526 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6527 let query = multi_buffer_snapshot
6528 .text_for_range(selection_anchor_range.clone())
6529 .collect::<String>();
6530 if query.trim().is_empty() {
6531 return None;
6532 }
6533 Some((query, selection_anchor_range))
6534 }
6535
6536 fn update_selection_occurrence_highlights(
6537 &mut self,
6538 query_text: String,
6539 query_range: Range<Anchor>,
6540 multi_buffer_range_to_query: Range<Point>,
6541 use_debounce: bool,
6542 window: &mut Window,
6543 cx: &mut Context<Editor>,
6544 ) -> Task<()> {
6545 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6546 cx.spawn_in(window, async move |editor, cx| {
6547 if use_debounce {
6548 cx.background_executor()
6549 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6550 .await;
6551 }
6552 let match_task = cx.background_spawn(async move {
6553 let buffer_ranges = multi_buffer_snapshot
6554 .range_to_buffer_ranges(multi_buffer_range_to_query)
6555 .into_iter()
6556 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6557 let mut match_ranges = Vec::new();
6558 let Ok(regex) = project::search::SearchQuery::text(
6559 query_text.clone(),
6560 false,
6561 false,
6562 false,
6563 Default::default(),
6564 Default::default(),
6565 false,
6566 None,
6567 ) else {
6568 return Vec::default();
6569 };
6570 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6571 match_ranges.extend(
6572 regex
6573 .search(&buffer_snapshot, Some(search_range.clone()))
6574 .await
6575 .into_iter()
6576 .filter_map(|match_range| {
6577 let match_start = buffer_snapshot
6578 .anchor_after(search_range.start + match_range.start);
6579 let match_end = buffer_snapshot
6580 .anchor_before(search_range.start + match_range.end);
6581 let match_anchor_range = Anchor::range_in_buffer(
6582 excerpt_id,
6583 buffer_snapshot.remote_id(),
6584 match_start..match_end,
6585 );
6586 (match_anchor_range != query_range).then_some(match_anchor_range)
6587 }),
6588 );
6589 }
6590 match_ranges
6591 });
6592 let match_ranges = match_task.await;
6593 editor
6594 .update_in(cx, |editor, _, cx| {
6595 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6596 if !match_ranges.is_empty() {
6597 editor.highlight_background::<SelectedTextHighlight>(
6598 &match_ranges,
6599 |theme| theme.editor_document_highlight_bracket_background,
6600 cx,
6601 )
6602 }
6603 })
6604 .log_err();
6605 })
6606 }
6607
6608 fn refresh_selected_text_highlights(
6609 &mut self,
6610 on_buffer_edit: bool,
6611 window: &mut Window,
6612 cx: &mut Context<Editor>,
6613 ) {
6614 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6615 else {
6616 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6617 self.quick_selection_highlight_task.take();
6618 self.debounced_selection_highlight_task.take();
6619 return;
6620 };
6621 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6622 if on_buffer_edit
6623 || self
6624 .quick_selection_highlight_task
6625 .as_ref()
6626 .map_or(true, |(prev_anchor_range, _)| {
6627 prev_anchor_range != &query_range
6628 })
6629 {
6630 let multi_buffer_visible_start = self
6631 .scroll_manager
6632 .anchor()
6633 .anchor
6634 .to_point(&multi_buffer_snapshot);
6635 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6636 multi_buffer_visible_start
6637 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6638 Bias::Left,
6639 );
6640 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6641 self.quick_selection_highlight_task = Some((
6642 query_range.clone(),
6643 self.update_selection_occurrence_highlights(
6644 query_text.clone(),
6645 query_range.clone(),
6646 multi_buffer_visible_range,
6647 false,
6648 window,
6649 cx,
6650 ),
6651 ));
6652 }
6653 if on_buffer_edit
6654 || self
6655 .debounced_selection_highlight_task
6656 .as_ref()
6657 .map_or(true, |(prev_anchor_range, _)| {
6658 prev_anchor_range != &query_range
6659 })
6660 {
6661 let multi_buffer_start = multi_buffer_snapshot
6662 .anchor_before(0)
6663 .to_point(&multi_buffer_snapshot);
6664 let multi_buffer_end = multi_buffer_snapshot
6665 .anchor_after(multi_buffer_snapshot.len())
6666 .to_point(&multi_buffer_snapshot);
6667 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6668 self.debounced_selection_highlight_task = Some((
6669 query_range.clone(),
6670 self.update_selection_occurrence_highlights(
6671 query_text,
6672 query_range,
6673 multi_buffer_full_range,
6674 true,
6675 window,
6676 cx,
6677 ),
6678 ));
6679 }
6680 }
6681
6682 pub fn refresh_inline_completion(
6683 &mut self,
6684 debounce: bool,
6685 user_requested: bool,
6686 window: &mut Window,
6687 cx: &mut Context<Self>,
6688 ) -> Option<()> {
6689 let provider = self.edit_prediction_provider()?;
6690 let cursor = self.selections.newest_anchor().head();
6691 let (buffer, cursor_buffer_position) =
6692 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6693
6694 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6695 self.discard_inline_completion(false, cx);
6696 return None;
6697 }
6698
6699 if !user_requested
6700 && (!self.should_show_edit_predictions()
6701 || !self.is_focused(window)
6702 || buffer.read(cx).is_empty())
6703 {
6704 self.discard_inline_completion(false, cx);
6705 return None;
6706 }
6707
6708 self.update_visible_inline_completion(window, cx);
6709 provider.refresh(
6710 self.project.clone(),
6711 buffer,
6712 cursor_buffer_position,
6713 debounce,
6714 cx,
6715 );
6716 Some(())
6717 }
6718
6719 fn show_edit_predictions_in_menu(&self) -> bool {
6720 match self.edit_prediction_settings {
6721 EditPredictionSettings::Disabled => false,
6722 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6723 }
6724 }
6725
6726 pub fn edit_predictions_enabled(&self) -> bool {
6727 match self.edit_prediction_settings {
6728 EditPredictionSettings::Disabled => false,
6729 EditPredictionSettings::Enabled { .. } => true,
6730 }
6731 }
6732
6733 fn edit_prediction_requires_modifier(&self) -> bool {
6734 match self.edit_prediction_settings {
6735 EditPredictionSettings::Disabled => false,
6736 EditPredictionSettings::Enabled {
6737 preview_requires_modifier,
6738 ..
6739 } => preview_requires_modifier,
6740 }
6741 }
6742
6743 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6744 if self.edit_prediction_provider.is_none() {
6745 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6746 } else {
6747 let selection = self.selections.newest_anchor();
6748 let cursor = selection.head();
6749
6750 if let Some((buffer, cursor_buffer_position)) =
6751 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6752 {
6753 self.edit_prediction_settings =
6754 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6755 }
6756 }
6757 }
6758
6759 fn edit_prediction_settings_at_position(
6760 &self,
6761 buffer: &Entity<Buffer>,
6762 buffer_position: language::Anchor,
6763 cx: &App,
6764 ) -> EditPredictionSettings {
6765 if !self.mode.is_full()
6766 || !self.show_inline_completions_override.unwrap_or(true)
6767 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6768 {
6769 return EditPredictionSettings::Disabled;
6770 }
6771
6772 let buffer = buffer.read(cx);
6773
6774 let file = buffer.file();
6775
6776 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6777 return EditPredictionSettings::Disabled;
6778 };
6779
6780 let by_provider = matches!(
6781 self.menu_inline_completions_policy,
6782 MenuInlineCompletionsPolicy::ByProvider
6783 );
6784
6785 let show_in_menu = by_provider
6786 && self
6787 .edit_prediction_provider
6788 .as_ref()
6789 .map_or(false, |provider| {
6790 provider.provider.show_completions_in_menu()
6791 });
6792
6793 let preview_requires_modifier =
6794 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6795
6796 EditPredictionSettings::Enabled {
6797 show_in_menu,
6798 preview_requires_modifier,
6799 }
6800 }
6801
6802 fn should_show_edit_predictions(&self) -> bool {
6803 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6804 }
6805
6806 pub fn edit_prediction_preview_is_active(&self) -> bool {
6807 matches!(
6808 self.edit_prediction_preview,
6809 EditPredictionPreview::Active { .. }
6810 )
6811 }
6812
6813 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6814 let cursor = self.selections.newest_anchor().head();
6815 if let Some((buffer, cursor_position)) =
6816 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6817 {
6818 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6819 } else {
6820 false
6821 }
6822 }
6823
6824 pub fn supports_minimap(&self, cx: &App) -> bool {
6825 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6826 }
6827
6828 fn edit_predictions_enabled_in_buffer(
6829 &self,
6830 buffer: &Entity<Buffer>,
6831 buffer_position: language::Anchor,
6832 cx: &App,
6833 ) -> bool {
6834 maybe!({
6835 if self.read_only(cx) {
6836 return Some(false);
6837 }
6838 let provider = self.edit_prediction_provider()?;
6839 if !provider.is_enabled(&buffer, buffer_position, cx) {
6840 return Some(false);
6841 }
6842 let buffer = buffer.read(cx);
6843 let Some(file) = buffer.file() else {
6844 return Some(true);
6845 };
6846 let settings = all_language_settings(Some(file), cx);
6847 Some(settings.edit_predictions_enabled_for_file(file, cx))
6848 })
6849 .unwrap_or(false)
6850 }
6851
6852 fn cycle_inline_completion(
6853 &mut self,
6854 direction: Direction,
6855 window: &mut Window,
6856 cx: &mut Context<Self>,
6857 ) -> Option<()> {
6858 let provider = self.edit_prediction_provider()?;
6859 let cursor = self.selections.newest_anchor().head();
6860 let (buffer, cursor_buffer_position) =
6861 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6862 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6863 return None;
6864 }
6865
6866 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6867 self.update_visible_inline_completion(window, cx);
6868
6869 Some(())
6870 }
6871
6872 pub fn show_inline_completion(
6873 &mut self,
6874 _: &ShowEditPrediction,
6875 window: &mut Window,
6876 cx: &mut Context<Self>,
6877 ) {
6878 if !self.has_active_inline_completion() {
6879 self.refresh_inline_completion(false, true, window, cx);
6880 return;
6881 }
6882
6883 self.update_visible_inline_completion(window, cx);
6884 }
6885
6886 pub fn display_cursor_names(
6887 &mut self,
6888 _: &DisplayCursorNames,
6889 window: &mut Window,
6890 cx: &mut Context<Self>,
6891 ) {
6892 self.show_cursor_names(window, cx);
6893 }
6894
6895 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6896 self.show_cursor_names = true;
6897 cx.notify();
6898 cx.spawn_in(window, async move |this, cx| {
6899 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6900 this.update(cx, |this, cx| {
6901 this.show_cursor_names = false;
6902 cx.notify()
6903 })
6904 .ok()
6905 })
6906 .detach();
6907 }
6908
6909 pub fn next_edit_prediction(
6910 &mut self,
6911 _: &NextEditPrediction,
6912 window: &mut Window,
6913 cx: &mut Context<Self>,
6914 ) {
6915 if self.has_active_inline_completion() {
6916 self.cycle_inline_completion(Direction::Next, window, cx);
6917 } else {
6918 let is_copilot_disabled = self
6919 .refresh_inline_completion(false, true, window, cx)
6920 .is_none();
6921 if is_copilot_disabled {
6922 cx.propagate();
6923 }
6924 }
6925 }
6926
6927 pub fn previous_edit_prediction(
6928 &mut self,
6929 _: &PreviousEditPrediction,
6930 window: &mut Window,
6931 cx: &mut Context<Self>,
6932 ) {
6933 if self.has_active_inline_completion() {
6934 self.cycle_inline_completion(Direction::Prev, window, cx);
6935 } else {
6936 let is_copilot_disabled = self
6937 .refresh_inline_completion(false, true, window, cx)
6938 .is_none();
6939 if is_copilot_disabled {
6940 cx.propagate();
6941 }
6942 }
6943 }
6944
6945 pub fn accept_edit_prediction(
6946 &mut self,
6947 _: &AcceptEditPrediction,
6948 window: &mut Window,
6949 cx: &mut Context<Self>,
6950 ) {
6951 if self.show_edit_predictions_in_menu() {
6952 self.hide_context_menu(window, cx);
6953 }
6954
6955 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6956 return;
6957 };
6958
6959 self.report_inline_completion_event(
6960 active_inline_completion.completion_id.clone(),
6961 true,
6962 cx,
6963 );
6964
6965 match &active_inline_completion.completion {
6966 InlineCompletion::Move { target, .. } => {
6967 let target = *target;
6968
6969 if let Some(position_map) = &self.last_position_map {
6970 if position_map
6971 .visible_row_range
6972 .contains(&target.to_display_point(&position_map.snapshot).row())
6973 || !self.edit_prediction_requires_modifier()
6974 {
6975 self.unfold_ranges(&[target..target], true, false, cx);
6976 // Note that this is also done in vim's handler of the Tab action.
6977 self.change_selections(
6978 Some(Autoscroll::newest()),
6979 window,
6980 cx,
6981 |selections| {
6982 selections.select_anchor_ranges([target..target]);
6983 },
6984 );
6985 self.clear_row_highlights::<EditPredictionPreview>();
6986
6987 self.edit_prediction_preview
6988 .set_previous_scroll_position(None);
6989 } else {
6990 self.edit_prediction_preview
6991 .set_previous_scroll_position(Some(
6992 position_map.snapshot.scroll_anchor,
6993 ));
6994
6995 self.highlight_rows::<EditPredictionPreview>(
6996 target..target,
6997 cx.theme().colors().editor_highlighted_line_background,
6998 RowHighlightOptions {
6999 autoscroll: true,
7000 ..Default::default()
7001 },
7002 cx,
7003 );
7004 self.request_autoscroll(Autoscroll::fit(), cx);
7005 }
7006 }
7007 }
7008 InlineCompletion::Edit { edits, .. } => {
7009 if let Some(provider) = self.edit_prediction_provider() {
7010 provider.accept(cx);
7011 }
7012
7013 // Store the transaction ID and selections before applying the edit
7014 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7015
7016 let snapshot = self.buffer.read(cx).snapshot(cx);
7017 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7018
7019 self.buffer.update(cx, |buffer, cx| {
7020 buffer.edit(edits.iter().cloned(), None, cx)
7021 });
7022
7023 self.change_selections(None, window, cx, |s| {
7024 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7025 });
7026
7027 let selections = self.selections.disjoint_anchors();
7028 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7029 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7030 if has_new_transaction {
7031 self.selection_history
7032 .insert_transaction(transaction_id_now, selections);
7033 }
7034 }
7035
7036 self.update_visible_inline_completion(window, cx);
7037 if self.active_inline_completion.is_none() {
7038 self.refresh_inline_completion(true, true, window, cx);
7039 }
7040
7041 cx.notify();
7042 }
7043 }
7044
7045 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7046 }
7047
7048 pub fn accept_partial_inline_completion(
7049 &mut self,
7050 _: &AcceptPartialEditPrediction,
7051 window: &mut Window,
7052 cx: &mut Context<Self>,
7053 ) {
7054 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7055 return;
7056 };
7057 if self.selections.count() != 1 {
7058 return;
7059 }
7060
7061 self.report_inline_completion_event(
7062 active_inline_completion.completion_id.clone(),
7063 true,
7064 cx,
7065 );
7066
7067 match &active_inline_completion.completion {
7068 InlineCompletion::Move { target, .. } => {
7069 let target = *target;
7070 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7071 selections.select_anchor_ranges([target..target]);
7072 });
7073 }
7074 InlineCompletion::Edit { edits, .. } => {
7075 // Find an insertion that starts at the cursor position.
7076 let snapshot = self.buffer.read(cx).snapshot(cx);
7077 let cursor_offset = self.selections.newest::<usize>(cx).head();
7078 let insertion = edits.iter().find_map(|(range, text)| {
7079 let range = range.to_offset(&snapshot);
7080 if range.is_empty() && range.start == cursor_offset {
7081 Some(text)
7082 } else {
7083 None
7084 }
7085 });
7086
7087 if let Some(text) = insertion {
7088 let mut partial_completion = text
7089 .chars()
7090 .by_ref()
7091 .take_while(|c| c.is_alphabetic())
7092 .collect::<String>();
7093 if partial_completion.is_empty() {
7094 partial_completion = text
7095 .chars()
7096 .by_ref()
7097 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7098 .collect::<String>();
7099 }
7100
7101 cx.emit(EditorEvent::InputHandled {
7102 utf16_range_to_replace: None,
7103 text: partial_completion.clone().into(),
7104 });
7105
7106 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7107
7108 self.refresh_inline_completion(true, true, window, cx);
7109 cx.notify();
7110 } else {
7111 self.accept_edit_prediction(&Default::default(), window, cx);
7112 }
7113 }
7114 }
7115 }
7116
7117 fn discard_inline_completion(
7118 &mut self,
7119 should_report_inline_completion_event: bool,
7120 cx: &mut Context<Self>,
7121 ) -> bool {
7122 if should_report_inline_completion_event {
7123 let completion_id = self
7124 .active_inline_completion
7125 .as_ref()
7126 .and_then(|active_completion| active_completion.completion_id.clone());
7127
7128 self.report_inline_completion_event(completion_id, false, cx);
7129 }
7130
7131 if let Some(provider) = self.edit_prediction_provider() {
7132 provider.discard(cx);
7133 }
7134
7135 self.take_active_inline_completion(cx)
7136 }
7137
7138 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7139 let Some(provider) = self.edit_prediction_provider() else {
7140 return;
7141 };
7142
7143 let Some((_, buffer, _)) = self
7144 .buffer
7145 .read(cx)
7146 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7147 else {
7148 return;
7149 };
7150
7151 let extension = buffer
7152 .read(cx)
7153 .file()
7154 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7155
7156 let event_type = match accepted {
7157 true => "Edit Prediction Accepted",
7158 false => "Edit Prediction Discarded",
7159 };
7160 telemetry::event!(
7161 event_type,
7162 provider = provider.name(),
7163 prediction_id = id,
7164 suggestion_accepted = accepted,
7165 file_extension = extension,
7166 );
7167 }
7168
7169 pub fn has_active_inline_completion(&self) -> bool {
7170 self.active_inline_completion.is_some()
7171 }
7172
7173 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7174 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7175 return false;
7176 };
7177
7178 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7179 self.clear_highlights::<InlineCompletionHighlight>(cx);
7180 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7181 true
7182 }
7183
7184 /// Returns true when we're displaying the edit prediction popover below the cursor
7185 /// like we are not previewing and the LSP autocomplete menu is visible
7186 /// or we are in `when_holding_modifier` mode.
7187 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7188 if self.edit_prediction_preview_is_active()
7189 || !self.show_edit_predictions_in_menu()
7190 || !self.edit_predictions_enabled()
7191 {
7192 return false;
7193 }
7194
7195 if self.has_visible_completions_menu() {
7196 return true;
7197 }
7198
7199 has_completion && self.edit_prediction_requires_modifier()
7200 }
7201
7202 fn handle_modifiers_changed(
7203 &mut self,
7204 modifiers: Modifiers,
7205 position_map: &PositionMap,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 if self.show_edit_predictions_in_menu() {
7210 self.update_edit_prediction_preview(&modifiers, window, cx);
7211 }
7212
7213 self.update_selection_mode(&modifiers, position_map, window, cx);
7214
7215 let mouse_position = window.mouse_position();
7216 if !position_map.text_hitbox.is_hovered(window) {
7217 return;
7218 }
7219
7220 self.update_hovered_link(
7221 position_map.point_for_position(mouse_position),
7222 &position_map.snapshot,
7223 modifiers,
7224 window,
7225 cx,
7226 )
7227 }
7228
7229 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7230 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7231 if invert {
7232 match multi_cursor_setting {
7233 MultiCursorModifier::Alt => modifiers.alt,
7234 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7235 }
7236 } else {
7237 match multi_cursor_setting {
7238 MultiCursorModifier::Alt => modifiers.secondary(),
7239 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7240 }
7241 }
7242 }
7243
7244 fn columnar_selection_mode(
7245 modifiers: &Modifiers,
7246 cx: &mut Context<Self>,
7247 ) -> Option<ColumnarMode> {
7248 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7249 if Self::multi_cursor_modifier(false, modifiers, cx) {
7250 Some(ColumnarMode::FromMouse)
7251 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7252 Some(ColumnarMode::FromSelection)
7253 } else {
7254 None
7255 }
7256 } else {
7257 None
7258 }
7259 }
7260
7261 fn update_selection_mode(
7262 &mut self,
7263 modifiers: &Modifiers,
7264 position_map: &PositionMap,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7269 return;
7270 };
7271 if self.selections.pending.is_none() {
7272 return;
7273 }
7274
7275 let mouse_position = window.mouse_position();
7276 let point_for_position = position_map.point_for_position(mouse_position);
7277 let position = point_for_position.previous_valid;
7278
7279 self.select(
7280 SelectPhase::BeginColumnar {
7281 position,
7282 reset: false,
7283 mode,
7284 goal_column: point_for_position.exact_unclipped.column(),
7285 },
7286 window,
7287 cx,
7288 );
7289 }
7290
7291 fn update_edit_prediction_preview(
7292 &mut self,
7293 modifiers: &Modifiers,
7294 window: &mut Window,
7295 cx: &mut Context<Self>,
7296 ) {
7297 let mut modifiers_held = false;
7298 if let Some(accept_keystroke) = self
7299 .accept_edit_prediction_keybind(false, window, cx)
7300 .keystroke()
7301 {
7302 modifiers_held = modifiers_held
7303 || (&accept_keystroke.modifiers == modifiers
7304 && accept_keystroke.modifiers.modified());
7305 };
7306 if let Some(accept_partial_keystroke) = self
7307 .accept_edit_prediction_keybind(true, window, cx)
7308 .keystroke()
7309 {
7310 modifiers_held = modifiers_held
7311 || (&accept_partial_keystroke.modifiers == modifiers
7312 && accept_partial_keystroke.modifiers.modified());
7313 }
7314
7315 if modifiers_held {
7316 if matches!(
7317 self.edit_prediction_preview,
7318 EditPredictionPreview::Inactive { .. }
7319 ) {
7320 self.edit_prediction_preview = EditPredictionPreview::Active {
7321 previous_scroll_position: None,
7322 since: Instant::now(),
7323 };
7324
7325 self.update_visible_inline_completion(window, cx);
7326 cx.notify();
7327 }
7328 } else if let EditPredictionPreview::Active {
7329 previous_scroll_position,
7330 since,
7331 } = self.edit_prediction_preview
7332 {
7333 if let (Some(previous_scroll_position), Some(position_map)) =
7334 (previous_scroll_position, self.last_position_map.as_ref())
7335 {
7336 self.set_scroll_position(
7337 previous_scroll_position
7338 .scroll_position(&position_map.snapshot.display_snapshot),
7339 window,
7340 cx,
7341 );
7342 }
7343
7344 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7345 released_too_fast: since.elapsed() < Duration::from_millis(200),
7346 };
7347 self.clear_row_highlights::<EditPredictionPreview>();
7348 self.update_visible_inline_completion(window, cx);
7349 cx.notify();
7350 }
7351 }
7352
7353 fn update_visible_inline_completion(
7354 &mut self,
7355 _window: &mut Window,
7356 cx: &mut Context<Self>,
7357 ) -> Option<()> {
7358 let selection = self.selections.newest_anchor();
7359 let cursor = selection.head();
7360 let multibuffer = self.buffer.read(cx).snapshot(cx);
7361 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7362 let excerpt_id = cursor.excerpt_id;
7363
7364 let show_in_menu = self.show_edit_predictions_in_menu();
7365 let completions_menu_has_precedence = !show_in_menu
7366 && (self.context_menu.borrow().is_some()
7367 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7368
7369 if completions_menu_has_precedence
7370 || !offset_selection.is_empty()
7371 || self
7372 .active_inline_completion
7373 .as_ref()
7374 .map_or(false, |completion| {
7375 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7376 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7377 !invalidation_range.contains(&offset_selection.head())
7378 })
7379 {
7380 self.discard_inline_completion(false, cx);
7381 return None;
7382 }
7383
7384 self.take_active_inline_completion(cx);
7385 let Some(provider) = self.edit_prediction_provider() else {
7386 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7387 return None;
7388 };
7389
7390 let (buffer, cursor_buffer_position) =
7391 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7392
7393 self.edit_prediction_settings =
7394 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7395
7396 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7397
7398 if self.edit_prediction_indent_conflict {
7399 let cursor_point = cursor.to_point(&multibuffer);
7400
7401 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7402
7403 if let Some((_, indent)) = indents.iter().next() {
7404 if indent.len == cursor_point.column {
7405 self.edit_prediction_indent_conflict = false;
7406 }
7407 }
7408 }
7409
7410 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7411 let edits = inline_completion
7412 .edits
7413 .into_iter()
7414 .flat_map(|(range, new_text)| {
7415 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7416 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7417 Some((start..end, new_text))
7418 })
7419 .collect::<Vec<_>>();
7420 if edits.is_empty() {
7421 return None;
7422 }
7423
7424 let first_edit_start = edits.first().unwrap().0.start;
7425 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7426 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7427
7428 let last_edit_end = edits.last().unwrap().0.end;
7429 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7430 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7431
7432 let cursor_row = cursor.to_point(&multibuffer).row;
7433
7434 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7435
7436 let mut inlay_ids = Vec::new();
7437 let invalidation_row_range;
7438 let move_invalidation_row_range = if cursor_row < edit_start_row {
7439 Some(cursor_row..edit_end_row)
7440 } else if cursor_row > edit_end_row {
7441 Some(edit_start_row..cursor_row)
7442 } else {
7443 None
7444 };
7445 let is_move =
7446 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7447 let completion = if is_move {
7448 invalidation_row_range =
7449 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7450 let target = first_edit_start;
7451 InlineCompletion::Move { target, snapshot }
7452 } else {
7453 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7454 && !self.inline_completions_hidden_for_vim_mode;
7455
7456 if show_completions_in_buffer {
7457 if edits
7458 .iter()
7459 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7460 {
7461 let mut inlays = Vec::new();
7462 for (range, new_text) in &edits {
7463 let inlay = Inlay::inline_completion(
7464 post_inc(&mut self.next_inlay_id),
7465 range.start,
7466 new_text.as_str(),
7467 );
7468 inlay_ids.push(inlay.id);
7469 inlays.push(inlay);
7470 }
7471
7472 self.splice_inlays(&[], inlays, cx);
7473 } else {
7474 let background_color = cx.theme().status().deleted_background;
7475 self.highlight_text::<InlineCompletionHighlight>(
7476 edits.iter().map(|(range, _)| range.clone()).collect(),
7477 HighlightStyle {
7478 background_color: Some(background_color),
7479 ..Default::default()
7480 },
7481 cx,
7482 );
7483 }
7484 }
7485
7486 invalidation_row_range = edit_start_row..edit_end_row;
7487
7488 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7489 if provider.show_tab_accept_marker() {
7490 EditDisplayMode::TabAccept
7491 } else {
7492 EditDisplayMode::Inline
7493 }
7494 } else {
7495 EditDisplayMode::DiffPopover
7496 };
7497
7498 InlineCompletion::Edit {
7499 edits,
7500 edit_preview: inline_completion.edit_preview,
7501 display_mode,
7502 snapshot,
7503 }
7504 };
7505
7506 let invalidation_range = multibuffer
7507 .anchor_before(Point::new(invalidation_row_range.start, 0))
7508 ..multibuffer.anchor_after(Point::new(
7509 invalidation_row_range.end,
7510 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7511 ));
7512
7513 self.stale_inline_completion_in_menu = None;
7514 self.active_inline_completion = Some(InlineCompletionState {
7515 inlay_ids,
7516 completion,
7517 completion_id: inline_completion.id,
7518 invalidation_range,
7519 });
7520
7521 cx.notify();
7522
7523 Some(())
7524 }
7525
7526 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7527 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7528 }
7529
7530 fn clear_tasks(&mut self) {
7531 self.tasks.clear()
7532 }
7533
7534 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7535 if self.tasks.insert(key, value).is_some() {
7536 // This case should hopefully be rare, but just in case...
7537 log::error!(
7538 "multiple different run targets found on a single line, only the last target will be rendered"
7539 )
7540 }
7541 }
7542
7543 /// Get all display points of breakpoints that will be rendered within editor
7544 ///
7545 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7546 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7547 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7548 fn active_breakpoints(
7549 &self,
7550 range: Range<DisplayRow>,
7551 window: &mut Window,
7552 cx: &mut Context<Self>,
7553 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7554 let mut breakpoint_display_points = HashMap::default();
7555
7556 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7557 return breakpoint_display_points;
7558 };
7559
7560 let snapshot = self.snapshot(window, cx);
7561
7562 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7563 let Some(project) = self.project.as_ref() else {
7564 return breakpoint_display_points;
7565 };
7566
7567 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7568 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7569
7570 for (buffer_snapshot, range, excerpt_id) in
7571 multi_buffer_snapshot.range_to_buffer_ranges(range)
7572 {
7573 let Some(buffer) = project
7574 .read(cx)
7575 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7576 else {
7577 continue;
7578 };
7579 let breakpoints = breakpoint_store.read(cx).breakpoints(
7580 &buffer,
7581 Some(
7582 buffer_snapshot.anchor_before(range.start)
7583 ..buffer_snapshot.anchor_after(range.end),
7584 ),
7585 buffer_snapshot,
7586 cx,
7587 );
7588 for (breakpoint, state) in breakpoints {
7589 let multi_buffer_anchor =
7590 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7591 let position = multi_buffer_anchor
7592 .to_point(&multi_buffer_snapshot)
7593 .to_display_point(&snapshot);
7594
7595 breakpoint_display_points.insert(
7596 position.row(),
7597 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7598 );
7599 }
7600 }
7601
7602 breakpoint_display_points
7603 }
7604
7605 fn breakpoint_context_menu(
7606 &self,
7607 anchor: Anchor,
7608 window: &mut Window,
7609 cx: &mut Context<Self>,
7610 ) -> Entity<ui::ContextMenu> {
7611 let weak_editor = cx.weak_entity();
7612 let focus_handle = self.focus_handle(cx);
7613
7614 let row = self
7615 .buffer
7616 .read(cx)
7617 .snapshot(cx)
7618 .summary_for_anchor::<Point>(&anchor)
7619 .row;
7620
7621 let breakpoint = self
7622 .breakpoint_at_row(row, window, cx)
7623 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7624
7625 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7626 "Edit Log Breakpoint"
7627 } else {
7628 "Set Log Breakpoint"
7629 };
7630
7631 let condition_breakpoint_msg = if breakpoint
7632 .as_ref()
7633 .is_some_and(|bp| bp.1.condition.is_some())
7634 {
7635 "Edit Condition Breakpoint"
7636 } else {
7637 "Set Condition Breakpoint"
7638 };
7639
7640 let hit_condition_breakpoint_msg = if breakpoint
7641 .as_ref()
7642 .is_some_and(|bp| bp.1.hit_condition.is_some())
7643 {
7644 "Edit Hit Condition Breakpoint"
7645 } else {
7646 "Set Hit Condition Breakpoint"
7647 };
7648
7649 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7650 "Unset Breakpoint"
7651 } else {
7652 "Set Breakpoint"
7653 };
7654
7655 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7656
7657 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7658 BreakpointState::Enabled => Some("Disable"),
7659 BreakpointState::Disabled => Some("Enable"),
7660 });
7661
7662 let (anchor, breakpoint) =
7663 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7664
7665 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7666 menu.on_blur_subscription(Subscription::new(|| {}))
7667 .context(focus_handle)
7668 .when(run_to_cursor, |this| {
7669 let weak_editor = weak_editor.clone();
7670 this.entry("Run to cursor", None, move |window, cx| {
7671 weak_editor
7672 .update(cx, |editor, cx| {
7673 editor.change_selections(None, window, cx, |s| {
7674 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7675 });
7676 })
7677 .ok();
7678
7679 window.dispatch_action(Box::new(RunToCursor), cx);
7680 })
7681 .separator()
7682 })
7683 .when_some(toggle_state_msg, |this, msg| {
7684 this.entry(msg, None, {
7685 let weak_editor = weak_editor.clone();
7686 let breakpoint = breakpoint.clone();
7687 move |_window, cx| {
7688 weak_editor
7689 .update(cx, |this, cx| {
7690 this.edit_breakpoint_at_anchor(
7691 anchor,
7692 breakpoint.as_ref().clone(),
7693 BreakpointEditAction::InvertState,
7694 cx,
7695 );
7696 })
7697 .log_err();
7698 }
7699 })
7700 })
7701 .entry(set_breakpoint_msg, None, {
7702 let weak_editor = weak_editor.clone();
7703 let breakpoint = breakpoint.clone();
7704 move |_window, cx| {
7705 weak_editor
7706 .update(cx, |this, cx| {
7707 this.edit_breakpoint_at_anchor(
7708 anchor,
7709 breakpoint.as_ref().clone(),
7710 BreakpointEditAction::Toggle,
7711 cx,
7712 );
7713 })
7714 .log_err();
7715 }
7716 })
7717 .entry(log_breakpoint_msg, None, {
7718 let breakpoint = breakpoint.clone();
7719 let weak_editor = weak_editor.clone();
7720 move |window, cx| {
7721 weak_editor
7722 .update(cx, |this, cx| {
7723 this.add_edit_breakpoint_block(
7724 anchor,
7725 breakpoint.as_ref(),
7726 BreakpointPromptEditAction::Log,
7727 window,
7728 cx,
7729 );
7730 })
7731 .log_err();
7732 }
7733 })
7734 .entry(condition_breakpoint_msg, None, {
7735 let breakpoint = breakpoint.clone();
7736 let weak_editor = weak_editor.clone();
7737 move |window, cx| {
7738 weak_editor
7739 .update(cx, |this, cx| {
7740 this.add_edit_breakpoint_block(
7741 anchor,
7742 breakpoint.as_ref(),
7743 BreakpointPromptEditAction::Condition,
7744 window,
7745 cx,
7746 );
7747 })
7748 .log_err();
7749 }
7750 })
7751 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7752 weak_editor
7753 .update(cx, |this, cx| {
7754 this.add_edit_breakpoint_block(
7755 anchor,
7756 breakpoint.as_ref(),
7757 BreakpointPromptEditAction::HitCondition,
7758 window,
7759 cx,
7760 );
7761 })
7762 .log_err();
7763 })
7764 })
7765 }
7766
7767 fn render_breakpoint(
7768 &self,
7769 position: Anchor,
7770 row: DisplayRow,
7771 breakpoint: &Breakpoint,
7772 state: Option<BreakpointSessionState>,
7773 cx: &mut Context<Self>,
7774 ) -> IconButton {
7775 let is_rejected = state.is_some_and(|s| !s.verified);
7776 // Is it a breakpoint that shows up when hovering over gutter?
7777 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7778 (false, false),
7779 |PhantomBreakpointIndicator {
7780 is_active,
7781 display_row,
7782 collides_with_existing_breakpoint,
7783 }| {
7784 (
7785 is_active && display_row == row,
7786 collides_with_existing_breakpoint,
7787 )
7788 },
7789 );
7790
7791 let (color, icon) = {
7792 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7793 (false, false) => ui::IconName::DebugBreakpoint,
7794 (true, false) => ui::IconName::DebugLogBreakpoint,
7795 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7796 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7797 };
7798
7799 let color = if is_phantom {
7800 Color::Hint
7801 } else if is_rejected {
7802 Color::Disabled
7803 } else {
7804 Color::Debugger
7805 };
7806
7807 (color, icon)
7808 };
7809
7810 let breakpoint = Arc::from(breakpoint.clone());
7811
7812 let alt_as_text = gpui::Keystroke {
7813 modifiers: Modifiers::secondary_key(),
7814 ..Default::default()
7815 };
7816 let primary_action_text = if breakpoint.is_disabled() {
7817 "Enable breakpoint"
7818 } else if is_phantom && !collides_with_existing {
7819 "Set breakpoint"
7820 } else {
7821 "Unset breakpoint"
7822 };
7823 let focus_handle = self.focus_handle.clone();
7824
7825 let meta = if is_rejected {
7826 SharedString::from("No executable code is associated with this line.")
7827 } else if collides_with_existing && !breakpoint.is_disabled() {
7828 SharedString::from(format!(
7829 "{alt_as_text}-click to disable,\nright-click for more options."
7830 ))
7831 } else {
7832 SharedString::from("Right-click for more options.")
7833 };
7834 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7835 .icon_size(IconSize::XSmall)
7836 .size(ui::ButtonSize::None)
7837 .when(is_rejected, |this| {
7838 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7839 })
7840 .icon_color(color)
7841 .style(ButtonStyle::Transparent)
7842 .on_click(cx.listener({
7843 let breakpoint = breakpoint.clone();
7844
7845 move |editor, event: &ClickEvent, window, cx| {
7846 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7847 BreakpointEditAction::InvertState
7848 } else {
7849 BreakpointEditAction::Toggle
7850 };
7851
7852 window.focus(&editor.focus_handle(cx));
7853 editor.edit_breakpoint_at_anchor(
7854 position,
7855 breakpoint.as_ref().clone(),
7856 edit_action,
7857 cx,
7858 );
7859 }
7860 }))
7861 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7862 editor.set_breakpoint_context_menu(
7863 row,
7864 Some(position),
7865 event.down.position,
7866 window,
7867 cx,
7868 );
7869 }))
7870 .tooltip(move |window, cx| {
7871 Tooltip::with_meta_in(
7872 primary_action_text,
7873 Some(&ToggleBreakpoint),
7874 meta.clone(),
7875 &focus_handle,
7876 window,
7877 cx,
7878 )
7879 })
7880 }
7881
7882 fn build_tasks_context(
7883 project: &Entity<Project>,
7884 buffer: &Entity<Buffer>,
7885 buffer_row: u32,
7886 tasks: &Arc<RunnableTasks>,
7887 cx: &mut Context<Self>,
7888 ) -> Task<Option<task::TaskContext>> {
7889 let position = Point::new(buffer_row, tasks.column);
7890 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7891 let location = Location {
7892 buffer: buffer.clone(),
7893 range: range_start..range_start,
7894 };
7895 // Fill in the environmental variables from the tree-sitter captures
7896 let mut captured_task_variables = TaskVariables::default();
7897 for (capture_name, value) in tasks.extra_variables.clone() {
7898 captured_task_variables.insert(
7899 task::VariableName::Custom(capture_name.into()),
7900 value.clone(),
7901 );
7902 }
7903 project.update(cx, |project, cx| {
7904 project.task_store().update(cx, |task_store, cx| {
7905 task_store.task_context_for_location(captured_task_variables, location, cx)
7906 })
7907 })
7908 }
7909
7910 pub fn spawn_nearest_task(
7911 &mut self,
7912 action: &SpawnNearestTask,
7913 window: &mut Window,
7914 cx: &mut Context<Self>,
7915 ) {
7916 let Some((workspace, _)) = self.workspace.clone() else {
7917 return;
7918 };
7919 let Some(project) = self.project.clone() else {
7920 return;
7921 };
7922
7923 // Try to find a closest, enclosing node using tree-sitter that has a
7924 // task
7925 let Some((buffer, buffer_row, tasks)) = self
7926 .find_enclosing_node_task(cx)
7927 // Or find the task that's closest in row-distance.
7928 .or_else(|| self.find_closest_task(cx))
7929 else {
7930 return;
7931 };
7932
7933 let reveal_strategy = action.reveal;
7934 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7935 cx.spawn_in(window, async move |_, cx| {
7936 let context = task_context.await?;
7937 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7938
7939 let resolved = &mut resolved_task.resolved;
7940 resolved.reveal = reveal_strategy;
7941
7942 workspace
7943 .update_in(cx, |workspace, window, cx| {
7944 workspace.schedule_resolved_task(
7945 task_source_kind,
7946 resolved_task,
7947 false,
7948 window,
7949 cx,
7950 );
7951 })
7952 .ok()
7953 })
7954 .detach();
7955 }
7956
7957 fn find_closest_task(
7958 &mut self,
7959 cx: &mut Context<Self>,
7960 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7961 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7962
7963 let ((buffer_id, row), tasks) = self
7964 .tasks
7965 .iter()
7966 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7967
7968 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7969 let tasks = Arc::new(tasks.to_owned());
7970 Some((buffer, *row, tasks))
7971 }
7972
7973 fn find_enclosing_node_task(
7974 &mut self,
7975 cx: &mut Context<Self>,
7976 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7977 let snapshot = self.buffer.read(cx).snapshot(cx);
7978 let offset = self.selections.newest::<usize>(cx).head();
7979 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7980 let buffer_id = excerpt.buffer().remote_id();
7981
7982 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7983 let mut cursor = layer.node().walk();
7984
7985 while cursor.goto_first_child_for_byte(offset).is_some() {
7986 if cursor.node().end_byte() == offset {
7987 cursor.goto_next_sibling();
7988 }
7989 }
7990
7991 // Ascend to the smallest ancestor that contains the range and has a task.
7992 loop {
7993 let node = cursor.node();
7994 let node_range = node.byte_range();
7995 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7996
7997 // Check if this node contains our offset
7998 if node_range.start <= offset && node_range.end >= offset {
7999 // If it contains offset, check for task
8000 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8001 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8002 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8003 }
8004 }
8005
8006 if !cursor.goto_parent() {
8007 break;
8008 }
8009 }
8010 None
8011 }
8012
8013 fn render_run_indicator(
8014 &self,
8015 _style: &EditorStyle,
8016 is_active: bool,
8017 row: DisplayRow,
8018 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8019 cx: &mut Context<Self>,
8020 ) -> IconButton {
8021 let color = Color::Muted;
8022 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8023
8024 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8025 .shape(ui::IconButtonShape::Square)
8026 .icon_size(IconSize::XSmall)
8027 .icon_color(color)
8028 .toggle_state(is_active)
8029 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8030 let quick_launch = e.down.button == MouseButton::Left;
8031 window.focus(&editor.focus_handle(cx));
8032 editor.toggle_code_actions(
8033 &ToggleCodeActions {
8034 deployed_from: Some(CodeActionSource::RunMenu(row)),
8035 quick_launch,
8036 },
8037 window,
8038 cx,
8039 );
8040 }))
8041 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8042 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8043 }))
8044 }
8045
8046 pub fn context_menu_visible(&self) -> bool {
8047 !self.edit_prediction_preview_is_active()
8048 && self
8049 .context_menu
8050 .borrow()
8051 .as_ref()
8052 .map_or(false, |menu| menu.visible())
8053 }
8054
8055 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8056 self.context_menu
8057 .borrow()
8058 .as_ref()
8059 .map(|menu| menu.origin())
8060 }
8061
8062 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8063 self.context_menu_options = Some(options);
8064 }
8065
8066 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8067 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8068
8069 fn render_edit_prediction_popover(
8070 &mut self,
8071 text_bounds: &Bounds<Pixels>,
8072 content_origin: gpui::Point<Pixels>,
8073 right_margin: Pixels,
8074 editor_snapshot: &EditorSnapshot,
8075 visible_row_range: Range<DisplayRow>,
8076 scroll_top: f32,
8077 scroll_bottom: f32,
8078 line_layouts: &[LineWithInvisibles],
8079 line_height: Pixels,
8080 scroll_pixel_position: gpui::Point<Pixels>,
8081 newest_selection_head: Option<DisplayPoint>,
8082 editor_width: Pixels,
8083 style: &EditorStyle,
8084 window: &mut Window,
8085 cx: &mut App,
8086 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8087 if self.mode().is_minimap() {
8088 return None;
8089 }
8090 let active_inline_completion = self.active_inline_completion.as_ref()?;
8091
8092 if self.edit_prediction_visible_in_cursor_popover(true) {
8093 return None;
8094 }
8095
8096 match &active_inline_completion.completion {
8097 InlineCompletion::Move { target, .. } => {
8098 let target_display_point = target.to_display_point(editor_snapshot);
8099
8100 if self.edit_prediction_requires_modifier() {
8101 if !self.edit_prediction_preview_is_active() {
8102 return None;
8103 }
8104
8105 self.render_edit_prediction_modifier_jump_popover(
8106 text_bounds,
8107 content_origin,
8108 visible_row_range,
8109 line_layouts,
8110 line_height,
8111 scroll_pixel_position,
8112 newest_selection_head,
8113 target_display_point,
8114 window,
8115 cx,
8116 )
8117 } else {
8118 self.render_edit_prediction_eager_jump_popover(
8119 text_bounds,
8120 content_origin,
8121 editor_snapshot,
8122 visible_row_range,
8123 scroll_top,
8124 scroll_bottom,
8125 line_height,
8126 scroll_pixel_position,
8127 target_display_point,
8128 editor_width,
8129 window,
8130 cx,
8131 )
8132 }
8133 }
8134 InlineCompletion::Edit {
8135 display_mode: EditDisplayMode::Inline,
8136 ..
8137 } => None,
8138 InlineCompletion::Edit {
8139 display_mode: EditDisplayMode::TabAccept,
8140 edits,
8141 ..
8142 } => {
8143 let range = &edits.first()?.0;
8144 let target_display_point = range.end.to_display_point(editor_snapshot);
8145
8146 self.render_edit_prediction_end_of_line_popover(
8147 "Accept",
8148 editor_snapshot,
8149 visible_row_range,
8150 target_display_point,
8151 line_height,
8152 scroll_pixel_position,
8153 content_origin,
8154 editor_width,
8155 window,
8156 cx,
8157 )
8158 }
8159 InlineCompletion::Edit {
8160 edits,
8161 edit_preview,
8162 display_mode: EditDisplayMode::DiffPopover,
8163 snapshot,
8164 } => self.render_edit_prediction_diff_popover(
8165 text_bounds,
8166 content_origin,
8167 right_margin,
8168 editor_snapshot,
8169 visible_row_range,
8170 line_layouts,
8171 line_height,
8172 scroll_pixel_position,
8173 newest_selection_head,
8174 editor_width,
8175 style,
8176 edits,
8177 edit_preview,
8178 snapshot,
8179 window,
8180 cx,
8181 ),
8182 }
8183 }
8184
8185 fn render_edit_prediction_modifier_jump_popover(
8186 &mut self,
8187 text_bounds: &Bounds<Pixels>,
8188 content_origin: gpui::Point<Pixels>,
8189 visible_row_range: Range<DisplayRow>,
8190 line_layouts: &[LineWithInvisibles],
8191 line_height: Pixels,
8192 scroll_pixel_position: gpui::Point<Pixels>,
8193 newest_selection_head: Option<DisplayPoint>,
8194 target_display_point: DisplayPoint,
8195 window: &mut Window,
8196 cx: &mut App,
8197 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8198 let scrolled_content_origin =
8199 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8200
8201 const SCROLL_PADDING_Y: Pixels = px(12.);
8202
8203 if target_display_point.row() < visible_row_range.start {
8204 return self.render_edit_prediction_scroll_popover(
8205 |_| SCROLL_PADDING_Y,
8206 IconName::ArrowUp,
8207 visible_row_range,
8208 line_layouts,
8209 newest_selection_head,
8210 scrolled_content_origin,
8211 window,
8212 cx,
8213 );
8214 } else if target_display_point.row() >= visible_row_range.end {
8215 return self.render_edit_prediction_scroll_popover(
8216 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8217 IconName::ArrowDown,
8218 visible_row_range,
8219 line_layouts,
8220 newest_selection_head,
8221 scrolled_content_origin,
8222 window,
8223 cx,
8224 );
8225 }
8226
8227 const POLE_WIDTH: Pixels = px(2.);
8228
8229 let line_layout =
8230 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8231 let target_column = target_display_point.column() as usize;
8232
8233 let target_x = line_layout.x_for_index(target_column);
8234 let target_y =
8235 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8236
8237 let flag_on_right = target_x < text_bounds.size.width / 2.;
8238
8239 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8240 border_color.l += 0.001;
8241
8242 let mut element = v_flex()
8243 .items_end()
8244 .when(flag_on_right, |el| el.items_start())
8245 .child(if flag_on_right {
8246 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8247 .rounded_bl(px(0.))
8248 .rounded_tl(px(0.))
8249 .border_l_2()
8250 .border_color(border_color)
8251 } else {
8252 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8253 .rounded_br(px(0.))
8254 .rounded_tr(px(0.))
8255 .border_r_2()
8256 .border_color(border_color)
8257 })
8258 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8259 .into_any();
8260
8261 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8262
8263 let mut origin = scrolled_content_origin + point(target_x, target_y)
8264 - point(
8265 if flag_on_right {
8266 POLE_WIDTH
8267 } else {
8268 size.width - POLE_WIDTH
8269 },
8270 size.height - line_height,
8271 );
8272
8273 origin.x = origin.x.max(content_origin.x);
8274
8275 element.prepaint_at(origin, window, cx);
8276
8277 Some((element, origin))
8278 }
8279
8280 fn render_edit_prediction_scroll_popover(
8281 &mut self,
8282 to_y: impl Fn(Size<Pixels>) -> Pixels,
8283 scroll_icon: IconName,
8284 visible_row_range: Range<DisplayRow>,
8285 line_layouts: &[LineWithInvisibles],
8286 newest_selection_head: Option<DisplayPoint>,
8287 scrolled_content_origin: gpui::Point<Pixels>,
8288 window: &mut Window,
8289 cx: &mut App,
8290 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8291 let mut element = self
8292 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8293 .into_any();
8294
8295 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8296
8297 let cursor = newest_selection_head?;
8298 let cursor_row_layout =
8299 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8300 let cursor_column = cursor.column() as usize;
8301
8302 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8303
8304 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8305
8306 element.prepaint_at(origin, window, cx);
8307 Some((element, origin))
8308 }
8309
8310 fn render_edit_prediction_eager_jump_popover(
8311 &mut self,
8312 text_bounds: &Bounds<Pixels>,
8313 content_origin: gpui::Point<Pixels>,
8314 editor_snapshot: &EditorSnapshot,
8315 visible_row_range: Range<DisplayRow>,
8316 scroll_top: f32,
8317 scroll_bottom: f32,
8318 line_height: Pixels,
8319 scroll_pixel_position: gpui::Point<Pixels>,
8320 target_display_point: DisplayPoint,
8321 editor_width: Pixels,
8322 window: &mut Window,
8323 cx: &mut App,
8324 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8325 if target_display_point.row().as_f32() < scroll_top {
8326 let mut element = self
8327 .render_edit_prediction_line_popover(
8328 "Jump to Edit",
8329 Some(IconName::ArrowUp),
8330 window,
8331 cx,
8332 )?
8333 .into_any();
8334
8335 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8336 let offset = point(
8337 (text_bounds.size.width - size.width) / 2.,
8338 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8339 );
8340
8341 let origin = text_bounds.origin + offset;
8342 element.prepaint_at(origin, window, cx);
8343 Some((element, origin))
8344 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8345 let mut element = self
8346 .render_edit_prediction_line_popover(
8347 "Jump to Edit",
8348 Some(IconName::ArrowDown),
8349 window,
8350 cx,
8351 )?
8352 .into_any();
8353
8354 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8355 let offset = point(
8356 (text_bounds.size.width - size.width) / 2.,
8357 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8358 );
8359
8360 let origin = text_bounds.origin + offset;
8361 element.prepaint_at(origin, window, cx);
8362 Some((element, origin))
8363 } else {
8364 self.render_edit_prediction_end_of_line_popover(
8365 "Jump to Edit",
8366 editor_snapshot,
8367 visible_row_range,
8368 target_display_point,
8369 line_height,
8370 scroll_pixel_position,
8371 content_origin,
8372 editor_width,
8373 window,
8374 cx,
8375 )
8376 }
8377 }
8378
8379 fn render_edit_prediction_end_of_line_popover(
8380 self: &mut Editor,
8381 label: &'static str,
8382 editor_snapshot: &EditorSnapshot,
8383 visible_row_range: Range<DisplayRow>,
8384 target_display_point: DisplayPoint,
8385 line_height: Pixels,
8386 scroll_pixel_position: gpui::Point<Pixels>,
8387 content_origin: gpui::Point<Pixels>,
8388 editor_width: Pixels,
8389 window: &mut Window,
8390 cx: &mut App,
8391 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8392 let target_line_end = DisplayPoint::new(
8393 target_display_point.row(),
8394 editor_snapshot.line_len(target_display_point.row()),
8395 );
8396
8397 let mut element = self
8398 .render_edit_prediction_line_popover(label, None, window, cx)?
8399 .into_any();
8400
8401 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8402
8403 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8404
8405 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8406 let mut origin = start_point
8407 + line_origin
8408 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8409 origin.x = origin.x.max(content_origin.x);
8410
8411 let max_x = content_origin.x + editor_width - size.width;
8412
8413 if origin.x > max_x {
8414 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8415
8416 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8417 origin.y += offset;
8418 IconName::ArrowUp
8419 } else {
8420 origin.y -= offset;
8421 IconName::ArrowDown
8422 };
8423
8424 element = self
8425 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8426 .into_any();
8427
8428 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8429
8430 origin.x = content_origin.x + editor_width - size.width - px(2.);
8431 }
8432
8433 element.prepaint_at(origin, window, cx);
8434 Some((element, origin))
8435 }
8436
8437 fn render_edit_prediction_diff_popover(
8438 self: &Editor,
8439 text_bounds: &Bounds<Pixels>,
8440 content_origin: gpui::Point<Pixels>,
8441 right_margin: Pixels,
8442 editor_snapshot: &EditorSnapshot,
8443 visible_row_range: Range<DisplayRow>,
8444 line_layouts: &[LineWithInvisibles],
8445 line_height: Pixels,
8446 scroll_pixel_position: gpui::Point<Pixels>,
8447 newest_selection_head: Option<DisplayPoint>,
8448 editor_width: Pixels,
8449 style: &EditorStyle,
8450 edits: &Vec<(Range<Anchor>, String)>,
8451 edit_preview: &Option<language::EditPreview>,
8452 snapshot: &language::BufferSnapshot,
8453 window: &mut Window,
8454 cx: &mut App,
8455 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8456 let edit_start = edits
8457 .first()
8458 .unwrap()
8459 .0
8460 .start
8461 .to_display_point(editor_snapshot);
8462 let edit_end = edits
8463 .last()
8464 .unwrap()
8465 .0
8466 .end
8467 .to_display_point(editor_snapshot);
8468
8469 let is_visible = visible_row_range.contains(&edit_start.row())
8470 || visible_row_range.contains(&edit_end.row());
8471 if !is_visible {
8472 return None;
8473 }
8474
8475 let highlighted_edits =
8476 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8477
8478 let styled_text = highlighted_edits.to_styled_text(&style.text);
8479 let line_count = highlighted_edits.text.lines().count();
8480
8481 const BORDER_WIDTH: Pixels = px(1.);
8482
8483 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8484 let has_keybind = keybind.is_some();
8485
8486 let mut element = h_flex()
8487 .items_start()
8488 .child(
8489 h_flex()
8490 .bg(cx.theme().colors().editor_background)
8491 .border(BORDER_WIDTH)
8492 .shadow_sm()
8493 .border_color(cx.theme().colors().border)
8494 .rounded_l_lg()
8495 .when(line_count > 1, |el| el.rounded_br_lg())
8496 .pr_1()
8497 .child(styled_text),
8498 )
8499 .child(
8500 h_flex()
8501 .h(line_height + BORDER_WIDTH * 2.)
8502 .px_1p5()
8503 .gap_1()
8504 // Workaround: For some reason, there's a gap if we don't do this
8505 .ml(-BORDER_WIDTH)
8506 .shadow(vec![gpui::BoxShadow {
8507 color: gpui::black().opacity(0.05),
8508 offset: point(px(1.), px(1.)),
8509 blur_radius: px(2.),
8510 spread_radius: px(0.),
8511 }])
8512 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8513 .border(BORDER_WIDTH)
8514 .border_color(cx.theme().colors().border)
8515 .rounded_r_lg()
8516 .id("edit_prediction_diff_popover_keybind")
8517 .when(!has_keybind, |el| {
8518 let status_colors = cx.theme().status();
8519
8520 el.bg(status_colors.error_background)
8521 .border_color(status_colors.error.opacity(0.6))
8522 .child(Icon::new(IconName::Info).color(Color::Error))
8523 .cursor_default()
8524 .hoverable_tooltip(move |_window, cx| {
8525 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8526 })
8527 })
8528 .children(keybind),
8529 )
8530 .into_any();
8531
8532 let longest_row =
8533 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8534 let longest_line_width = if visible_row_range.contains(&longest_row) {
8535 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8536 } else {
8537 layout_line(
8538 longest_row,
8539 editor_snapshot,
8540 style,
8541 editor_width,
8542 |_| false,
8543 window,
8544 cx,
8545 )
8546 .width
8547 };
8548
8549 let viewport_bounds =
8550 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8551 right: -right_margin,
8552 ..Default::default()
8553 });
8554
8555 let x_after_longest =
8556 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8557 - scroll_pixel_position.x;
8558
8559 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8560
8561 // Fully visible if it can be displayed within the window (allow overlapping other
8562 // panes). However, this is only allowed if the popover starts within text_bounds.
8563 let can_position_to_the_right = x_after_longest < text_bounds.right()
8564 && x_after_longest + element_bounds.width < viewport_bounds.right();
8565
8566 let mut origin = if can_position_to_the_right {
8567 point(
8568 x_after_longest,
8569 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8570 - scroll_pixel_position.y,
8571 )
8572 } else {
8573 let cursor_row = newest_selection_head.map(|head| head.row());
8574 let above_edit = edit_start
8575 .row()
8576 .0
8577 .checked_sub(line_count as u32)
8578 .map(DisplayRow);
8579 let below_edit = Some(edit_end.row() + 1);
8580 let above_cursor =
8581 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8582 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8583
8584 // Place the edit popover adjacent to the edit if there is a location
8585 // available that is onscreen and does not obscure the cursor. Otherwise,
8586 // place it adjacent to the cursor.
8587 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8588 .into_iter()
8589 .flatten()
8590 .find(|&start_row| {
8591 let end_row = start_row + line_count as u32;
8592 visible_row_range.contains(&start_row)
8593 && visible_row_range.contains(&end_row)
8594 && cursor_row.map_or(true, |cursor_row| {
8595 !((start_row..end_row).contains(&cursor_row))
8596 })
8597 })?;
8598
8599 content_origin
8600 + point(
8601 -scroll_pixel_position.x,
8602 row_target.as_f32() * line_height - scroll_pixel_position.y,
8603 )
8604 };
8605
8606 origin.x -= BORDER_WIDTH;
8607
8608 window.defer_draw(element, origin, 1);
8609
8610 // Do not return an element, since it will already be drawn due to defer_draw.
8611 None
8612 }
8613
8614 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8615 px(30.)
8616 }
8617
8618 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8619 if self.read_only(cx) {
8620 cx.theme().players().read_only()
8621 } else {
8622 self.style.as_ref().unwrap().local_player
8623 }
8624 }
8625
8626 fn render_edit_prediction_accept_keybind(
8627 &self,
8628 window: &mut Window,
8629 cx: &App,
8630 ) -> Option<AnyElement> {
8631 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8632 let accept_keystroke = accept_binding.keystroke()?;
8633
8634 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8635
8636 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8637 Color::Accent
8638 } else {
8639 Color::Muted
8640 };
8641
8642 h_flex()
8643 .px_0p5()
8644 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8645 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8646 .text_size(TextSize::XSmall.rems(cx))
8647 .child(h_flex().children(ui::render_modifiers(
8648 &accept_keystroke.modifiers,
8649 PlatformStyle::platform(),
8650 Some(modifiers_color),
8651 Some(IconSize::XSmall.rems().into()),
8652 true,
8653 )))
8654 .when(is_platform_style_mac, |parent| {
8655 parent.child(accept_keystroke.key.clone())
8656 })
8657 .when(!is_platform_style_mac, |parent| {
8658 parent.child(
8659 Key::new(
8660 util::capitalize(&accept_keystroke.key),
8661 Some(Color::Default),
8662 )
8663 .size(Some(IconSize::XSmall.rems().into())),
8664 )
8665 })
8666 .into_any()
8667 .into()
8668 }
8669
8670 fn render_edit_prediction_line_popover(
8671 &self,
8672 label: impl Into<SharedString>,
8673 icon: Option<IconName>,
8674 window: &mut Window,
8675 cx: &App,
8676 ) -> Option<Stateful<Div>> {
8677 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8678
8679 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8680 let has_keybind = keybind.is_some();
8681
8682 let result = h_flex()
8683 .id("ep-line-popover")
8684 .py_0p5()
8685 .pl_1()
8686 .pr(padding_right)
8687 .gap_1()
8688 .rounded_md()
8689 .border_1()
8690 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8691 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8692 .shadow_sm()
8693 .when(!has_keybind, |el| {
8694 let status_colors = cx.theme().status();
8695
8696 el.bg(status_colors.error_background)
8697 .border_color(status_colors.error.opacity(0.6))
8698 .pl_2()
8699 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8700 .cursor_default()
8701 .hoverable_tooltip(move |_window, cx| {
8702 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8703 })
8704 })
8705 .children(keybind)
8706 .child(
8707 Label::new(label)
8708 .size(LabelSize::Small)
8709 .when(!has_keybind, |el| {
8710 el.color(cx.theme().status().error.into()).strikethrough()
8711 }),
8712 )
8713 .when(!has_keybind, |el| {
8714 el.child(
8715 h_flex().ml_1().child(
8716 Icon::new(IconName::Info)
8717 .size(IconSize::Small)
8718 .color(cx.theme().status().error.into()),
8719 ),
8720 )
8721 })
8722 .when_some(icon, |element, icon| {
8723 element.child(
8724 div()
8725 .mt(px(1.5))
8726 .child(Icon::new(icon).size(IconSize::Small)),
8727 )
8728 });
8729
8730 Some(result)
8731 }
8732
8733 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8734 let accent_color = cx.theme().colors().text_accent;
8735 let editor_bg_color = cx.theme().colors().editor_background;
8736 editor_bg_color.blend(accent_color.opacity(0.1))
8737 }
8738
8739 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8740 let accent_color = cx.theme().colors().text_accent;
8741 let editor_bg_color = cx.theme().colors().editor_background;
8742 editor_bg_color.blend(accent_color.opacity(0.6))
8743 }
8744
8745 fn render_edit_prediction_cursor_popover(
8746 &self,
8747 min_width: Pixels,
8748 max_width: Pixels,
8749 cursor_point: Point,
8750 style: &EditorStyle,
8751 accept_keystroke: Option<&gpui::Keystroke>,
8752 _window: &Window,
8753 cx: &mut Context<Editor>,
8754 ) -> Option<AnyElement> {
8755 let provider = self.edit_prediction_provider.as_ref()?;
8756
8757 if provider.provider.needs_terms_acceptance(cx) {
8758 return Some(
8759 h_flex()
8760 .min_w(min_width)
8761 .flex_1()
8762 .px_2()
8763 .py_1()
8764 .gap_3()
8765 .elevation_2(cx)
8766 .hover(|style| style.bg(cx.theme().colors().element_hover))
8767 .id("accept-terms")
8768 .cursor_pointer()
8769 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8770 .on_click(cx.listener(|this, _event, window, cx| {
8771 cx.stop_propagation();
8772 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8773 window.dispatch_action(
8774 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8775 cx,
8776 );
8777 }))
8778 .child(
8779 h_flex()
8780 .flex_1()
8781 .gap_2()
8782 .child(Icon::new(IconName::ZedPredict))
8783 .child(Label::new("Accept Terms of Service"))
8784 .child(div().w_full())
8785 .child(
8786 Icon::new(IconName::ArrowUpRight)
8787 .color(Color::Muted)
8788 .size(IconSize::Small),
8789 )
8790 .into_any_element(),
8791 )
8792 .into_any(),
8793 );
8794 }
8795
8796 let is_refreshing = provider.provider.is_refreshing(cx);
8797
8798 fn pending_completion_container() -> Div {
8799 h_flex()
8800 .h_full()
8801 .flex_1()
8802 .gap_2()
8803 .child(Icon::new(IconName::ZedPredict))
8804 }
8805
8806 let completion = match &self.active_inline_completion {
8807 Some(prediction) => {
8808 if !self.has_visible_completions_menu() {
8809 const RADIUS: Pixels = px(6.);
8810 const BORDER_WIDTH: Pixels = px(1.);
8811
8812 return Some(
8813 h_flex()
8814 .elevation_2(cx)
8815 .border(BORDER_WIDTH)
8816 .border_color(cx.theme().colors().border)
8817 .when(accept_keystroke.is_none(), |el| {
8818 el.border_color(cx.theme().status().error)
8819 })
8820 .rounded(RADIUS)
8821 .rounded_tl(px(0.))
8822 .overflow_hidden()
8823 .child(div().px_1p5().child(match &prediction.completion {
8824 InlineCompletion::Move { target, snapshot } => {
8825 use text::ToPoint as _;
8826 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8827 {
8828 Icon::new(IconName::ZedPredictDown)
8829 } else {
8830 Icon::new(IconName::ZedPredictUp)
8831 }
8832 }
8833 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8834 }))
8835 .child(
8836 h_flex()
8837 .gap_1()
8838 .py_1()
8839 .px_2()
8840 .rounded_r(RADIUS - BORDER_WIDTH)
8841 .border_l_1()
8842 .border_color(cx.theme().colors().border)
8843 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8844 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8845 el.child(
8846 Label::new("Hold")
8847 .size(LabelSize::Small)
8848 .when(accept_keystroke.is_none(), |el| {
8849 el.strikethrough()
8850 })
8851 .line_height_style(LineHeightStyle::UiLabel),
8852 )
8853 })
8854 .id("edit_prediction_cursor_popover_keybind")
8855 .when(accept_keystroke.is_none(), |el| {
8856 let status_colors = cx.theme().status();
8857
8858 el.bg(status_colors.error_background)
8859 .border_color(status_colors.error.opacity(0.6))
8860 .child(Icon::new(IconName::Info).color(Color::Error))
8861 .cursor_default()
8862 .hoverable_tooltip(move |_window, cx| {
8863 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8864 .into()
8865 })
8866 })
8867 .when_some(
8868 accept_keystroke.as_ref(),
8869 |el, accept_keystroke| {
8870 el.child(h_flex().children(ui::render_modifiers(
8871 &accept_keystroke.modifiers,
8872 PlatformStyle::platform(),
8873 Some(Color::Default),
8874 Some(IconSize::XSmall.rems().into()),
8875 false,
8876 )))
8877 },
8878 ),
8879 )
8880 .into_any(),
8881 );
8882 }
8883
8884 self.render_edit_prediction_cursor_popover_preview(
8885 prediction,
8886 cursor_point,
8887 style,
8888 cx,
8889 )?
8890 }
8891
8892 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8893 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8894 stale_completion,
8895 cursor_point,
8896 style,
8897 cx,
8898 )?,
8899
8900 None => {
8901 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8902 }
8903 },
8904
8905 None => pending_completion_container().child(Label::new("No Prediction")),
8906 };
8907
8908 let completion = if is_refreshing {
8909 completion
8910 .with_animation(
8911 "loading-completion",
8912 Animation::new(Duration::from_secs(2))
8913 .repeat()
8914 .with_easing(pulsating_between(0.4, 0.8)),
8915 |label, delta| label.opacity(delta),
8916 )
8917 .into_any_element()
8918 } else {
8919 completion.into_any_element()
8920 };
8921
8922 let has_completion = self.active_inline_completion.is_some();
8923
8924 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8925 Some(
8926 h_flex()
8927 .min_w(min_width)
8928 .max_w(max_width)
8929 .flex_1()
8930 .elevation_2(cx)
8931 .border_color(cx.theme().colors().border)
8932 .child(
8933 div()
8934 .flex_1()
8935 .py_1()
8936 .px_2()
8937 .overflow_hidden()
8938 .child(completion),
8939 )
8940 .when_some(accept_keystroke, |el, accept_keystroke| {
8941 if !accept_keystroke.modifiers.modified() {
8942 return el;
8943 }
8944
8945 el.child(
8946 h_flex()
8947 .h_full()
8948 .border_l_1()
8949 .rounded_r_lg()
8950 .border_color(cx.theme().colors().border)
8951 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8952 .gap_1()
8953 .py_1()
8954 .px_2()
8955 .child(
8956 h_flex()
8957 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8958 .when(is_platform_style_mac, |parent| parent.gap_1())
8959 .child(h_flex().children(ui::render_modifiers(
8960 &accept_keystroke.modifiers,
8961 PlatformStyle::platform(),
8962 Some(if !has_completion {
8963 Color::Muted
8964 } else {
8965 Color::Default
8966 }),
8967 None,
8968 false,
8969 ))),
8970 )
8971 .child(Label::new("Preview").into_any_element())
8972 .opacity(if has_completion { 1.0 } else { 0.4 }),
8973 )
8974 })
8975 .into_any(),
8976 )
8977 }
8978
8979 fn render_edit_prediction_cursor_popover_preview(
8980 &self,
8981 completion: &InlineCompletionState,
8982 cursor_point: Point,
8983 style: &EditorStyle,
8984 cx: &mut Context<Editor>,
8985 ) -> Option<Div> {
8986 use text::ToPoint as _;
8987
8988 fn render_relative_row_jump(
8989 prefix: impl Into<String>,
8990 current_row: u32,
8991 target_row: u32,
8992 ) -> Div {
8993 let (row_diff, arrow) = if target_row < current_row {
8994 (current_row - target_row, IconName::ArrowUp)
8995 } else {
8996 (target_row - current_row, IconName::ArrowDown)
8997 };
8998
8999 h_flex()
9000 .child(
9001 Label::new(format!("{}{}", prefix.into(), row_diff))
9002 .color(Color::Muted)
9003 .size(LabelSize::Small),
9004 )
9005 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9006 }
9007
9008 match &completion.completion {
9009 InlineCompletion::Move {
9010 target, snapshot, ..
9011 } => Some(
9012 h_flex()
9013 .px_2()
9014 .gap_2()
9015 .flex_1()
9016 .child(
9017 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9018 Icon::new(IconName::ZedPredictDown)
9019 } else {
9020 Icon::new(IconName::ZedPredictUp)
9021 },
9022 )
9023 .child(Label::new("Jump to Edit")),
9024 ),
9025
9026 InlineCompletion::Edit {
9027 edits,
9028 edit_preview,
9029 snapshot,
9030 display_mode: _,
9031 } => {
9032 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9033
9034 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9035 &snapshot,
9036 &edits,
9037 edit_preview.as_ref()?,
9038 true,
9039 cx,
9040 )
9041 .first_line_preview();
9042
9043 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9044 .with_default_highlights(&style.text, highlighted_edits.highlights);
9045
9046 let preview = h_flex()
9047 .gap_1()
9048 .min_w_16()
9049 .child(styled_text)
9050 .when(has_more_lines, |parent| parent.child("…"));
9051
9052 let left = if first_edit_row != cursor_point.row {
9053 render_relative_row_jump("", cursor_point.row, first_edit_row)
9054 .into_any_element()
9055 } else {
9056 Icon::new(IconName::ZedPredict).into_any_element()
9057 };
9058
9059 Some(
9060 h_flex()
9061 .h_full()
9062 .flex_1()
9063 .gap_2()
9064 .pr_1()
9065 .overflow_x_hidden()
9066 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9067 .child(left)
9068 .child(preview),
9069 )
9070 }
9071 }
9072 }
9073
9074 pub fn render_context_menu(
9075 &self,
9076 style: &EditorStyle,
9077 max_height_in_lines: u32,
9078 window: &mut Window,
9079 cx: &mut Context<Editor>,
9080 ) -> Option<AnyElement> {
9081 let menu = self.context_menu.borrow();
9082 let menu = menu.as_ref()?;
9083 if !menu.visible() {
9084 return None;
9085 };
9086 Some(menu.render(style, max_height_in_lines, window, cx))
9087 }
9088
9089 fn render_context_menu_aside(
9090 &mut self,
9091 max_size: Size<Pixels>,
9092 window: &mut Window,
9093 cx: &mut Context<Editor>,
9094 ) -> Option<AnyElement> {
9095 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9096 if menu.visible() {
9097 menu.render_aside(max_size, window, cx)
9098 } else {
9099 None
9100 }
9101 })
9102 }
9103
9104 fn hide_context_menu(
9105 &mut self,
9106 window: &mut Window,
9107 cx: &mut Context<Self>,
9108 ) -> Option<CodeContextMenu> {
9109 cx.notify();
9110 self.completion_tasks.clear();
9111 let context_menu = self.context_menu.borrow_mut().take();
9112 self.stale_inline_completion_in_menu.take();
9113 self.update_visible_inline_completion(window, cx);
9114 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9115 if let Some(completion_provider) = &self.completion_provider {
9116 completion_provider.selection_changed(None, window, cx);
9117 }
9118 }
9119 context_menu
9120 }
9121
9122 fn show_snippet_choices(
9123 &mut self,
9124 choices: &Vec<String>,
9125 selection: Range<Anchor>,
9126 cx: &mut Context<Self>,
9127 ) {
9128 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9129 (Some(a), Some(b)) if a == b => a,
9130 _ => {
9131 log::error!("expected anchor range to have matching buffer IDs");
9132 return;
9133 }
9134 };
9135 let multi_buffer = self.buffer().read(cx);
9136 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9137 return;
9138 };
9139
9140 let id = post_inc(&mut self.next_completion_id);
9141 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9142 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9143 CompletionsMenu::new_snippet_choices(
9144 id,
9145 true,
9146 choices,
9147 selection,
9148 buffer,
9149 snippet_sort_order,
9150 ),
9151 ));
9152 }
9153
9154 pub fn insert_snippet(
9155 &mut self,
9156 insertion_ranges: &[Range<usize>],
9157 snippet: Snippet,
9158 window: &mut Window,
9159 cx: &mut Context<Self>,
9160 ) -> Result<()> {
9161 struct Tabstop<T> {
9162 is_end_tabstop: bool,
9163 ranges: Vec<Range<T>>,
9164 choices: Option<Vec<String>>,
9165 }
9166
9167 let tabstops = self.buffer.update(cx, |buffer, cx| {
9168 let snippet_text: Arc<str> = snippet.text.clone().into();
9169 let edits = insertion_ranges
9170 .iter()
9171 .cloned()
9172 .map(|range| (range, snippet_text.clone()));
9173 let autoindent_mode = AutoindentMode::Block {
9174 original_indent_columns: Vec::new(),
9175 };
9176 buffer.edit(edits, Some(autoindent_mode), cx);
9177
9178 let snapshot = &*buffer.read(cx);
9179 let snippet = &snippet;
9180 snippet
9181 .tabstops
9182 .iter()
9183 .map(|tabstop| {
9184 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9185 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9186 });
9187 let mut tabstop_ranges = tabstop
9188 .ranges
9189 .iter()
9190 .flat_map(|tabstop_range| {
9191 let mut delta = 0_isize;
9192 insertion_ranges.iter().map(move |insertion_range| {
9193 let insertion_start = insertion_range.start as isize + delta;
9194 delta +=
9195 snippet.text.len() as isize - insertion_range.len() as isize;
9196
9197 let start = ((insertion_start + tabstop_range.start) as usize)
9198 .min(snapshot.len());
9199 let end = ((insertion_start + tabstop_range.end) as usize)
9200 .min(snapshot.len());
9201 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9202 })
9203 })
9204 .collect::<Vec<_>>();
9205 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9206
9207 Tabstop {
9208 is_end_tabstop,
9209 ranges: tabstop_ranges,
9210 choices: tabstop.choices.clone(),
9211 }
9212 })
9213 .collect::<Vec<_>>()
9214 });
9215 if let Some(tabstop) = tabstops.first() {
9216 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9217 // Reverse order so that the first range is the newest created selection.
9218 // Completions will use it and autoscroll will prioritize it.
9219 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9220 });
9221
9222 if let Some(choices) = &tabstop.choices {
9223 if let Some(selection) = tabstop.ranges.first() {
9224 self.show_snippet_choices(choices, selection.clone(), cx)
9225 }
9226 }
9227
9228 // If we're already at the last tabstop and it's at the end of the snippet,
9229 // we're done, we don't need to keep the state around.
9230 if !tabstop.is_end_tabstop {
9231 let choices = tabstops
9232 .iter()
9233 .map(|tabstop| tabstop.choices.clone())
9234 .collect();
9235
9236 let ranges = tabstops
9237 .into_iter()
9238 .map(|tabstop| tabstop.ranges)
9239 .collect::<Vec<_>>();
9240
9241 self.snippet_stack.push(SnippetState {
9242 active_index: 0,
9243 ranges,
9244 choices,
9245 });
9246 }
9247
9248 // Check whether the just-entered snippet ends with an auto-closable bracket.
9249 if self.autoclose_regions.is_empty() {
9250 let snapshot = self.buffer.read(cx).snapshot(cx);
9251 for selection in &mut self.selections.all::<Point>(cx) {
9252 let selection_head = selection.head();
9253 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9254 continue;
9255 };
9256
9257 let mut bracket_pair = None;
9258 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9259 let prev_chars = snapshot
9260 .reversed_chars_at(selection_head)
9261 .collect::<String>();
9262 for (pair, enabled) in scope.brackets() {
9263 if enabled
9264 && pair.close
9265 && prev_chars.starts_with(pair.start.as_str())
9266 && next_chars.starts_with(pair.end.as_str())
9267 {
9268 bracket_pair = Some(pair.clone());
9269 break;
9270 }
9271 }
9272 if let Some(pair) = bracket_pair {
9273 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9274 let autoclose_enabled =
9275 self.use_autoclose && snapshot_settings.use_autoclose;
9276 if autoclose_enabled {
9277 let start = snapshot.anchor_after(selection_head);
9278 let end = snapshot.anchor_after(selection_head);
9279 self.autoclose_regions.push(AutocloseRegion {
9280 selection_id: selection.id,
9281 range: start..end,
9282 pair,
9283 });
9284 }
9285 }
9286 }
9287 }
9288 }
9289 Ok(())
9290 }
9291
9292 pub fn move_to_next_snippet_tabstop(
9293 &mut self,
9294 window: &mut Window,
9295 cx: &mut Context<Self>,
9296 ) -> bool {
9297 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9298 }
9299
9300 pub fn move_to_prev_snippet_tabstop(
9301 &mut self,
9302 window: &mut Window,
9303 cx: &mut Context<Self>,
9304 ) -> bool {
9305 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9306 }
9307
9308 pub fn move_to_snippet_tabstop(
9309 &mut self,
9310 bias: Bias,
9311 window: &mut Window,
9312 cx: &mut Context<Self>,
9313 ) -> bool {
9314 if let Some(mut snippet) = self.snippet_stack.pop() {
9315 match bias {
9316 Bias::Left => {
9317 if snippet.active_index > 0 {
9318 snippet.active_index -= 1;
9319 } else {
9320 self.snippet_stack.push(snippet);
9321 return false;
9322 }
9323 }
9324 Bias::Right => {
9325 if snippet.active_index + 1 < snippet.ranges.len() {
9326 snippet.active_index += 1;
9327 } else {
9328 self.snippet_stack.push(snippet);
9329 return false;
9330 }
9331 }
9332 }
9333 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9334 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9335 // Reverse order so that the first range is the newest created selection.
9336 // Completions will use it and autoscroll will prioritize it.
9337 s.select_ranges(current_ranges.iter().rev().cloned())
9338 });
9339
9340 if let Some(choices) = &snippet.choices[snippet.active_index] {
9341 if let Some(selection) = current_ranges.first() {
9342 self.show_snippet_choices(&choices, selection.clone(), cx);
9343 }
9344 }
9345
9346 // If snippet state is not at the last tabstop, push it back on the stack
9347 if snippet.active_index + 1 < snippet.ranges.len() {
9348 self.snippet_stack.push(snippet);
9349 }
9350 return true;
9351 }
9352 }
9353
9354 false
9355 }
9356
9357 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9358 self.transact(window, cx, |this, window, cx| {
9359 this.select_all(&SelectAll, window, cx);
9360 this.insert("", window, cx);
9361 });
9362 }
9363
9364 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9366 self.transact(window, cx, |this, window, cx| {
9367 this.select_autoclose_pair(window, cx);
9368 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9369 if !this.linked_edit_ranges.is_empty() {
9370 let selections = this.selections.all::<MultiBufferPoint>(cx);
9371 let snapshot = this.buffer.read(cx).snapshot(cx);
9372
9373 for selection in selections.iter() {
9374 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9375 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9376 if selection_start.buffer_id != selection_end.buffer_id {
9377 continue;
9378 }
9379 if let Some(ranges) =
9380 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9381 {
9382 for (buffer, entries) in ranges {
9383 linked_ranges.entry(buffer).or_default().extend(entries);
9384 }
9385 }
9386 }
9387 }
9388
9389 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9390 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9391 for selection in &mut selections {
9392 if selection.is_empty() {
9393 let old_head = selection.head();
9394 let mut new_head =
9395 movement::left(&display_map, old_head.to_display_point(&display_map))
9396 .to_point(&display_map);
9397 if let Some((buffer, line_buffer_range)) = display_map
9398 .buffer_snapshot
9399 .buffer_line_for_row(MultiBufferRow(old_head.row))
9400 {
9401 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9402 let indent_len = match indent_size.kind {
9403 IndentKind::Space => {
9404 buffer.settings_at(line_buffer_range.start, cx).tab_size
9405 }
9406 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9407 };
9408 if old_head.column <= indent_size.len && old_head.column > 0 {
9409 let indent_len = indent_len.get();
9410 new_head = cmp::min(
9411 new_head,
9412 MultiBufferPoint::new(
9413 old_head.row,
9414 ((old_head.column - 1) / indent_len) * indent_len,
9415 ),
9416 );
9417 }
9418 }
9419
9420 selection.set_head(new_head, SelectionGoal::None);
9421 }
9422 }
9423
9424 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9425 s.select(selections)
9426 });
9427 this.insert("", window, cx);
9428 let empty_str: Arc<str> = Arc::from("");
9429 for (buffer, edits) in linked_ranges {
9430 let snapshot = buffer.read(cx).snapshot();
9431 use text::ToPoint as TP;
9432
9433 let edits = edits
9434 .into_iter()
9435 .map(|range| {
9436 let end_point = TP::to_point(&range.end, &snapshot);
9437 let mut start_point = TP::to_point(&range.start, &snapshot);
9438
9439 if end_point == start_point {
9440 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9441 .saturating_sub(1);
9442 start_point =
9443 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9444 };
9445
9446 (start_point..end_point, empty_str.clone())
9447 })
9448 .sorted_by_key(|(range, _)| range.start)
9449 .collect::<Vec<_>>();
9450 buffer.update(cx, |this, cx| {
9451 this.edit(edits, None, cx);
9452 })
9453 }
9454 this.refresh_inline_completion(true, false, window, cx);
9455 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9456 });
9457 }
9458
9459 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9460 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9461 self.transact(window, cx, |this, window, cx| {
9462 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9463 s.move_with(|map, selection| {
9464 if selection.is_empty() {
9465 let cursor = movement::right(map, selection.head());
9466 selection.end = cursor;
9467 selection.reversed = true;
9468 selection.goal = SelectionGoal::None;
9469 }
9470 })
9471 });
9472 this.insert("", window, cx);
9473 this.refresh_inline_completion(true, false, window, cx);
9474 });
9475 }
9476
9477 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9479 if self.move_to_prev_snippet_tabstop(window, cx) {
9480 return;
9481 }
9482 self.outdent(&Outdent, window, cx);
9483 }
9484
9485 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9486 if self.move_to_next_snippet_tabstop(window, cx) {
9487 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9488 return;
9489 }
9490 if self.read_only(cx) {
9491 return;
9492 }
9493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9494 let mut selections = self.selections.all_adjusted(cx);
9495 let buffer = self.buffer.read(cx);
9496 let snapshot = buffer.snapshot(cx);
9497 let rows_iter = selections.iter().map(|s| s.head().row);
9498 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9499
9500 let has_some_cursor_in_whitespace = selections
9501 .iter()
9502 .filter(|selection| selection.is_empty())
9503 .any(|selection| {
9504 let cursor = selection.head();
9505 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9506 cursor.column < current_indent.len
9507 });
9508
9509 let mut edits = Vec::new();
9510 let mut prev_edited_row = 0;
9511 let mut row_delta = 0;
9512 for selection in &mut selections {
9513 if selection.start.row != prev_edited_row {
9514 row_delta = 0;
9515 }
9516 prev_edited_row = selection.end.row;
9517
9518 // If the selection is non-empty, then increase the indentation of the selected lines.
9519 if !selection.is_empty() {
9520 row_delta =
9521 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9522 continue;
9523 }
9524
9525 let cursor = selection.head();
9526 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9527 if let Some(suggested_indent) =
9528 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9529 {
9530 // Don't do anything if already at suggested indent
9531 // and there is any other cursor which is not
9532 if has_some_cursor_in_whitespace
9533 && cursor.column == current_indent.len
9534 && current_indent.len == suggested_indent.len
9535 {
9536 continue;
9537 }
9538
9539 // Adjust line and move cursor to suggested indent
9540 // if cursor is not at suggested indent
9541 if cursor.column < suggested_indent.len
9542 && cursor.column <= current_indent.len
9543 && current_indent.len <= suggested_indent.len
9544 {
9545 selection.start = Point::new(cursor.row, suggested_indent.len);
9546 selection.end = selection.start;
9547 if row_delta == 0 {
9548 edits.extend(Buffer::edit_for_indent_size_adjustment(
9549 cursor.row,
9550 current_indent,
9551 suggested_indent,
9552 ));
9553 row_delta = suggested_indent.len - current_indent.len;
9554 }
9555 continue;
9556 }
9557
9558 // If current indent is more than suggested indent
9559 // only move cursor to current indent and skip indent
9560 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9561 selection.start = Point::new(cursor.row, current_indent.len);
9562 selection.end = selection.start;
9563 continue;
9564 }
9565 }
9566
9567 // Otherwise, insert a hard or soft tab.
9568 let settings = buffer.language_settings_at(cursor, cx);
9569 let tab_size = if settings.hard_tabs {
9570 IndentSize::tab()
9571 } else {
9572 let tab_size = settings.tab_size.get();
9573 let indent_remainder = snapshot
9574 .text_for_range(Point::new(cursor.row, 0)..cursor)
9575 .flat_map(str::chars)
9576 .fold(row_delta % tab_size, |counter: u32, c| {
9577 if c == '\t' {
9578 0
9579 } else {
9580 (counter + 1) % tab_size
9581 }
9582 });
9583
9584 let chars_to_next_tab_stop = tab_size - indent_remainder;
9585 IndentSize::spaces(chars_to_next_tab_stop)
9586 };
9587 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9588 selection.end = selection.start;
9589 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9590 row_delta += tab_size.len;
9591 }
9592
9593 self.transact(window, cx, |this, window, cx| {
9594 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9595 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9596 s.select(selections)
9597 });
9598 this.refresh_inline_completion(true, false, window, cx);
9599 });
9600 }
9601
9602 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9603 if self.read_only(cx) {
9604 return;
9605 }
9606 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9607 let mut selections = self.selections.all::<Point>(cx);
9608 let mut prev_edited_row = 0;
9609 let mut row_delta = 0;
9610 let mut edits = Vec::new();
9611 let buffer = self.buffer.read(cx);
9612 let snapshot = buffer.snapshot(cx);
9613 for selection in &mut selections {
9614 if selection.start.row != prev_edited_row {
9615 row_delta = 0;
9616 }
9617 prev_edited_row = selection.end.row;
9618
9619 row_delta =
9620 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9621 }
9622
9623 self.transact(window, cx, |this, window, cx| {
9624 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9625 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9626 s.select(selections)
9627 });
9628 });
9629 }
9630
9631 fn indent_selection(
9632 buffer: &MultiBuffer,
9633 snapshot: &MultiBufferSnapshot,
9634 selection: &mut Selection<Point>,
9635 edits: &mut Vec<(Range<Point>, String)>,
9636 delta_for_start_row: u32,
9637 cx: &App,
9638 ) -> u32 {
9639 let settings = buffer.language_settings_at(selection.start, cx);
9640 let tab_size = settings.tab_size.get();
9641 let indent_kind = if settings.hard_tabs {
9642 IndentKind::Tab
9643 } else {
9644 IndentKind::Space
9645 };
9646 let mut start_row = selection.start.row;
9647 let mut end_row = selection.end.row + 1;
9648
9649 // If a selection ends at the beginning of a line, don't indent
9650 // that last line.
9651 if selection.end.column == 0 && selection.end.row > selection.start.row {
9652 end_row -= 1;
9653 }
9654
9655 // Avoid re-indenting a row that has already been indented by a
9656 // previous selection, but still update this selection's column
9657 // to reflect that indentation.
9658 if delta_for_start_row > 0 {
9659 start_row += 1;
9660 selection.start.column += delta_for_start_row;
9661 if selection.end.row == selection.start.row {
9662 selection.end.column += delta_for_start_row;
9663 }
9664 }
9665
9666 let mut delta_for_end_row = 0;
9667 let has_multiple_rows = start_row + 1 != end_row;
9668 for row in start_row..end_row {
9669 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9670 let indent_delta = match (current_indent.kind, indent_kind) {
9671 (IndentKind::Space, IndentKind::Space) => {
9672 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9673 IndentSize::spaces(columns_to_next_tab_stop)
9674 }
9675 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9676 (_, IndentKind::Tab) => IndentSize::tab(),
9677 };
9678
9679 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9680 0
9681 } else {
9682 selection.start.column
9683 };
9684 let row_start = Point::new(row, start);
9685 edits.push((
9686 row_start..row_start,
9687 indent_delta.chars().collect::<String>(),
9688 ));
9689
9690 // Update this selection's endpoints to reflect the indentation.
9691 if row == selection.start.row {
9692 selection.start.column += indent_delta.len;
9693 }
9694 if row == selection.end.row {
9695 selection.end.column += indent_delta.len;
9696 delta_for_end_row = indent_delta.len;
9697 }
9698 }
9699
9700 if selection.start.row == selection.end.row {
9701 delta_for_start_row + delta_for_end_row
9702 } else {
9703 delta_for_end_row
9704 }
9705 }
9706
9707 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9708 if self.read_only(cx) {
9709 return;
9710 }
9711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9713 let selections = self.selections.all::<Point>(cx);
9714 let mut deletion_ranges = Vec::new();
9715 let mut last_outdent = None;
9716 {
9717 let buffer = self.buffer.read(cx);
9718 let snapshot = buffer.snapshot(cx);
9719 for selection in &selections {
9720 let settings = buffer.language_settings_at(selection.start, cx);
9721 let tab_size = settings.tab_size.get();
9722 let mut rows = selection.spanned_rows(false, &display_map);
9723
9724 // Avoid re-outdenting a row that has already been outdented by a
9725 // previous selection.
9726 if let Some(last_row) = last_outdent {
9727 if last_row == rows.start {
9728 rows.start = rows.start.next_row();
9729 }
9730 }
9731 let has_multiple_rows = rows.len() > 1;
9732 for row in rows.iter_rows() {
9733 let indent_size = snapshot.indent_size_for_line(row);
9734 if indent_size.len > 0 {
9735 let deletion_len = match indent_size.kind {
9736 IndentKind::Space => {
9737 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9738 if columns_to_prev_tab_stop == 0 {
9739 tab_size
9740 } else {
9741 columns_to_prev_tab_stop
9742 }
9743 }
9744 IndentKind::Tab => 1,
9745 };
9746 let start = if has_multiple_rows
9747 || deletion_len > selection.start.column
9748 || indent_size.len < selection.start.column
9749 {
9750 0
9751 } else {
9752 selection.start.column - deletion_len
9753 };
9754 deletion_ranges.push(
9755 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9756 );
9757 last_outdent = Some(row);
9758 }
9759 }
9760 }
9761 }
9762
9763 self.transact(window, cx, |this, window, cx| {
9764 this.buffer.update(cx, |buffer, cx| {
9765 let empty_str: Arc<str> = Arc::default();
9766 buffer.edit(
9767 deletion_ranges
9768 .into_iter()
9769 .map(|range| (range, empty_str.clone())),
9770 None,
9771 cx,
9772 );
9773 });
9774 let selections = this.selections.all::<usize>(cx);
9775 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9776 s.select(selections)
9777 });
9778 });
9779 }
9780
9781 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9782 if self.read_only(cx) {
9783 return;
9784 }
9785 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9786 let selections = self
9787 .selections
9788 .all::<usize>(cx)
9789 .into_iter()
9790 .map(|s| s.range());
9791
9792 self.transact(window, cx, |this, window, cx| {
9793 this.buffer.update(cx, |buffer, cx| {
9794 buffer.autoindent_ranges(selections, cx);
9795 });
9796 let selections = this.selections.all::<usize>(cx);
9797 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9798 s.select(selections)
9799 });
9800 });
9801 }
9802
9803 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9804 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9806 let selections = self.selections.all::<Point>(cx);
9807
9808 let mut new_cursors = Vec::new();
9809 let mut edit_ranges = Vec::new();
9810 let mut selections = selections.iter().peekable();
9811 while let Some(selection) = selections.next() {
9812 let mut rows = selection.spanned_rows(false, &display_map);
9813 let goal_display_column = selection.head().to_display_point(&display_map).column();
9814
9815 // Accumulate contiguous regions of rows that we want to delete.
9816 while let Some(next_selection) = selections.peek() {
9817 let next_rows = next_selection.spanned_rows(false, &display_map);
9818 if next_rows.start <= rows.end {
9819 rows.end = next_rows.end;
9820 selections.next().unwrap();
9821 } else {
9822 break;
9823 }
9824 }
9825
9826 let buffer = &display_map.buffer_snapshot;
9827 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9828 let edit_end;
9829 let cursor_buffer_row;
9830 if buffer.max_point().row >= rows.end.0 {
9831 // If there's a line after the range, delete the \n from the end of the row range
9832 // and position the cursor on the next line.
9833 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9834 cursor_buffer_row = rows.end;
9835 } else {
9836 // If there isn't a line after the range, delete the \n from the line before the
9837 // start of the row range and position the cursor there.
9838 edit_start = edit_start.saturating_sub(1);
9839 edit_end = buffer.len();
9840 cursor_buffer_row = rows.start.previous_row();
9841 }
9842
9843 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9844 *cursor.column_mut() =
9845 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9846
9847 new_cursors.push((
9848 selection.id,
9849 buffer.anchor_after(cursor.to_point(&display_map)),
9850 ));
9851 edit_ranges.push(edit_start..edit_end);
9852 }
9853
9854 self.transact(window, cx, |this, window, cx| {
9855 let buffer = this.buffer.update(cx, |buffer, cx| {
9856 let empty_str: Arc<str> = Arc::default();
9857 buffer.edit(
9858 edit_ranges
9859 .into_iter()
9860 .map(|range| (range, empty_str.clone())),
9861 None,
9862 cx,
9863 );
9864 buffer.snapshot(cx)
9865 });
9866 let new_selections = new_cursors
9867 .into_iter()
9868 .map(|(id, cursor)| {
9869 let cursor = cursor.to_point(&buffer);
9870 Selection {
9871 id,
9872 start: cursor,
9873 end: cursor,
9874 reversed: false,
9875 goal: SelectionGoal::None,
9876 }
9877 })
9878 .collect();
9879
9880 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9881 s.select(new_selections);
9882 });
9883 });
9884 }
9885
9886 pub fn join_lines_impl(
9887 &mut self,
9888 insert_whitespace: bool,
9889 window: &mut Window,
9890 cx: &mut Context<Self>,
9891 ) {
9892 if self.read_only(cx) {
9893 return;
9894 }
9895 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9896 for selection in self.selections.all::<Point>(cx) {
9897 let start = MultiBufferRow(selection.start.row);
9898 // Treat single line selections as if they include the next line. Otherwise this action
9899 // would do nothing for single line selections individual cursors.
9900 let end = if selection.start.row == selection.end.row {
9901 MultiBufferRow(selection.start.row + 1)
9902 } else {
9903 MultiBufferRow(selection.end.row)
9904 };
9905
9906 if let Some(last_row_range) = row_ranges.last_mut() {
9907 if start <= last_row_range.end {
9908 last_row_range.end = end;
9909 continue;
9910 }
9911 }
9912 row_ranges.push(start..end);
9913 }
9914
9915 let snapshot = self.buffer.read(cx).snapshot(cx);
9916 let mut cursor_positions = Vec::new();
9917 for row_range in &row_ranges {
9918 let anchor = snapshot.anchor_before(Point::new(
9919 row_range.end.previous_row().0,
9920 snapshot.line_len(row_range.end.previous_row()),
9921 ));
9922 cursor_positions.push(anchor..anchor);
9923 }
9924
9925 self.transact(window, cx, |this, window, cx| {
9926 for row_range in row_ranges.into_iter().rev() {
9927 for row in row_range.iter_rows().rev() {
9928 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9929 let next_line_row = row.next_row();
9930 let indent = snapshot.indent_size_for_line(next_line_row);
9931 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9932
9933 let replace =
9934 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9935 " "
9936 } else {
9937 ""
9938 };
9939
9940 this.buffer.update(cx, |buffer, cx| {
9941 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9942 });
9943 }
9944 }
9945
9946 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9947 s.select_anchor_ranges(cursor_positions)
9948 });
9949 });
9950 }
9951
9952 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9953 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9954 self.join_lines_impl(true, window, cx);
9955 }
9956
9957 pub fn sort_lines_case_sensitive(
9958 &mut self,
9959 _: &SortLinesCaseSensitive,
9960 window: &mut Window,
9961 cx: &mut Context<Self>,
9962 ) {
9963 self.manipulate_lines(window, cx, |lines| lines.sort())
9964 }
9965
9966 pub fn sort_lines_case_insensitive(
9967 &mut self,
9968 _: &SortLinesCaseInsensitive,
9969 window: &mut Window,
9970 cx: &mut Context<Self>,
9971 ) {
9972 self.manipulate_lines(window, cx, |lines| {
9973 lines.sort_by_key(|line| line.to_lowercase())
9974 })
9975 }
9976
9977 pub fn unique_lines_case_insensitive(
9978 &mut self,
9979 _: &UniqueLinesCaseInsensitive,
9980 window: &mut Window,
9981 cx: &mut Context<Self>,
9982 ) {
9983 self.manipulate_lines(window, cx, |lines| {
9984 let mut seen = HashSet::default();
9985 lines.retain(|line| seen.insert(line.to_lowercase()));
9986 })
9987 }
9988
9989 pub fn unique_lines_case_sensitive(
9990 &mut self,
9991 _: &UniqueLinesCaseSensitive,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) {
9995 self.manipulate_lines(window, cx, |lines| {
9996 let mut seen = HashSet::default();
9997 lines.retain(|line| seen.insert(*line));
9998 })
9999 }
10000
10001 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10002 let Some(project) = self.project.clone() else {
10003 return;
10004 };
10005 self.reload(project, window, cx)
10006 .detach_and_notify_err(window, cx);
10007 }
10008
10009 pub fn restore_file(
10010 &mut self,
10011 _: &::git::RestoreFile,
10012 window: &mut Window,
10013 cx: &mut Context<Self>,
10014 ) {
10015 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10016 let mut buffer_ids = HashSet::default();
10017 let snapshot = self.buffer().read(cx).snapshot(cx);
10018 for selection in self.selections.all::<usize>(cx) {
10019 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10020 }
10021
10022 let buffer = self.buffer().read(cx);
10023 let ranges = buffer_ids
10024 .into_iter()
10025 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10026 .collect::<Vec<_>>();
10027
10028 self.restore_hunks_in_ranges(ranges, window, cx);
10029 }
10030
10031 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10032 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10033 let selections = self
10034 .selections
10035 .all(cx)
10036 .into_iter()
10037 .map(|s| s.range())
10038 .collect();
10039 self.restore_hunks_in_ranges(selections, window, cx);
10040 }
10041
10042 pub fn restore_hunks_in_ranges(
10043 &mut self,
10044 ranges: Vec<Range<Point>>,
10045 window: &mut Window,
10046 cx: &mut Context<Editor>,
10047 ) {
10048 let mut revert_changes = HashMap::default();
10049 let chunk_by = self
10050 .snapshot(window, cx)
10051 .hunks_for_ranges(ranges)
10052 .into_iter()
10053 .chunk_by(|hunk| hunk.buffer_id);
10054 for (buffer_id, hunks) in &chunk_by {
10055 let hunks = hunks.collect::<Vec<_>>();
10056 for hunk in &hunks {
10057 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10058 }
10059 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10060 }
10061 drop(chunk_by);
10062 if !revert_changes.is_empty() {
10063 self.transact(window, cx, |editor, window, cx| {
10064 editor.restore(revert_changes, window, cx);
10065 });
10066 }
10067 }
10068
10069 pub fn open_active_item_in_terminal(
10070 &mut self,
10071 _: &OpenInTerminal,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10076 let project_path = buffer.read(cx).project_path(cx)?;
10077 let project = self.project.as_ref()?.read(cx);
10078 let entry = project.entry_for_path(&project_path, cx)?;
10079 let parent = match &entry.canonical_path {
10080 Some(canonical_path) => canonical_path.to_path_buf(),
10081 None => project.absolute_path(&project_path, cx)?,
10082 }
10083 .parent()?
10084 .to_path_buf();
10085 Some(parent)
10086 }) {
10087 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10088 }
10089 }
10090
10091 fn set_breakpoint_context_menu(
10092 &mut self,
10093 display_row: DisplayRow,
10094 position: Option<Anchor>,
10095 clicked_point: gpui::Point<Pixels>,
10096 window: &mut Window,
10097 cx: &mut Context<Self>,
10098 ) {
10099 if !cx.has_flag::<DebuggerFeatureFlag>() {
10100 return;
10101 }
10102 let source = self
10103 .buffer
10104 .read(cx)
10105 .snapshot(cx)
10106 .anchor_before(Point::new(display_row.0, 0u32));
10107
10108 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10109
10110 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10111 self,
10112 source,
10113 clicked_point,
10114 context_menu,
10115 window,
10116 cx,
10117 );
10118 }
10119
10120 fn add_edit_breakpoint_block(
10121 &mut self,
10122 anchor: Anchor,
10123 breakpoint: &Breakpoint,
10124 edit_action: BreakpointPromptEditAction,
10125 window: &mut Window,
10126 cx: &mut Context<Self>,
10127 ) {
10128 let weak_editor = cx.weak_entity();
10129 let bp_prompt = cx.new(|cx| {
10130 BreakpointPromptEditor::new(
10131 weak_editor,
10132 anchor,
10133 breakpoint.clone(),
10134 edit_action,
10135 window,
10136 cx,
10137 )
10138 });
10139
10140 let height = bp_prompt.update(cx, |this, cx| {
10141 this.prompt
10142 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10143 });
10144 let cloned_prompt = bp_prompt.clone();
10145 let blocks = vec![BlockProperties {
10146 style: BlockStyle::Sticky,
10147 placement: BlockPlacement::Above(anchor),
10148 height: Some(height),
10149 render: Arc::new(move |cx| {
10150 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10151 cloned_prompt.clone().into_any_element()
10152 }),
10153 priority: 0,
10154 render_in_minimap: true,
10155 }];
10156
10157 let focus_handle = bp_prompt.focus_handle(cx);
10158 window.focus(&focus_handle);
10159
10160 let block_ids = self.insert_blocks(blocks, None, cx);
10161 bp_prompt.update(cx, |prompt, _| {
10162 prompt.add_block_ids(block_ids);
10163 });
10164 }
10165
10166 pub(crate) fn breakpoint_at_row(
10167 &self,
10168 row: u32,
10169 window: &mut Window,
10170 cx: &mut Context<Self>,
10171 ) -> Option<(Anchor, Breakpoint)> {
10172 let snapshot = self.snapshot(window, cx);
10173 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10174
10175 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10176 }
10177
10178 pub(crate) fn breakpoint_at_anchor(
10179 &self,
10180 breakpoint_position: Anchor,
10181 snapshot: &EditorSnapshot,
10182 cx: &mut Context<Self>,
10183 ) -> Option<(Anchor, Breakpoint)> {
10184 let project = self.project.clone()?;
10185
10186 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10187 snapshot
10188 .buffer_snapshot
10189 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10190 })?;
10191
10192 let enclosing_excerpt = breakpoint_position.excerpt_id;
10193 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10194 let buffer_snapshot = buffer.read(cx).snapshot();
10195
10196 let row = buffer_snapshot
10197 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10198 .row;
10199
10200 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10201 let anchor_end = snapshot
10202 .buffer_snapshot
10203 .anchor_after(Point::new(row, line_len));
10204
10205 let bp = self
10206 .breakpoint_store
10207 .as_ref()?
10208 .read_with(cx, |breakpoint_store, cx| {
10209 breakpoint_store
10210 .breakpoints(
10211 &buffer,
10212 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10213 &buffer_snapshot,
10214 cx,
10215 )
10216 .next()
10217 .and_then(|(bp, _)| {
10218 let breakpoint_row = buffer_snapshot
10219 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10220 .row;
10221
10222 if breakpoint_row == row {
10223 snapshot
10224 .buffer_snapshot
10225 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10226 .map(|position| (position, bp.bp.clone()))
10227 } else {
10228 None
10229 }
10230 })
10231 });
10232 bp
10233 }
10234
10235 pub fn edit_log_breakpoint(
10236 &mut self,
10237 _: &EditLogBreakpoint,
10238 window: &mut Window,
10239 cx: &mut Context<Self>,
10240 ) {
10241 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10242 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10243 message: None,
10244 state: BreakpointState::Enabled,
10245 condition: None,
10246 hit_condition: None,
10247 });
10248
10249 self.add_edit_breakpoint_block(
10250 anchor,
10251 &breakpoint,
10252 BreakpointPromptEditAction::Log,
10253 window,
10254 cx,
10255 );
10256 }
10257 }
10258
10259 fn breakpoints_at_cursors(
10260 &self,
10261 window: &mut Window,
10262 cx: &mut Context<Self>,
10263 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10264 let snapshot = self.snapshot(window, cx);
10265 let cursors = self
10266 .selections
10267 .disjoint_anchors()
10268 .into_iter()
10269 .map(|selection| {
10270 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10271
10272 let breakpoint_position = self
10273 .breakpoint_at_row(cursor_position.row, window, cx)
10274 .map(|bp| bp.0)
10275 .unwrap_or_else(|| {
10276 snapshot
10277 .display_snapshot
10278 .buffer_snapshot
10279 .anchor_after(Point::new(cursor_position.row, 0))
10280 });
10281
10282 let breakpoint = self
10283 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10284 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10285
10286 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10287 })
10288 // 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.
10289 .collect::<HashMap<Anchor, _>>();
10290
10291 cursors.into_iter().collect()
10292 }
10293
10294 pub fn enable_breakpoint(
10295 &mut self,
10296 _: &crate::actions::EnableBreakpoint,
10297 window: &mut Window,
10298 cx: &mut Context<Self>,
10299 ) {
10300 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10301 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10302 continue;
10303 };
10304 self.edit_breakpoint_at_anchor(
10305 anchor,
10306 breakpoint,
10307 BreakpointEditAction::InvertState,
10308 cx,
10309 );
10310 }
10311 }
10312
10313 pub fn disable_breakpoint(
10314 &mut self,
10315 _: &crate::actions::DisableBreakpoint,
10316 window: &mut Window,
10317 cx: &mut Context<Self>,
10318 ) {
10319 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10320 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10321 continue;
10322 };
10323 self.edit_breakpoint_at_anchor(
10324 anchor,
10325 breakpoint,
10326 BreakpointEditAction::InvertState,
10327 cx,
10328 );
10329 }
10330 }
10331
10332 pub fn toggle_breakpoint(
10333 &mut self,
10334 _: &crate::actions::ToggleBreakpoint,
10335 window: &mut Window,
10336 cx: &mut Context<Self>,
10337 ) {
10338 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10339 if let Some(breakpoint) = breakpoint {
10340 self.edit_breakpoint_at_anchor(
10341 anchor,
10342 breakpoint,
10343 BreakpointEditAction::Toggle,
10344 cx,
10345 );
10346 } else {
10347 self.edit_breakpoint_at_anchor(
10348 anchor,
10349 Breakpoint::new_standard(),
10350 BreakpointEditAction::Toggle,
10351 cx,
10352 );
10353 }
10354 }
10355 }
10356
10357 pub fn edit_breakpoint_at_anchor(
10358 &mut self,
10359 breakpoint_position: Anchor,
10360 breakpoint: Breakpoint,
10361 edit_action: BreakpointEditAction,
10362 cx: &mut Context<Self>,
10363 ) {
10364 let Some(breakpoint_store) = &self.breakpoint_store else {
10365 return;
10366 };
10367
10368 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10369 if breakpoint_position == Anchor::min() {
10370 self.buffer()
10371 .read(cx)
10372 .excerpt_buffer_ids()
10373 .into_iter()
10374 .next()
10375 } else {
10376 None
10377 }
10378 }) else {
10379 return;
10380 };
10381
10382 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10383 return;
10384 };
10385
10386 breakpoint_store.update(cx, |breakpoint_store, cx| {
10387 breakpoint_store.toggle_breakpoint(
10388 buffer,
10389 BreakpointWithPosition {
10390 position: breakpoint_position.text_anchor,
10391 bp: breakpoint,
10392 },
10393 edit_action,
10394 cx,
10395 );
10396 });
10397
10398 cx.notify();
10399 }
10400
10401 #[cfg(any(test, feature = "test-support"))]
10402 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10403 self.breakpoint_store.clone()
10404 }
10405
10406 pub fn prepare_restore_change(
10407 &self,
10408 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10409 hunk: &MultiBufferDiffHunk,
10410 cx: &mut App,
10411 ) -> Option<()> {
10412 if hunk.is_created_file() {
10413 return None;
10414 }
10415 let buffer = self.buffer.read(cx);
10416 let diff = buffer.diff_for(hunk.buffer_id)?;
10417 let buffer = buffer.buffer(hunk.buffer_id)?;
10418 let buffer = buffer.read(cx);
10419 let original_text = diff
10420 .read(cx)
10421 .base_text()
10422 .as_rope()
10423 .slice(hunk.diff_base_byte_range.clone());
10424 let buffer_snapshot = buffer.snapshot();
10425 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10426 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10427 probe
10428 .0
10429 .start
10430 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10431 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10432 }) {
10433 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10434 Some(())
10435 } else {
10436 None
10437 }
10438 }
10439
10440 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10441 self.manipulate_lines(window, cx, |lines| lines.reverse())
10442 }
10443
10444 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10445 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10446 }
10447
10448 fn manipulate_lines<Fn>(
10449 &mut self,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 mut callback: Fn,
10453 ) where
10454 Fn: FnMut(&mut Vec<&str>),
10455 {
10456 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10457
10458 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10459 let buffer = self.buffer.read(cx).snapshot(cx);
10460
10461 let mut edits = Vec::new();
10462
10463 let selections = self.selections.all::<Point>(cx);
10464 let mut selections = selections.iter().peekable();
10465 let mut contiguous_row_selections = Vec::new();
10466 let mut new_selections = Vec::new();
10467 let mut added_lines = 0;
10468 let mut removed_lines = 0;
10469
10470 while let Some(selection) = selections.next() {
10471 let (start_row, end_row) = consume_contiguous_rows(
10472 &mut contiguous_row_selections,
10473 selection,
10474 &display_map,
10475 &mut selections,
10476 );
10477
10478 let start_point = Point::new(start_row.0, 0);
10479 let end_point = Point::new(
10480 end_row.previous_row().0,
10481 buffer.line_len(end_row.previous_row()),
10482 );
10483 let text = buffer
10484 .text_for_range(start_point..end_point)
10485 .collect::<String>();
10486
10487 let mut lines = text.split('\n').collect_vec();
10488
10489 let lines_before = lines.len();
10490 callback(&mut lines);
10491 let lines_after = lines.len();
10492
10493 edits.push((start_point..end_point, lines.join("\n")));
10494
10495 // Selections must change based on added and removed line count
10496 let start_row =
10497 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10498 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10499 new_selections.push(Selection {
10500 id: selection.id,
10501 start: start_row,
10502 end: end_row,
10503 goal: SelectionGoal::None,
10504 reversed: selection.reversed,
10505 });
10506
10507 if lines_after > lines_before {
10508 added_lines += lines_after - lines_before;
10509 } else if lines_before > lines_after {
10510 removed_lines += lines_before - lines_after;
10511 }
10512 }
10513
10514 self.transact(window, cx, |this, window, cx| {
10515 let buffer = this.buffer.update(cx, |buffer, cx| {
10516 buffer.edit(edits, None, cx);
10517 buffer.snapshot(cx)
10518 });
10519
10520 // Recalculate offsets on newly edited buffer
10521 let new_selections = new_selections
10522 .iter()
10523 .map(|s| {
10524 let start_point = Point::new(s.start.0, 0);
10525 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10526 Selection {
10527 id: s.id,
10528 start: buffer.point_to_offset(start_point),
10529 end: buffer.point_to_offset(end_point),
10530 goal: s.goal,
10531 reversed: s.reversed,
10532 }
10533 })
10534 .collect();
10535
10536 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10537 s.select(new_selections);
10538 });
10539
10540 this.request_autoscroll(Autoscroll::fit(), cx);
10541 });
10542 }
10543
10544 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10545 self.manipulate_text(window, cx, |text| {
10546 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10547 if has_upper_case_characters {
10548 text.to_lowercase()
10549 } else {
10550 text.to_uppercase()
10551 }
10552 })
10553 }
10554
10555 pub fn convert_to_upper_case(
10556 &mut self,
10557 _: &ConvertToUpperCase,
10558 window: &mut Window,
10559 cx: &mut Context<Self>,
10560 ) {
10561 self.manipulate_text(window, cx, |text| text.to_uppercase())
10562 }
10563
10564 pub fn convert_to_lower_case(
10565 &mut self,
10566 _: &ConvertToLowerCase,
10567 window: &mut Window,
10568 cx: &mut Context<Self>,
10569 ) {
10570 self.manipulate_text(window, cx, |text| text.to_lowercase())
10571 }
10572
10573 pub fn convert_to_title_case(
10574 &mut self,
10575 _: &ConvertToTitleCase,
10576 window: &mut Window,
10577 cx: &mut Context<Self>,
10578 ) {
10579 self.manipulate_text(window, cx, |text| {
10580 text.split('\n')
10581 .map(|line| line.to_case(Case::Title))
10582 .join("\n")
10583 })
10584 }
10585
10586 pub fn convert_to_snake_case(
10587 &mut self,
10588 _: &ConvertToSnakeCase,
10589 window: &mut Window,
10590 cx: &mut Context<Self>,
10591 ) {
10592 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10593 }
10594
10595 pub fn convert_to_kebab_case(
10596 &mut self,
10597 _: &ConvertToKebabCase,
10598 window: &mut Window,
10599 cx: &mut Context<Self>,
10600 ) {
10601 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10602 }
10603
10604 pub fn convert_to_upper_camel_case(
10605 &mut self,
10606 _: &ConvertToUpperCamelCase,
10607 window: &mut Window,
10608 cx: &mut Context<Self>,
10609 ) {
10610 self.manipulate_text(window, cx, |text| {
10611 text.split('\n')
10612 .map(|line| line.to_case(Case::UpperCamel))
10613 .join("\n")
10614 })
10615 }
10616
10617 pub fn convert_to_lower_camel_case(
10618 &mut self,
10619 _: &ConvertToLowerCamelCase,
10620 window: &mut Window,
10621 cx: &mut Context<Self>,
10622 ) {
10623 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10624 }
10625
10626 pub fn convert_to_opposite_case(
10627 &mut self,
10628 _: &ConvertToOppositeCase,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 self.manipulate_text(window, cx, |text| {
10633 text.chars()
10634 .fold(String::with_capacity(text.len()), |mut t, c| {
10635 if c.is_uppercase() {
10636 t.extend(c.to_lowercase());
10637 } else {
10638 t.extend(c.to_uppercase());
10639 }
10640 t
10641 })
10642 })
10643 }
10644
10645 pub fn convert_to_rot13(
10646 &mut self,
10647 _: &ConvertToRot13,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.manipulate_text(window, cx, |text| {
10652 text.chars()
10653 .map(|c| match c {
10654 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10655 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10656 _ => c,
10657 })
10658 .collect()
10659 })
10660 }
10661
10662 pub fn convert_to_rot47(
10663 &mut self,
10664 _: &ConvertToRot47,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.manipulate_text(window, cx, |text| {
10669 text.chars()
10670 .map(|c| {
10671 let code_point = c as u32;
10672 if code_point >= 33 && code_point <= 126 {
10673 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10674 }
10675 c
10676 })
10677 .collect()
10678 })
10679 }
10680
10681 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10682 where
10683 Fn: FnMut(&str) -> String,
10684 {
10685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10686 let buffer = self.buffer.read(cx).snapshot(cx);
10687
10688 let mut new_selections = Vec::new();
10689 let mut edits = Vec::new();
10690 let mut selection_adjustment = 0i32;
10691
10692 for selection in self.selections.all::<usize>(cx) {
10693 let selection_is_empty = selection.is_empty();
10694
10695 let (start, end) = if selection_is_empty {
10696 let word_range = movement::surrounding_word(
10697 &display_map,
10698 selection.start.to_display_point(&display_map),
10699 );
10700 let start = word_range.start.to_offset(&display_map, Bias::Left);
10701 let end = word_range.end.to_offset(&display_map, Bias::Left);
10702 (start, end)
10703 } else {
10704 (selection.start, selection.end)
10705 };
10706
10707 let text = buffer.text_for_range(start..end).collect::<String>();
10708 let old_length = text.len() as i32;
10709 let text = callback(&text);
10710
10711 new_selections.push(Selection {
10712 start: (start as i32 - selection_adjustment) as usize,
10713 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10714 goal: SelectionGoal::None,
10715 ..selection
10716 });
10717
10718 selection_adjustment += old_length - text.len() as i32;
10719
10720 edits.push((start..end, text));
10721 }
10722
10723 self.transact(window, cx, |this, window, cx| {
10724 this.buffer.update(cx, |buffer, cx| {
10725 buffer.edit(edits, None, cx);
10726 });
10727
10728 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10729 s.select(new_selections);
10730 });
10731
10732 this.request_autoscroll(Autoscroll::fit(), cx);
10733 });
10734 }
10735
10736 pub fn move_selection_on_drop(
10737 &mut self,
10738 selection: &Selection<Anchor>,
10739 target: DisplayPoint,
10740 is_cut: bool,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10745 let buffer = &display_map.buffer_snapshot;
10746 let mut edits = Vec::new();
10747 let insert_point = display_map
10748 .clip_point(target, Bias::Left)
10749 .to_point(&display_map);
10750 let text = buffer
10751 .text_for_range(selection.start..selection.end)
10752 .collect::<String>();
10753 if is_cut {
10754 edits.push(((selection.start..selection.end), String::new()));
10755 }
10756 let insert_anchor = buffer.anchor_before(insert_point);
10757 edits.push(((insert_anchor..insert_anchor), text));
10758 let last_edit_start = insert_anchor.bias_left(buffer);
10759 let last_edit_end = insert_anchor.bias_right(buffer);
10760 self.transact(window, cx, |this, window, cx| {
10761 this.buffer.update(cx, |buffer, cx| {
10762 buffer.edit(edits, None, cx);
10763 });
10764 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10765 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10766 });
10767 });
10768 }
10769
10770 pub fn clear_selection_drag_state(&mut self) {
10771 self.selection_drag_state = SelectionDragState::None;
10772 }
10773
10774 pub fn duplicate(
10775 &mut self,
10776 upwards: bool,
10777 whole_lines: bool,
10778 window: &mut Window,
10779 cx: &mut Context<Self>,
10780 ) {
10781 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10782
10783 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10784 let buffer = &display_map.buffer_snapshot;
10785 let selections = self.selections.all::<Point>(cx);
10786
10787 let mut edits = Vec::new();
10788 let mut selections_iter = selections.iter().peekable();
10789 while let Some(selection) = selections_iter.next() {
10790 let mut rows = selection.spanned_rows(false, &display_map);
10791 // duplicate line-wise
10792 if whole_lines || selection.start == selection.end {
10793 // Avoid duplicating the same lines twice.
10794 while let Some(next_selection) = selections_iter.peek() {
10795 let next_rows = next_selection.spanned_rows(false, &display_map);
10796 if next_rows.start < rows.end {
10797 rows.end = next_rows.end;
10798 selections_iter.next().unwrap();
10799 } else {
10800 break;
10801 }
10802 }
10803
10804 // Copy the text from the selected row region and splice it either at the start
10805 // or end of the region.
10806 let start = Point::new(rows.start.0, 0);
10807 let end = Point::new(
10808 rows.end.previous_row().0,
10809 buffer.line_len(rows.end.previous_row()),
10810 );
10811 let text = buffer
10812 .text_for_range(start..end)
10813 .chain(Some("\n"))
10814 .collect::<String>();
10815 let insert_location = if upwards {
10816 Point::new(rows.end.0, 0)
10817 } else {
10818 start
10819 };
10820 edits.push((insert_location..insert_location, text));
10821 } else {
10822 // duplicate character-wise
10823 let start = selection.start;
10824 let end = selection.end;
10825 let text = buffer.text_for_range(start..end).collect::<String>();
10826 edits.push((selection.end..selection.end, text));
10827 }
10828 }
10829
10830 self.transact(window, cx, |this, _, cx| {
10831 this.buffer.update(cx, |buffer, cx| {
10832 buffer.edit(edits, None, cx);
10833 });
10834
10835 this.request_autoscroll(Autoscroll::fit(), cx);
10836 });
10837 }
10838
10839 pub fn duplicate_line_up(
10840 &mut self,
10841 _: &DuplicateLineUp,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 self.duplicate(true, true, window, cx);
10846 }
10847
10848 pub fn duplicate_line_down(
10849 &mut self,
10850 _: &DuplicateLineDown,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) {
10854 self.duplicate(false, true, window, cx);
10855 }
10856
10857 pub fn duplicate_selection(
10858 &mut self,
10859 _: &DuplicateSelection,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 ) {
10863 self.duplicate(false, false, window, cx);
10864 }
10865
10866 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10867 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10868
10869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10870 let buffer = self.buffer.read(cx).snapshot(cx);
10871
10872 let mut edits = Vec::new();
10873 let mut unfold_ranges = Vec::new();
10874 let mut refold_creases = Vec::new();
10875
10876 let selections = self.selections.all::<Point>(cx);
10877 let mut selections = selections.iter().peekable();
10878 let mut contiguous_row_selections = Vec::new();
10879 let mut new_selections = Vec::new();
10880
10881 while let Some(selection) = selections.next() {
10882 // Find all the selections that span a contiguous row range
10883 let (start_row, end_row) = consume_contiguous_rows(
10884 &mut contiguous_row_selections,
10885 selection,
10886 &display_map,
10887 &mut selections,
10888 );
10889
10890 // Move the text spanned by the row range to be before the line preceding the row range
10891 if start_row.0 > 0 {
10892 let range_to_move = Point::new(
10893 start_row.previous_row().0,
10894 buffer.line_len(start_row.previous_row()),
10895 )
10896 ..Point::new(
10897 end_row.previous_row().0,
10898 buffer.line_len(end_row.previous_row()),
10899 );
10900 let insertion_point = display_map
10901 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10902 .0;
10903
10904 // Don't move lines across excerpts
10905 if buffer
10906 .excerpt_containing(insertion_point..range_to_move.end)
10907 .is_some()
10908 {
10909 let text = buffer
10910 .text_for_range(range_to_move.clone())
10911 .flat_map(|s| s.chars())
10912 .skip(1)
10913 .chain(['\n'])
10914 .collect::<String>();
10915
10916 edits.push((
10917 buffer.anchor_after(range_to_move.start)
10918 ..buffer.anchor_before(range_to_move.end),
10919 String::new(),
10920 ));
10921 let insertion_anchor = buffer.anchor_after(insertion_point);
10922 edits.push((insertion_anchor..insertion_anchor, text));
10923
10924 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10925
10926 // Move selections up
10927 new_selections.extend(contiguous_row_selections.drain(..).map(
10928 |mut selection| {
10929 selection.start.row -= row_delta;
10930 selection.end.row -= row_delta;
10931 selection
10932 },
10933 ));
10934
10935 // Move folds up
10936 unfold_ranges.push(range_to_move.clone());
10937 for fold in display_map.folds_in_range(
10938 buffer.anchor_before(range_to_move.start)
10939 ..buffer.anchor_after(range_to_move.end),
10940 ) {
10941 let mut start = fold.range.start.to_point(&buffer);
10942 let mut end = fold.range.end.to_point(&buffer);
10943 start.row -= row_delta;
10944 end.row -= row_delta;
10945 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10946 }
10947 }
10948 }
10949
10950 // If we didn't move line(s), preserve the existing selections
10951 new_selections.append(&mut contiguous_row_selections);
10952 }
10953
10954 self.transact(window, cx, |this, window, cx| {
10955 this.unfold_ranges(&unfold_ranges, true, true, cx);
10956 this.buffer.update(cx, |buffer, cx| {
10957 for (range, text) in edits {
10958 buffer.edit([(range, text)], None, cx);
10959 }
10960 });
10961 this.fold_creases(refold_creases, true, window, cx);
10962 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10963 s.select(new_selections);
10964 })
10965 });
10966 }
10967
10968 pub fn move_line_down(
10969 &mut self,
10970 _: &MoveLineDown,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10975
10976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10977 let buffer = self.buffer.read(cx).snapshot(cx);
10978
10979 let mut edits = Vec::new();
10980 let mut unfold_ranges = Vec::new();
10981 let mut refold_creases = Vec::new();
10982
10983 let selections = self.selections.all::<Point>(cx);
10984 let mut selections = selections.iter().peekable();
10985 let mut contiguous_row_selections = Vec::new();
10986 let mut new_selections = Vec::new();
10987
10988 while let Some(selection) = selections.next() {
10989 // Find all the selections that span a contiguous row range
10990 let (start_row, end_row) = consume_contiguous_rows(
10991 &mut contiguous_row_selections,
10992 selection,
10993 &display_map,
10994 &mut selections,
10995 );
10996
10997 // Move the text spanned by the row range to be after the last line of the row range
10998 if end_row.0 <= buffer.max_point().row {
10999 let range_to_move =
11000 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11001 let insertion_point = display_map
11002 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11003 .0;
11004
11005 // Don't move lines across excerpt boundaries
11006 if buffer
11007 .excerpt_containing(range_to_move.start..insertion_point)
11008 .is_some()
11009 {
11010 let mut text = String::from("\n");
11011 text.extend(buffer.text_for_range(range_to_move.clone()));
11012 text.pop(); // Drop trailing newline
11013 edits.push((
11014 buffer.anchor_after(range_to_move.start)
11015 ..buffer.anchor_before(range_to_move.end),
11016 String::new(),
11017 ));
11018 let insertion_anchor = buffer.anchor_after(insertion_point);
11019 edits.push((insertion_anchor..insertion_anchor, text));
11020
11021 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11022
11023 // Move selections down
11024 new_selections.extend(contiguous_row_selections.drain(..).map(
11025 |mut selection| {
11026 selection.start.row += row_delta;
11027 selection.end.row += row_delta;
11028 selection
11029 },
11030 ));
11031
11032 // Move folds down
11033 unfold_ranges.push(range_to_move.clone());
11034 for fold in display_map.folds_in_range(
11035 buffer.anchor_before(range_to_move.start)
11036 ..buffer.anchor_after(range_to_move.end),
11037 ) {
11038 let mut start = fold.range.start.to_point(&buffer);
11039 let mut end = fold.range.end.to_point(&buffer);
11040 start.row += row_delta;
11041 end.row += row_delta;
11042 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11043 }
11044 }
11045 }
11046
11047 // If we didn't move line(s), preserve the existing selections
11048 new_selections.append(&mut contiguous_row_selections);
11049 }
11050
11051 self.transact(window, cx, |this, window, cx| {
11052 this.unfold_ranges(&unfold_ranges, true, true, cx);
11053 this.buffer.update(cx, |buffer, cx| {
11054 for (range, text) in edits {
11055 buffer.edit([(range, text)], None, cx);
11056 }
11057 });
11058 this.fold_creases(refold_creases, true, window, cx);
11059 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11060 s.select(new_selections)
11061 });
11062 });
11063 }
11064
11065 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11067 let text_layout_details = &self.text_layout_details(window);
11068 self.transact(window, cx, |this, window, cx| {
11069 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11070 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11071 s.move_with(|display_map, selection| {
11072 if !selection.is_empty() {
11073 return;
11074 }
11075
11076 let mut head = selection.head();
11077 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11078 if head.column() == display_map.line_len(head.row()) {
11079 transpose_offset = display_map
11080 .buffer_snapshot
11081 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11082 }
11083
11084 if transpose_offset == 0 {
11085 return;
11086 }
11087
11088 *head.column_mut() += 1;
11089 head = display_map.clip_point(head, Bias::Right);
11090 let goal = SelectionGoal::HorizontalPosition(
11091 display_map
11092 .x_for_display_point(head, text_layout_details)
11093 .into(),
11094 );
11095 selection.collapse_to(head, goal);
11096
11097 let transpose_start = display_map
11098 .buffer_snapshot
11099 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11100 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11101 let transpose_end = display_map
11102 .buffer_snapshot
11103 .clip_offset(transpose_offset + 1, Bias::Right);
11104 if let Some(ch) =
11105 display_map.buffer_snapshot.chars_at(transpose_start).next()
11106 {
11107 edits.push((transpose_start..transpose_offset, String::new()));
11108 edits.push((transpose_end..transpose_end, ch.to_string()));
11109 }
11110 }
11111 });
11112 edits
11113 });
11114 this.buffer
11115 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11116 let selections = this.selections.all::<usize>(cx);
11117 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11118 s.select(selections);
11119 });
11120 });
11121 }
11122
11123 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11124 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11125 self.rewrap_impl(RewrapOptions::default(), cx)
11126 }
11127
11128 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11129 let buffer = self.buffer.read(cx).snapshot(cx);
11130 let selections = self.selections.all::<Point>(cx);
11131
11132 // Shrink and split selections to respect paragraph boundaries.
11133 let ranges = selections.into_iter().flat_map(|selection| {
11134 let language_settings = buffer.language_settings_at(selection.head(), cx);
11135 let language_scope = buffer.language_scope_at(selection.head());
11136
11137 let Some(start_row) = (selection.start.row..=selection.end.row)
11138 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11139 else {
11140 return vec![];
11141 };
11142 let Some(end_row) = (selection.start.row..=selection.end.row)
11143 .rev()
11144 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11145 else {
11146 return vec![];
11147 };
11148
11149 let mut row = start_row;
11150 let mut ranges = Vec::new();
11151 while let Some(blank_row) =
11152 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11153 {
11154 let next_paragraph_start = (blank_row + 1..=end_row)
11155 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11156 .unwrap();
11157 ranges.push((
11158 language_settings.clone(),
11159 language_scope.clone(),
11160 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11161 ));
11162 row = next_paragraph_start;
11163 }
11164 ranges.push((
11165 language_settings.clone(),
11166 language_scope.clone(),
11167 Point::new(row, 0)..Point::new(end_row, 0),
11168 ));
11169
11170 ranges
11171 });
11172
11173 let mut edits = Vec::new();
11174 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11175
11176 for (language_settings, language_scope, range) in ranges {
11177 let mut start_row = range.start.row;
11178 let mut end_row = range.end.row;
11179
11180 // Skip selections that overlap with a range that has already been rewrapped.
11181 let selection_range = start_row..end_row;
11182 if rewrapped_row_ranges
11183 .iter()
11184 .any(|range| range.overlaps(&selection_range))
11185 {
11186 continue;
11187 }
11188
11189 let tab_size = language_settings.tab_size;
11190
11191 // Since not all lines in the selection may be at the same indent
11192 // level, choose the indent size that is the most common between all
11193 // of the lines.
11194 //
11195 // If there is a tie, we use the deepest indent.
11196 let (indent_size, indent_end) = {
11197 let mut indent_size_occurrences = HashMap::default();
11198 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11199
11200 for row in start_row..=end_row {
11201 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11202 rows_by_indent_size.entry(indent).or_default().push(row);
11203 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11204 }
11205
11206 let indent_size = indent_size_occurrences
11207 .into_iter()
11208 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11209 .map(|(indent, _)| indent)
11210 .unwrap_or_default();
11211 let row = rows_by_indent_size[&indent_size][0];
11212 let indent_end = Point::new(row, indent_size.len);
11213
11214 (indent_size, indent_end)
11215 };
11216
11217 let mut line_prefix = indent_size.chars().collect::<String>();
11218
11219 let mut inside_comment = false;
11220 if let Some(comment_prefix) = language_scope.and_then(|language| {
11221 language
11222 .line_comment_prefixes()
11223 .iter()
11224 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11225 .cloned()
11226 }) {
11227 line_prefix.push_str(&comment_prefix);
11228 inside_comment = true;
11229 }
11230
11231 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11232 RewrapBehavior::InComments => inside_comment,
11233 RewrapBehavior::InSelections => !range.is_empty(),
11234 RewrapBehavior::Anywhere => true,
11235 };
11236
11237 let should_rewrap = options.override_language_settings
11238 || allow_rewrap_based_on_language
11239 || self.hard_wrap.is_some();
11240 if !should_rewrap {
11241 continue;
11242 }
11243
11244 if range.is_empty() {
11245 'expand_upwards: while start_row > 0 {
11246 let prev_row = start_row - 1;
11247 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11248 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11249 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11250 {
11251 start_row = prev_row;
11252 } else {
11253 break 'expand_upwards;
11254 }
11255 }
11256
11257 'expand_downwards: while end_row < buffer.max_point().row {
11258 let next_row = end_row + 1;
11259 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11260 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11261 && !buffer.is_line_blank(MultiBufferRow(next_row))
11262 {
11263 end_row = next_row;
11264 } else {
11265 break 'expand_downwards;
11266 }
11267 }
11268 }
11269
11270 let start = Point::new(start_row, 0);
11271 let start_offset = start.to_offset(&buffer);
11272 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11273 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11274 let Some(lines_without_prefixes) = selection_text
11275 .lines()
11276 .map(|line| {
11277 line.strip_prefix(&line_prefix)
11278 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11279 .with_context(|| {
11280 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11281 })
11282 })
11283 .collect::<Result<Vec<_>, _>>()
11284 .log_err()
11285 else {
11286 continue;
11287 };
11288
11289 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11290 buffer
11291 .language_settings_at(Point::new(start_row, 0), cx)
11292 .preferred_line_length as usize
11293 });
11294 let wrapped_text = wrap_with_prefix(
11295 line_prefix,
11296 lines_without_prefixes.join("\n"),
11297 wrap_column,
11298 tab_size,
11299 options.preserve_existing_whitespace,
11300 );
11301
11302 // TODO: should always use char-based diff while still supporting cursor behavior that
11303 // matches vim.
11304 let mut diff_options = DiffOptions::default();
11305 if options.override_language_settings {
11306 diff_options.max_word_diff_len = 0;
11307 diff_options.max_word_diff_line_count = 0;
11308 } else {
11309 diff_options.max_word_diff_len = usize::MAX;
11310 diff_options.max_word_diff_line_count = usize::MAX;
11311 }
11312
11313 for (old_range, new_text) in
11314 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11315 {
11316 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11317 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11318 edits.push((edit_start..edit_end, new_text));
11319 }
11320
11321 rewrapped_row_ranges.push(start_row..=end_row);
11322 }
11323
11324 self.buffer
11325 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11326 }
11327
11328 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11329 let mut text = String::new();
11330 let buffer = self.buffer.read(cx).snapshot(cx);
11331 let mut selections = self.selections.all::<Point>(cx);
11332 let mut clipboard_selections = Vec::with_capacity(selections.len());
11333 {
11334 let max_point = buffer.max_point();
11335 let mut is_first = true;
11336 for selection in &mut selections {
11337 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11338 if is_entire_line {
11339 selection.start = Point::new(selection.start.row, 0);
11340 if !selection.is_empty() && selection.end.column == 0 {
11341 selection.end = cmp::min(max_point, selection.end);
11342 } else {
11343 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11344 }
11345 selection.goal = SelectionGoal::None;
11346 }
11347 if is_first {
11348 is_first = false;
11349 } else {
11350 text += "\n";
11351 }
11352 let mut len = 0;
11353 for chunk in buffer.text_for_range(selection.start..selection.end) {
11354 text.push_str(chunk);
11355 len += chunk.len();
11356 }
11357 clipboard_selections.push(ClipboardSelection {
11358 len,
11359 is_entire_line,
11360 first_line_indent: buffer
11361 .indent_size_for_line(MultiBufferRow(selection.start.row))
11362 .len,
11363 });
11364 }
11365 }
11366
11367 self.transact(window, cx, |this, window, cx| {
11368 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.select(selections);
11370 });
11371 this.insert("", window, cx);
11372 });
11373 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11374 }
11375
11376 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11377 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11378 let item = self.cut_common(window, cx);
11379 cx.write_to_clipboard(item);
11380 }
11381
11382 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11384 self.change_selections(None, window, cx, |s| {
11385 s.move_with(|snapshot, sel| {
11386 if sel.is_empty() {
11387 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11388 }
11389 });
11390 });
11391 let item = self.cut_common(window, cx);
11392 cx.set_global(KillRing(item))
11393 }
11394
11395 pub fn kill_ring_yank(
11396 &mut self,
11397 _: &KillRingYank,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11402 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11403 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11404 (kill_ring.text().to_string(), kill_ring.metadata_json())
11405 } else {
11406 return;
11407 }
11408 } else {
11409 return;
11410 };
11411 self.do_paste(&text, metadata, false, window, cx);
11412 }
11413
11414 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11415 self.do_copy(true, cx);
11416 }
11417
11418 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11419 self.do_copy(false, cx);
11420 }
11421
11422 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11423 let selections = self.selections.all::<Point>(cx);
11424 let buffer = self.buffer.read(cx).read(cx);
11425 let mut text = String::new();
11426
11427 let mut clipboard_selections = Vec::with_capacity(selections.len());
11428 {
11429 let max_point = buffer.max_point();
11430 let mut is_first = true;
11431 for selection in &selections {
11432 let mut start = selection.start;
11433 let mut end = selection.end;
11434 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11435 if is_entire_line {
11436 start = Point::new(start.row, 0);
11437 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11438 }
11439
11440 let mut trimmed_selections = Vec::new();
11441 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11442 let row = MultiBufferRow(start.row);
11443 let first_indent = buffer.indent_size_for_line(row);
11444 if first_indent.len == 0 || start.column > first_indent.len {
11445 trimmed_selections.push(start..end);
11446 } else {
11447 trimmed_selections.push(
11448 Point::new(row.0, first_indent.len)
11449 ..Point::new(row.0, buffer.line_len(row)),
11450 );
11451 for row in start.row + 1..=end.row {
11452 let mut line_len = buffer.line_len(MultiBufferRow(row));
11453 if row == end.row {
11454 line_len = end.column;
11455 }
11456 if line_len == 0 {
11457 trimmed_selections
11458 .push(Point::new(row, 0)..Point::new(row, line_len));
11459 continue;
11460 }
11461 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11462 if row_indent_size.len >= first_indent.len {
11463 trimmed_selections.push(
11464 Point::new(row, first_indent.len)..Point::new(row, line_len),
11465 );
11466 } else {
11467 trimmed_selections.clear();
11468 trimmed_selections.push(start..end);
11469 break;
11470 }
11471 }
11472 }
11473 } else {
11474 trimmed_selections.push(start..end);
11475 }
11476
11477 for trimmed_range in trimmed_selections {
11478 if is_first {
11479 is_first = false;
11480 } else {
11481 text += "\n";
11482 }
11483 let mut len = 0;
11484 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11485 text.push_str(chunk);
11486 len += chunk.len();
11487 }
11488 clipboard_selections.push(ClipboardSelection {
11489 len,
11490 is_entire_line,
11491 first_line_indent: buffer
11492 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11493 .len,
11494 });
11495 }
11496 }
11497 }
11498
11499 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11500 text,
11501 clipboard_selections,
11502 ));
11503 }
11504
11505 pub fn do_paste(
11506 &mut self,
11507 text: &String,
11508 clipboard_selections: Option<Vec<ClipboardSelection>>,
11509 handle_entire_lines: bool,
11510 window: &mut Window,
11511 cx: &mut Context<Self>,
11512 ) {
11513 if self.read_only(cx) {
11514 return;
11515 }
11516
11517 let clipboard_text = Cow::Borrowed(text);
11518
11519 self.transact(window, cx, |this, window, cx| {
11520 if let Some(mut clipboard_selections) = clipboard_selections {
11521 let old_selections = this.selections.all::<usize>(cx);
11522 let all_selections_were_entire_line =
11523 clipboard_selections.iter().all(|s| s.is_entire_line);
11524 let first_selection_indent_column =
11525 clipboard_selections.first().map(|s| s.first_line_indent);
11526 if clipboard_selections.len() != old_selections.len() {
11527 clipboard_selections.drain(..);
11528 }
11529 let cursor_offset = this.selections.last::<usize>(cx).head();
11530 let mut auto_indent_on_paste = true;
11531
11532 this.buffer.update(cx, |buffer, cx| {
11533 let snapshot = buffer.read(cx);
11534 auto_indent_on_paste = snapshot
11535 .language_settings_at(cursor_offset, cx)
11536 .auto_indent_on_paste;
11537
11538 let mut start_offset = 0;
11539 let mut edits = Vec::new();
11540 let mut original_indent_columns = Vec::new();
11541 for (ix, selection) in old_selections.iter().enumerate() {
11542 let to_insert;
11543 let entire_line;
11544 let original_indent_column;
11545 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11546 let end_offset = start_offset + clipboard_selection.len;
11547 to_insert = &clipboard_text[start_offset..end_offset];
11548 entire_line = clipboard_selection.is_entire_line;
11549 start_offset = end_offset + 1;
11550 original_indent_column = Some(clipboard_selection.first_line_indent);
11551 } else {
11552 to_insert = clipboard_text.as_str();
11553 entire_line = all_selections_were_entire_line;
11554 original_indent_column = first_selection_indent_column
11555 }
11556
11557 // If the corresponding selection was empty when this slice of the
11558 // clipboard text was written, then the entire line containing the
11559 // selection was copied. If this selection is also currently empty,
11560 // then paste the line before the current line of the buffer.
11561 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11562 let column = selection.start.to_point(&snapshot).column as usize;
11563 let line_start = selection.start - column;
11564 line_start..line_start
11565 } else {
11566 selection.range()
11567 };
11568
11569 edits.push((range, to_insert));
11570 original_indent_columns.push(original_indent_column);
11571 }
11572 drop(snapshot);
11573
11574 buffer.edit(
11575 edits,
11576 if auto_indent_on_paste {
11577 Some(AutoindentMode::Block {
11578 original_indent_columns,
11579 })
11580 } else {
11581 None
11582 },
11583 cx,
11584 );
11585 });
11586
11587 let selections = this.selections.all::<usize>(cx);
11588 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11589 s.select(selections)
11590 });
11591 } else {
11592 this.insert(&clipboard_text, window, cx);
11593 }
11594 });
11595 }
11596
11597 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11598 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11599 if let Some(item) = cx.read_from_clipboard() {
11600 let entries = item.entries();
11601
11602 match entries.first() {
11603 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11604 // of all the pasted entries.
11605 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11606 .do_paste(
11607 clipboard_string.text(),
11608 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11609 true,
11610 window,
11611 cx,
11612 ),
11613 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11614 }
11615 }
11616 }
11617
11618 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11619 if self.read_only(cx) {
11620 return;
11621 }
11622
11623 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11624
11625 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11626 if let Some((selections, _)) =
11627 self.selection_history.transaction(transaction_id).cloned()
11628 {
11629 self.change_selections(None, window, cx, |s| {
11630 s.select_anchors(selections.to_vec());
11631 });
11632 } else {
11633 log::error!(
11634 "No entry in selection_history found for undo. \
11635 This may correspond to a bug where undo does not update the selection. \
11636 If this is occurring, please add details to \
11637 https://github.com/zed-industries/zed/issues/22692"
11638 );
11639 }
11640 self.request_autoscroll(Autoscroll::fit(), cx);
11641 self.unmark_text(window, cx);
11642 self.refresh_inline_completion(true, false, window, cx);
11643 cx.emit(EditorEvent::Edited { transaction_id });
11644 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11645 }
11646 }
11647
11648 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11649 if self.read_only(cx) {
11650 return;
11651 }
11652
11653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11654
11655 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11656 if let Some((_, Some(selections))) =
11657 self.selection_history.transaction(transaction_id).cloned()
11658 {
11659 self.change_selections(None, window, cx, |s| {
11660 s.select_anchors(selections.to_vec());
11661 });
11662 } else {
11663 log::error!(
11664 "No entry in selection_history found for redo. \
11665 This may correspond to a bug where undo does not update the selection. \
11666 If this is occurring, please add details to \
11667 https://github.com/zed-industries/zed/issues/22692"
11668 );
11669 }
11670 self.request_autoscroll(Autoscroll::fit(), cx);
11671 self.unmark_text(window, cx);
11672 self.refresh_inline_completion(true, false, window, cx);
11673 cx.emit(EditorEvent::Edited { transaction_id });
11674 }
11675 }
11676
11677 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11678 self.buffer
11679 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11680 }
11681
11682 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11683 self.buffer
11684 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11685 }
11686
11687 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11688 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11689 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11690 s.move_with(|map, selection| {
11691 let cursor = if selection.is_empty() {
11692 movement::left(map, selection.start)
11693 } else {
11694 selection.start
11695 };
11696 selection.collapse_to(cursor, SelectionGoal::None);
11697 });
11698 })
11699 }
11700
11701 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11702 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11703 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11704 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11705 })
11706 }
11707
11708 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11709 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11711 s.move_with(|map, selection| {
11712 let cursor = if selection.is_empty() {
11713 movement::right(map, selection.end)
11714 } else {
11715 selection.end
11716 };
11717 selection.collapse_to(cursor, SelectionGoal::None)
11718 });
11719 })
11720 }
11721
11722 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11723 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11725 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11726 })
11727 }
11728
11729 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11730 if self.take_rename(true, window, cx).is_some() {
11731 return;
11732 }
11733
11734 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11735 cx.propagate();
11736 return;
11737 }
11738
11739 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11740
11741 let text_layout_details = &self.text_layout_details(window);
11742 let selection_count = self.selections.count();
11743 let first_selection = self.selections.first_anchor();
11744
11745 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11746 s.move_with(|map, selection| {
11747 if !selection.is_empty() {
11748 selection.goal = SelectionGoal::None;
11749 }
11750 let (cursor, goal) = movement::up(
11751 map,
11752 selection.start,
11753 selection.goal,
11754 false,
11755 text_layout_details,
11756 );
11757 selection.collapse_to(cursor, goal);
11758 });
11759 });
11760
11761 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11762 {
11763 cx.propagate();
11764 }
11765 }
11766
11767 pub fn move_up_by_lines(
11768 &mut self,
11769 action: &MoveUpByLines,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 ) {
11773 if self.take_rename(true, window, cx).is_some() {
11774 return;
11775 }
11776
11777 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11778 cx.propagate();
11779 return;
11780 }
11781
11782 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11783
11784 let text_layout_details = &self.text_layout_details(window);
11785
11786 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11787 s.move_with(|map, selection| {
11788 if !selection.is_empty() {
11789 selection.goal = SelectionGoal::None;
11790 }
11791 let (cursor, goal) = movement::up_by_rows(
11792 map,
11793 selection.start,
11794 action.lines,
11795 selection.goal,
11796 false,
11797 text_layout_details,
11798 );
11799 selection.collapse_to(cursor, goal);
11800 });
11801 })
11802 }
11803
11804 pub fn move_down_by_lines(
11805 &mut self,
11806 action: &MoveDownByLines,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 if self.take_rename(true, window, cx).is_some() {
11811 return;
11812 }
11813
11814 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11815 cx.propagate();
11816 return;
11817 }
11818
11819 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11820
11821 let text_layout_details = &self.text_layout_details(window);
11822
11823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11824 s.move_with(|map, selection| {
11825 if !selection.is_empty() {
11826 selection.goal = SelectionGoal::None;
11827 }
11828 let (cursor, goal) = movement::down_by_rows(
11829 map,
11830 selection.start,
11831 action.lines,
11832 selection.goal,
11833 false,
11834 text_layout_details,
11835 );
11836 selection.collapse_to(cursor, goal);
11837 });
11838 })
11839 }
11840
11841 pub fn select_down_by_lines(
11842 &mut self,
11843 action: &SelectDownByLines,
11844 window: &mut Window,
11845 cx: &mut Context<Self>,
11846 ) {
11847 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11848 let text_layout_details = &self.text_layout_details(window);
11849 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11850 s.move_heads_with(|map, head, goal| {
11851 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11852 })
11853 })
11854 }
11855
11856 pub fn select_up_by_lines(
11857 &mut self,
11858 action: &SelectUpByLines,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11863 let text_layout_details = &self.text_layout_details(window);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.move_heads_with(|map, head, goal| {
11866 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11867 })
11868 })
11869 }
11870
11871 pub fn select_page_up(
11872 &mut self,
11873 _: &SelectPageUp,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 let Some(row_count) = self.visible_row_count() else {
11878 return;
11879 };
11880
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882
11883 let text_layout_details = &self.text_layout_details(window);
11884
11885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11886 s.move_heads_with(|map, head, goal| {
11887 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11888 })
11889 })
11890 }
11891
11892 pub fn move_page_up(
11893 &mut self,
11894 action: &MovePageUp,
11895 window: &mut Window,
11896 cx: &mut Context<Self>,
11897 ) {
11898 if self.take_rename(true, window, cx).is_some() {
11899 return;
11900 }
11901
11902 if self
11903 .context_menu
11904 .borrow_mut()
11905 .as_mut()
11906 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11907 .unwrap_or(false)
11908 {
11909 return;
11910 }
11911
11912 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11913 cx.propagate();
11914 return;
11915 }
11916
11917 let Some(row_count) = self.visible_row_count() else {
11918 return;
11919 };
11920
11921 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11922
11923 let autoscroll = if action.center_cursor {
11924 Autoscroll::center()
11925 } else {
11926 Autoscroll::fit()
11927 };
11928
11929 let text_layout_details = &self.text_layout_details(window);
11930
11931 self.change_selections(Some(autoscroll), window, cx, |s| {
11932 s.move_with(|map, selection| {
11933 if !selection.is_empty() {
11934 selection.goal = SelectionGoal::None;
11935 }
11936 let (cursor, goal) = movement::up_by_rows(
11937 map,
11938 selection.end,
11939 row_count,
11940 selection.goal,
11941 false,
11942 text_layout_details,
11943 );
11944 selection.collapse_to(cursor, goal);
11945 });
11946 });
11947 }
11948
11949 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11950 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11951 let text_layout_details = &self.text_layout_details(window);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.move_heads_with(|map, head, goal| {
11954 movement::up(map, head, goal, false, text_layout_details)
11955 })
11956 })
11957 }
11958
11959 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11960 self.take_rename(true, window, cx);
11961
11962 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11963 cx.propagate();
11964 return;
11965 }
11966
11967 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11968
11969 let text_layout_details = &self.text_layout_details(window);
11970 let selection_count = self.selections.count();
11971 let first_selection = self.selections.first_anchor();
11972
11973 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11974 s.move_with(|map, selection| {
11975 if !selection.is_empty() {
11976 selection.goal = SelectionGoal::None;
11977 }
11978 let (cursor, goal) = movement::down(
11979 map,
11980 selection.end,
11981 selection.goal,
11982 false,
11983 text_layout_details,
11984 );
11985 selection.collapse_to(cursor, goal);
11986 });
11987 });
11988
11989 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11990 {
11991 cx.propagate();
11992 }
11993 }
11994
11995 pub fn select_page_down(
11996 &mut self,
11997 _: &SelectPageDown,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 let Some(row_count) = self.visible_row_count() else {
12002 return;
12003 };
12004
12005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12006
12007 let text_layout_details = &self.text_layout_details(window);
12008
12009 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12010 s.move_heads_with(|map, head, goal| {
12011 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12012 })
12013 })
12014 }
12015
12016 pub fn move_page_down(
12017 &mut self,
12018 action: &MovePageDown,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 if self.take_rename(true, window, cx).is_some() {
12023 return;
12024 }
12025
12026 if self
12027 .context_menu
12028 .borrow_mut()
12029 .as_mut()
12030 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12031 .unwrap_or(false)
12032 {
12033 return;
12034 }
12035
12036 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12037 cx.propagate();
12038 return;
12039 }
12040
12041 let Some(row_count) = self.visible_row_count() else {
12042 return;
12043 };
12044
12045 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12046
12047 let autoscroll = if action.center_cursor {
12048 Autoscroll::center()
12049 } else {
12050 Autoscroll::fit()
12051 };
12052
12053 let text_layout_details = &self.text_layout_details(window);
12054 self.change_selections(Some(autoscroll), window, cx, |s| {
12055 s.move_with(|map, selection| {
12056 if !selection.is_empty() {
12057 selection.goal = SelectionGoal::None;
12058 }
12059 let (cursor, goal) = movement::down_by_rows(
12060 map,
12061 selection.end,
12062 row_count,
12063 selection.goal,
12064 false,
12065 text_layout_details,
12066 );
12067 selection.collapse_to(cursor, goal);
12068 });
12069 });
12070 }
12071
12072 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12074 let text_layout_details = &self.text_layout_details(window);
12075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_heads_with(|map, head, goal| {
12077 movement::down(map, head, goal, false, text_layout_details)
12078 })
12079 });
12080 }
12081
12082 pub fn context_menu_first(
12083 &mut self,
12084 _: &ContextMenuFirst,
12085 window: &mut Window,
12086 cx: &mut Context<Self>,
12087 ) {
12088 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12089 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12090 }
12091 }
12092
12093 pub fn context_menu_prev(
12094 &mut self,
12095 _: &ContextMenuPrevious,
12096 window: &mut Window,
12097 cx: &mut Context<Self>,
12098 ) {
12099 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12100 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12101 }
12102 }
12103
12104 pub fn context_menu_next(
12105 &mut self,
12106 _: &ContextMenuNext,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12111 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12112 }
12113 }
12114
12115 pub fn context_menu_last(
12116 &mut self,
12117 _: &ContextMenuLast,
12118 window: &mut Window,
12119 cx: &mut Context<Self>,
12120 ) {
12121 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12122 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12123 }
12124 }
12125
12126 pub fn move_to_previous_word_start(
12127 &mut self,
12128 _: &MoveToPreviousWordStart,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 ) {
12132 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12134 s.move_cursors_with(|map, head, _| {
12135 (
12136 movement::previous_word_start(map, head),
12137 SelectionGoal::None,
12138 )
12139 });
12140 })
12141 }
12142
12143 pub fn move_to_previous_subword_start(
12144 &mut self,
12145 _: &MoveToPreviousSubwordStart,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12150 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12151 s.move_cursors_with(|map, head, _| {
12152 (
12153 movement::previous_subword_start(map, head),
12154 SelectionGoal::None,
12155 )
12156 });
12157 })
12158 }
12159
12160 pub fn select_to_previous_word_start(
12161 &mut self,
12162 _: &SelectToPreviousWordStart,
12163 window: &mut Window,
12164 cx: &mut Context<Self>,
12165 ) {
12166 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12167 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12168 s.move_heads_with(|map, head, _| {
12169 (
12170 movement::previous_word_start(map, head),
12171 SelectionGoal::None,
12172 )
12173 });
12174 })
12175 }
12176
12177 pub fn select_to_previous_subword_start(
12178 &mut self,
12179 _: &SelectToPreviousSubwordStart,
12180 window: &mut Window,
12181 cx: &mut Context<Self>,
12182 ) {
12183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12185 s.move_heads_with(|map, head, _| {
12186 (
12187 movement::previous_subword_start(map, head),
12188 SelectionGoal::None,
12189 )
12190 });
12191 })
12192 }
12193
12194 pub fn delete_to_previous_word_start(
12195 &mut self,
12196 action: &DeleteToPreviousWordStart,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12201 self.transact(window, cx, |this, window, cx| {
12202 this.select_autoclose_pair(window, cx);
12203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12204 s.move_with(|map, selection| {
12205 if selection.is_empty() {
12206 let cursor = if action.ignore_newlines {
12207 movement::previous_word_start(map, selection.head())
12208 } else {
12209 movement::previous_word_start_or_newline(map, selection.head())
12210 };
12211 selection.set_head(cursor, SelectionGoal::None);
12212 }
12213 });
12214 });
12215 this.insert("", window, cx);
12216 });
12217 }
12218
12219 pub fn delete_to_previous_subword_start(
12220 &mut self,
12221 _: &DeleteToPreviousSubwordStart,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12226 self.transact(window, cx, |this, window, cx| {
12227 this.select_autoclose_pair(window, cx);
12228 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12229 s.move_with(|map, selection| {
12230 if selection.is_empty() {
12231 let cursor = movement::previous_subword_start(map, selection.head());
12232 selection.set_head(cursor, SelectionGoal::None);
12233 }
12234 });
12235 });
12236 this.insert("", window, cx);
12237 });
12238 }
12239
12240 pub fn move_to_next_word_end(
12241 &mut self,
12242 _: &MoveToNextWordEnd,
12243 window: &mut Window,
12244 cx: &mut Context<Self>,
12245 ) {
12246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.move_cursors_with(|map, head, _| {
12249 (movement::next_word_end(map, head), SelectionGoal::None)
12250 });
12251 })
12252 }
12253
12254 pub fn move_to_next_subword_end(
12255 &mut self,
12256 _: &MoveToNextSubwordEnd,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) {
12260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12261 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12262 s.move_cursors_with(|map, head, _| {
12263 (movement::next_subword_end(map, head), SelectionGoal::None)
12264 });
12265 })
12266 }
12267
12268 pub fn select_to_next_word_end(
12269 &mut self,
12270 _: &SelectToNextWordEnd,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) {
12274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12276 s.move_heads_with(|map, head, _| {
12277 (movement::next_word_end(map, head), SelectionGoal::None)
12278 });
12279 })
12280 }
12281
12282 pub fn select_to_next_subword_end(
12283 &mut self,
12284 _: &SelectToNextSubwordEnd,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12289 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12290 s.move_heads_with(|map, head, _| {
12291 (movement::next_subword_end(map, head), SelectionGoal::None)
12292 });
12293 })
12294 }
12295
12296 pub fn delete_to_next_word_end(
12297 &mut self,
12298 action: &DeleteToNextWordEnd,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12303 self.transact(window, cx, |this, window, cx| {
12304 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_with(|map, selection| {
12306 if selection.is_empty() {
12307 let cursor = if action.ignore_newlines {
12308 movement::next_word_end(map, selection.head())
12309 } else {
12310 movement::next_word_end_or_newline(map, selection.head())
12311 };
12312 selection.set_head(cursor, SelectionGoal::None);
12313 }
12314 });
12315 });
12316 this.insert("", window, cx);
12317 });
12318 }
12319
12320 pub fn delete_to_next_subword_end(
12321 &mut self,
12322 _: &DeleteToNextSubwordEnd,
12323 window: &mut Window,
12324 cx: &mut Context<Self>,
12325 ) {
12326 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12327 self.transact(window, cx, |this, window, cx| {
12328 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12329 s.move_with(|map, selection| {
12330 if selection.is_empty() {
12331 let cursor = movement::next_subword_end(map, selection.head());
12332 selection.set_head(cursor, SelectionGoal::None);
12333 }
12334 });
12335 });
12336 this.insert("", window, cx);
12337 });
12338 }
12339
12340 pub fn move_to_beginning_of_line(
12341 &mut self,
12342 action: &MoveToBeginningOfLine,
12343 window: &mut Window,
12344 cx: &mut Context<Self>,
12345 ) {
12346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12347 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12348 s.move_cursors_with(|map, head, _| {
12349 (
12350 movement::indented_line_beginning(
12351 map,
12352 head,
12353 action.stop_at_soft_wraps,
12354 action.stop_at_indent,
12355 ),
12356 SelectionGoal::None,
12357 )
12358 });
12359 })
12360 }
12361
12362 pub fn select_to_beginning_of_line(
12363 &mut self,
12364 action: &SelectToBeginningOfLine,
12365 window: &mut Window,
12366 cx: &mut Context<Self>,
12367 ) {
12368 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12369 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12370 s.move_heads_with(|map, head, _| {
12371 (
12372 movement::indented_line_beginning(
12373 map,
12374 head,
12375 action.stop_at_soft_wraps,
12376 action.stop_at_indent,
12377 ),
12378 SelectionGoal::None,
12379 )
12380 });
12381 });
12382 }
12383
12384 pub fn delete_to_beginning_of_line(
12385 &mut self,
12386 action: &DeleteToBeginningOfLine,
12387 window: &mut Window,
12388 cx: &mut Context<Self>,
12389 ) {
12390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12391 self.transact(window, cx, |this, window, cx| {
12392 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12393 s.move_with(|_, selection| {
12394 selection.reversed = true;
12395 });
12396 });
12397
12398 this.select_to_beginning_of_line(
12399 &SelectToBeginningOfLine {
12400 stop_at_soft_wraps: false,
12401 stop_at_indent: action.stop_at_indent,
12402 },
12403 window,
12404 cx,
12405 );
12406 this.backspace(&Backspace, window, cx);
12407 });
12408 }
12409
12410 pub fn move_to_end_of_line(
12411 &mut self,
12412 action: &MoveToEndOfLine,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12417 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12418 s.move_cursors_with(|map, head, _| {
12419 (
12420 movement::line_end(map, head, action.stop_at_soft_wraps),
12421 SelectionGoal::None,
12422 )
12423 });
12424 })
12425 }
12426
12427 pub fn select_to_end_of_line(
12428 &mut self,
12429 action: &SelectToEndOfLine,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) {
12433 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12434 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12435 s.move_heads_with(|map, head, _| {
12436 (
12437 movement::line_end(map, head, action.stop_at_soft_wraps),
12438 SelectionGoal::None,
12439 )
12440 });
12441 })
12442 }
12443
12444 pub fn delete_to_end_of_line(
12445 &mut self,
12446 _: &DeleteToEndOfLine,
12447 window: &mut Window,
12448 cx: &mut Context<Self>,
12449 ) {
12450 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12451 self.transact(window, cx, |this, window, cx| {
12452 this.select_to_end_of_line(
12453 &SelectToEndOfLine {
12454 stop_at_soft_wraps: false,
12455 },
12456 window,
12457 cx,
12458 );
12459 this.delete(&Delete, window, cx);
12460 });
12461 }
12462
12463 pub fn cut_to_end_of_line(
12464 &mut self,
12465 _: &CutToEndOfLine,
12466 window: &mut Window,
12467 cx: &mut Context<Self>,
12468 ) {
12469 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12470 self.transact(window, cx, |this, window, cx| {
12471 this.select_to_end_of_line(
12472 &SelectToEndOfLine {
12473 stop_at_soft_wraps: false,
12474 },
12475 window,
12476 cx,
12477 );
12478 this.cut(&Cut, window, cx);
12479 });
12480 }
12481
12482 pub fn move_to_start_of_paragraph(
12483 &mut self,
12484 _: &MoveToStartOfParagraph,
12485 window: &mut Window,
12486 cx: &mut Context<Self>,
12487 ) {
12488 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12489 cx.propagate();
12490 return;
12491 }
12492 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12493 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12494 s.move_with(|map, selection| {
12495 selection.collapse_to(
12496 movement::start_of_paragraph(map, selection.head(), 1),
12497 SelectionGoal::None,
12498 )
12499 });
12500 })
12501 }
12502
12503 pub fn move_to_end_of_paragraph(
12504 &mut self,
12505 _: &MoveToEndOfParagraph,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12510 cx.propagate();
12511 return;
12512 }
12513 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12515 s.move_with(|map, selection| {
12516 selection.collapse_to(
12517 movement::end_of_paragraph(map, selection.head(), 1),
12518 SelectionGoal::None,
12519 )
12520 });
12521 })
12522 }
12523
12524 pub fn select_to_start_of_paragraph(
12525 &mut self,
12526 _: &SelectToStartOfParagraph,
12527 window: &mut Window,
12528 cx: &mut Context<Self>,
12529 ) {
12530 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12531 cx.propagate();
12532 return;
12533 }
12534 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12536 s.move_heads_with(|map, head, _| {
12537 (
12538 movement::start_of_paragraph(map, head, 1),
12539 SelectionGoal::None,
12540 )
12541 });
12542 })
12543 }
12544
12545 pub fn select_to_end_of_paragraph(
12546 &mut self,
12547 _: &SelectToEndOfParagraph,
12548 window: &mut Window,
12549 cx: &mut Context<Self>,
12550 ) {
12551 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12552 cx.propagate();
12553 return;
12554 }
12555 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12556 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12557 s.move_heads_with(|map, head, _| {
12558 (
12559 movement::end_of_paragraph(map, head, 1),
12560 SelectionGoal::None,
12561 )
12562 });
12563 })
12564 }
12565
12566 pub fn move_to_start_of_excerpt(
12567 &mut self,
12568 _: &MoveToStartOfExcerpt,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) {
12572 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12573 cx.propagate();
12574 return;
12575 }
12576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12577 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12578 s.move_with(|map, selection| {
12579 selection.collapse_to(
12580 movement::start_of_excerpt(
12581 map,
12582 selection.head(),
12583 workspace::searchable::Direction::Prev,
12584 ),
12585 SelectionGoal::None,
12586 )
12587 });
12588 })
12589 }
12590
12591 pub fn move_to_start_of_next_excerpt(
12592 &mut self,
12593 _: &MoveToStartOfNextExcerpt,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12598 cx.propagate();
12599 return;
12600 }
12601
12602 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12603 s.move_with(|map, selection| {
12604 selection.collapse_to(
12605 movement::start_of_excerpt(
12606 map,
12607 selection.head(),
12608 workspace::searchable::Direction::Next,
12609 ),
12610 SelectionGoal::None,
12611 )
12612 });
12613 })
12614 }
12615
12616 pub fn move_to_end_of_excerpt(
12617 &mut self,
12618 _: &MoveToEndOfExcerpt,
12619 window: &mut Window,
12620 cx: &mut Context<Self>,
12621 ) {
12622 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12623 cx.propagate();
12624 return;
12625 }
12626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12627 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12628 s.move_with(|map, selection| {
12629 selection.collapse_to(
12630 movement::end_of_excerpt(
12631 map,
12632 selection.head(),
12633 workspace::searchable::Direction::Next,
12634 ),
12635 SelectionGoal::None,
12636 )
12637 });
12638 })
12639 }
12640
12641 pub fn move_to_end_of_previous_excerpt(
12642 &mut self,
12643 _: &MoveToEndOfPreviousExcerpt,
12644 window: &mut Window,
12645 cx: &mut Context<Self>,
12646 ) {
12647 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12648 cx.propagate();
12649 return;
12650 }
12651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12653 s.move_with(|map, selection| {
12654 selection.collapse_to(
12655 movement::end_of_excerpt(
12656 map,
12657 selection.head(),
12658 workspace::searchable::Direction::Prev,
12659 ),
12660 SelectionGoal::None,
12661 )
12662 });
12663 })
12664 }
12665
12666 pub fn select_to_start_of_excerpt(
12667 &mut self,
12668 _: &SelectToStartOfExcerpt,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12673 cx.propagate();
12674 return;
12675 }
12676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12678 s.move_heads_with(|map, head, _| {
12679 (
12680 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12681 SelectionGoal::None,
12682 )
12683 });
12684 })
12685 }
12686
12687 pub fn select_to_start_of_next_excerpt(
12688 &mut self,
12689 _: &SelectToStartOfNextExcerpt,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) {
12693 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12694 cx.propagate();
12695 return;
12696 }
12697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12699 s.move_heads_with(|map, head, _| {
12700 (
12701 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12702 SelectionGoal::None,
12703 )
12704 });
12705 })
12706 }
12707
12708 pub fn select_to_end_of_excerpt(
12709 &mut self,
12710 _: &SelectToEndOfExcerpt,
12711 window: &mut Window,
12712 cx: &mut Context<Self>,
12713 ) {
12714 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12715 cx.propagate();
12716 return;
12717 }
12718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12719 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12720 s.move_heads_with(|map, head, _| {
12721 (
12722 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12723 SelectionGoal::None,
12724 )
12725 });
12726 })
12727 }
12728
12729 pub fn select_to_end_of_previous_excerpt(
12730 &mut self,
12731 _: &SelectToEndOfPreviousExcerpt,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12736 cx.propagate();
12737 return;
12738 }
12739 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12740 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12741 s.move_heads_with(|map, head, _| {
12742 (
12743 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12744 SelectionGoal::None,
12745 )
12746 });
12747 })
12748 }
12749
12750 pub fn move_to_beginning(
12751 &mut self,
12752 _: &MoveToBeginning,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) {
12756 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12757 cx.propagate();
12758 return;
12759 }
12760 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12761 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12762 s.select_ranges(vec![0..0]);
12763 });
12764 }
12765
12766 pub fn select_to_beginning(
12767 &mut self,
12768 _: &SelectToBeginning,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 let mut selection = self.selections.last::<Point>(cx);
12773 selection.set_head(Point::zero(), SelectionGoal::None);
12774 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12775 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12776 s.select(vec![selection]);
12777 });
12778 }
12779
12780 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12781 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12782 cx.propagate();
12783 return;
12784 }
12785 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12786 let cursor = self.buffer.read(cx).read(cx).len();
12787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12788 s.select_ranges(vec![cursor..cursor])
12789 });
12790 }
12791
12792 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12793 self.nav_history = nav_history;
12794 }
12795
12796 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12797 self.nav_history.as_ref()
12798 }
12799
12800 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12801 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12802 }
12803
12804 fn push_to_nav_history(
12805 &mut self,
12806 cursor_anchor: Anchor,
12807 new_position: Option<Point>,
12808 is_deactivate: bool,
12809 cx: &mut Context<Self>,
12810 ) {
12811 if let Some(nav_history) = self.nav_history.as_mut() {
12812 let buffer = self.buffer.read(cx).read(cx);
12813 let cursor_position = cursor_anchor.to_point(&buffer);
12814 let scroll_state = self.scroll_manager.anchor();
12815 let scroll_top_row = scroll_state.top_row(&buffer);
12816 drop(buffer);
12817
12818 if let Some(new_position) = new_position {
12819 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12820 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12821 return;
12822 }
12823 }
12824
12825 nav_history.push(
12826 Some(NavigationData {
12827 cursor_anchor,
12828 cursor_position,
12829 scroll_anchor: scroll_state,
12830 scroll_top_row,
12831 }),
12832 cx,
12833 );
12834 cx.emit(EditorEvent::PushedToNavHistory {
12835 anchor: cursor_anchor,
12836 is_deactivate,
12837 })
12838 }
12839 }
12840
12841 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12842 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12843 let buffer = self.buffer.read(cx).snapshot(cx);
12844 let mut selection = self.selections.first::<usize>(cx);
12845 selection.set_head(buffer.len(), SelectionGoal::None);
12846 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12847 s.select(vec![selection]);
12848 });
12849 }
12850
12851 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12852 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12853 let end = self.buffer.read(cx).read(cx).len();
12854 self.change_selections(None, window, cx, |s| {
12855 s.select_ranges(vec![0..end]);
12856 });
12857 }
12858
12859 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12860 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12862 let mut selections = self.selections.all::<Point>(cx);
12863 let max_point = display_map.buffer_snapshot.max_point();
12864 for selection in &mut selections {
12865 let rows = selection.spanned_rows(true, &display_map);
12866 selection.start = Point::new(rows.start.0, 0);
12867 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12868 selection.reversed = false;
12869 }
12870 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12871 s.select(selections);
12872 });
12873 }
12874
12875 pub fn split_selection_into_lines(
12876 &mut self,
12877 _: &SplitSelectionIntoLines,
12878 window: &mut Window,
12879 cx: &mut Context<Self>,
12880 ) {
12881 let selections = self
12882 .selections
12883 .all::<Point>(cx)
12884 .into_iter()
12885 .map(|selection| selection.start..selection.end)
12886 .collect::<Vec<_>>();
12887 self.unfold_ranges(&selections, true, true, cx);
12888
12889 let mut new_selection_ranges = Vec::new();
12890 {
12891 let buffer = self.buffer.read(cx).read(cx);
12892 for selection in selections {
12893 for row in selection.start.row..selection.end.row {
12894 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12895 new_selection_ranges.push(cursor..cursor);
12896 }
12897
12898 let is_multiline_selection = selection.start.row != selection.end.row;
12899 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12900 // so this action feels more ergonomic when paired with other selection operations
12901 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12902 if !should_skip_last {
12903 new_selection_ranges.push(selection.end..selection.end);
12904 }
12905 }
12906 }
12907 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12908 s.select_ranges(new_selection_ranges);
12909 });
12910 }
12911
12912 pub fn add_selection_above(
12913 &mut self,
12914 _: &AddSelectionAbove,
12915 window: &mut Window,
12916 cx: &mut Context<Self>,
12917 ) {
12918 self.add_selection(true, window, cx);
12919 }
12920
12921 pub fn add_selection_below(
12922 &mut self,
12923 _: &AddSelectionBelow,
12924 window: &mut Window,
12925 cx: &mut Context<Self>,
12926 ) {
12927 self.add_selection(false, window, cx);
12928 }
12929
12930 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12932
12933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12934 let all_selections = self.selections.all::<Point>(cx);
12935 let text_layout_details = self.text_layout_details(window);
12936
12937 let (mut columnar_selections, new_selections_to_columnarize) = {
12938 if let Some(state) = self.add_selections_state.as_ref() {
12939 let columnar_selection_ids: HashSet<_> = state
12940 .groups
12941 .iter()
12942 .flat_map(|group| group.stack.iter())
12943 .copied()
12944 .collect();
12945
12946 all_selections
12947 .into_iter()
12948 .partition(|s| columnar_selection_ids.contains(&s.id))
12949 } else {
12950 (Vec::new(), all_selections)
12951 }
12952 };
12953
12954 let mut state = self
12955 .add_selections_state
12956 .take()
12957 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12958
12959 for selection in new_selections_to_columnarize {
12960 let range = selection.display_range(&display_map).sorted();
12961 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12962 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12963 let positions = start_x.min(end_x)..start_x.max(end_x);
12964 let mut stack = Vec::new();
12965 for row in range.start.row().0..=range.end.row().0 {
12966 if let Some(selection) = self.selections.build_columnar_selection(
12967 &display_map,
12968 DisplayRow(row),
12969 &positions,
12970 selection.reversed,
12971 &text_layout_details,
12972 ) {
12973 stack.push(selection.id);
12974 columnar_selections.push(selection);
12975 }
12976 }
12977 if !stack.is_empty() {
12978 if above {
12979 stack.reverse();
12980 }
12981 state.groups.push(AddSelectionsGroup { above, stack });
12982 }
12983 }
12984
12985 let mut final_selections = Vec::new();
12986 let end_row = if above {
12987 DisplayRow(0)
12988 } else {
12989 display_map.max_point().row()
12990 };
12991
12992 let mut last_added_item_per_group = HashMap::default();
12993 for group in state.groups.iter_mut() {
12994 if let Some(last_id) = group.stack.last() {
12995 last_added_item_per_group.insert(*last_id, group);
12996 }
12997 }
12998
12999 for selection in columnar_selections {
13000 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13001 if above == group.above {
13002 let range = selection.display_range(&display_map).sorted();
13003 debug_assert_eq!(range.start.row(), range.end.row());
13004 let mut row = range.start.row();
13005 let positions =
13006 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13007 px(start)..px(end)
13008 } else {
13009 let start_x =
13010 display_map.x_for_display_point(range.start, &text_layout_details);
13011 let end_x =
13012 display_map.x_for_display_point(range.end, &text_layout_details);
13013 start_x.min(end_x)..start_x.max(end_x)
13014 };
13015
13016 let mut maybe_new_selection = None;
13017 while row != end_row {
13018 if above {
13019 row.0 -= 1;
13020 } else {
13021 row.0 += 1;
13022 }
13023 if let Some(new_selection) = self.selections.build_columnar_selection(
13024 &display_map,
13025 row,
13026 &positions,
13027 selection.reversed,
13028 &text_layout_details,
13029 ) {
13030 maybe_new_selection = Some(new_selection);
13031 break;
13032 }
13033 }
13034
13035 if let Some(new_selection) = maybe_new_selection {
13036 group.stack.push(new_selection.id);
13037 if above {
13038 final_selections.push(new_selection);
13039 final_selections.push(selection);
13040 } else {
13041 final_selections.push(selection);
13042 final_selections.push(new_selection);
13043 }
13044 } else {
13045 final_selections.push(selection);
13046 }
13047 } else {
13048 group.stack.pop();
13049 }
13050 } else {
13051 final_selections.push(selection);
13052 }
13053 }
13054
13055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13056 s.select(final_selections);
13057 });
13058
13059 let final_selection_ids: HashSet<_> = self
13060 .selections
13061 .all::<Point>(cx)
13062 .iter()
13063 .map(|s| s.id)
13064 .collect();
13065 state.groups.retain_mut(|group| {
13066 // selections might get merged above so we remove invalid items from stacks
13067 group.stack.retain(|id| final_selection_ids.contains(id));
13068
13069 // single selection in stack can be treated as initial state
13070 group.stack.len() > 1
13071 });
13072
13073 if !state.groups.is_empty() {
13074 self.add_selections_state = Some(state);
13075 }
13076 }
13077
13078 fn select_match_ranges(
13079 &mut self,
13080 range: Range<usize>,
13081 reversed: bool,
13082 replace_newest: bool,
13083 auto_scroll: Option<Autoscroll>,
13084 window: &mut Window,
13085 cx: &mut Context<Editor>,
13086 ) {
13087 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13088 self.change_selections(auto_scroll, window, cx, |s| {
13089 if replace_newest {
13090 s.delete(s.newest_anchor().id);
13091 }
13092 if reversed {
13093 s.insert_range(range.end..range.start);
13094 } else {
13095 s.insert_range(range);
13096 }
13097 });
13098 }
13099
13100 pub fn select_next_match_internal(
13101 &mut self,
13102 display_map: &DisplaySnapshot,
13103 replace_newest: bool,
13104 autoscroll: Option<Autoscroll>,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) -> Result<()> {
13108 let buffer = &display_map.buffer_snapshot;
13109 let mut selections = self.selections.all::<usize>(cx);
13110 if let Some(mut select_next_state) = self.select_next_state.take() {
13111 let query = &select_next_state.query;
13112 if !select_next_state.done {
13113 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13114 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13115 let mut next_selected_range = None;
13116
13117 let bytes_after_last_selection =
13118 buffer.bytes_in_range(last_selection.end..buffer.len());
13119 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13120 let query_matches = query
13121 .stream_find_iter(bytes_after_last_selection)
13122 .map(|result| (last_selection.end, result))
13123 .chain(
13124 query
13125 .stream_find_iter(bytes_before_first_selection)
13126 .map(|result| (0, result)),
13127 );
13128
13129 for (start_offset, query_match) in query_matches {
13130 let query_match = query_match.unwrap(); // can only fail due to I/O
13131 let offset_range =
13132 start_offset + query_match.start()..start_offset + query_match.end();
13133 let display_range = offset_range.start.to_display_point(display_map)
13134 ..offset_range.end.to_display_point(display_map);
13135
13136 if !select_next_state.wordwise
13137 || (!movement::is_inside_word(display_map, display_range.start)
13138 && !movement::is_inside_word(display_map, display_range.end))
13139 {
13140 // TODO: This is n^2, because we might check all the selections
13141 if !selections
13142 .iter()
13143 .any(|selection| selection.range().overlaps(&offset_range))
13144 {
13145 next_selected_range = Some(offset_range);
13146 break;
13147 }
13148 }
13149 }
13150
13151 if let Some(next_selected_range) = next_selected_range {
13152 self.select_match_ranges(
13153 next_selected_range,
13154 last_selection.reversed,
13155 replace_newest,
13156 autoscroll,
13157 window,
13158 cx,
13159 );
13160 } else {
13161 select_next_state.done = true;
13162 }
13163 }
13164
13165 self.select_next_state = Some(select_next_state);
13166 } else {
13167 let mut only_carets = true;
13168 let mut same_text_selected = true;
13169 let mut selected_text = None;
13170
13171 let mut selections_iter = selections.iter().peekable();
13172 while let Some(selection) = selections_iter.next() {
13173 if selection.start != selection.end {
13174 only_carets = false;
13175 }
13176
13177 if same_text_selected {
13178 if selected_text.is_none() {
13179 selected_text =
13180 Some(buffer.text_for_range(selection.range()).collect::<String>());
13181 }
13182
13183 if let Some(next_selection) = selections_iter.peek() {
13184 if next_selection.range().len() == selection.range().len() {
13185 let next_selected_text = buffer
13186 .text_for_range(next_selection.range())
13187 .collect::<String>();
13188 if Some(next_selected_text) != selected_text {
13189 same_text_selected = false;
13190 selected_text = None;
13191 }
13192 } else {
13193 same_text_selected = false;
13194 selected_text = None;
13195 }
13196 }
13197 }
13198 }
13199
13200 if only_carets {
13201 for selection in &mut selections {
13202 let word_range = movement::surrounding_word(
13203 display_map,
13204 selection.start.to_display_point(display_map),
13205 );
13206 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13207 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13208 selection.goal = SelectionGoal::None;
13209 selection.reversed = false;
13210 self.select_match_ranges(
13211 selection.start..selection.end,
13212 selection.reversed,
13213 replace_newest,
13214 autoscroll,
13215 window,
13216 cx,
13217 );
13218 }
13219
13220 if selections.len() == 1 {
13221 let selection = selections
13222 .last()
13223 .expect("ensured that there's only one selection");
13224 let query = buffer
13225 .text_for_range(selection.start..selection.end)
13226 .collect::<String>();
13227 let is_empty = query.is_empty();
13228 let select_state = SelectNextState {
13229 query: AhoCorasick::new(&[query])?,
13230 wordwise: true,
13231 done: is_empty,
13232 };
13233 self.select_next_state = Some(select_state);
13234 } else {
13235 self.select_next_state = None;
13236 }
13237 } else if let Some(selected_text) = selected_text {
13238 self.select_next_state = Some(SelectNextState {
13239 query: AhoCorasick::new(&[selected_text])?,
13240 wordwise: false,
13241 done: false,
13242 });
13243 self.select_next_match_internal(
13244 display_map,
13245 replace_newest,
13246 autoscroll,
13247 window,
13248 cx,
13249 )?;
13250 }
13251 }
13252 Ok(())
13253 }
13254
13255 pub fn select_all_matches(
13256 &mut self,
13257 _action: &SelectAllMatches,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) -> Result<()> {
13261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13262
13263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13264
13265 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13266 let Some(select_next_state) = self.select_next_state.as_mut() else {
13267 return Ok(());
13268 };
13269 if select_next_state.done {
13270 return Ok(());
13271 }
13272
13273 let mut new_selections = Vec::new();
13274
13275 let reversed = self.selections.oldest::<usize>(cx).reversed;
13276 let buffer = &display_map.buffer_snapshot;
13277 let query_matches = select_next_state
13278 .query
13279 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13280
13281 for query_match in query_matches.into_iter() {
13282 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13283 let offset_range = if reversed {
13284 query_match.end()..query_match.start()
13285 } else {
13286 query_match.start()..query_match.end()
13287 };
13288 let display_range = offset_range.start.to_display_point(&display_map)
13289 ..offset_range.end.to_display_point(&display_map);
13290
13291 if !select_next_state.wordwise
13292 || (!movement::is_inside_word(&display_map, display_range.start)
13293 && !movement::is_inside_word(&display_map, display_range.end))
13294 {
13295 new_selections.push(offset_range.start..offset_range.end);
13296 }
13297 }
13298
13299 select_next_state.done = true;
13300 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13301 self.change_selections(None, window, cx, |selections| {
13302 selections.select_ranges(new_selections)
13303 });
13304
13305 Ok(())
13306 }
13307
13308 pub fn select_next(
13309 &mut self,
13310 action: &SelectNext,
13311 window: &mut Window,
13312 cx: &mut Context<Self>,
13313 ) -> Result<()> {
13314 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13316 self.select_next_match_internal(
13317 &display_map,
13318 action.replace_newest,
13319 Some(Autoscroll::newest()),
13320 window,
13321 cx,
13322 )?;
13323 Ok(())
13324 }
13325
13326 pub fn select_previous(
13327 &mut self,
13328 action: &SelectPrevious,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) -> Result<()> {
13332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13334 let buffer = &display_map.buffer_snapshot;
13335 let mut selections = self.selections.all::<usize>(cx);
13336 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13337 let query = &select_prev_state.query;
13338 if !select_prev_state.done {
13339 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13340 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13341 let mut next_selected_range = None;
13342 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13343 let bytes_before_last_selection =
13344 buffer.reversed_bytes_in_range(0..last_selection.start);
13345 let bytes_after_first_selection =
13346 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13347 let query_matches = query
13348 .stream_find_iter(bytes_before_last_selection)
13349 .map(|result| (last_selection.start, result))
13350 .chain(
13351 query
13352 .stream_find_iter(bytes_after_first_selection)
13353 .map(|result| (buffer.len(), result)),
13354 );
13355 for (end_offset, query_match) in query_matches {
13356 let query_match = query_match.unwrap(); // can only fail due to I/O
13357 let offset_range =
13358 end_offset - query_match.end()..end_offset - query_match.start();
13359 let display_range = offset_range.start.to_display_point(&display_map)
13360 ..offset_range.end.to_display_point(&display_map);
13361
13362 if !select_prev_state.wordwise
13363 || (!movement::is_inside_word(&display_map, display_range.start)
13364 && !movement::is_inside_word(&display_map, display_range.end))
13365 {
13366 next_selected_range = Some(offset_range);
13367 break;
13368 }
13369 }
13370
13371 if let Some(next_selected_range) = next_selected_range {
13372 self.select_match_ranges(
13373 next_selected_range,
13374 last_selection.reversed,
13375 action.replace_newest,
13376 Some(Autoscroll::newest()),
13377 window,
13378 cx,
13379 );
13380 } else {
13381 select_prev_state.done = true;
13382 }
13383 }
13384
13385 self.select_prev_state = Some(select_prev_state);
13386 } else {
13387 let mut only_carets = true;
13388 let mut same_text_selected = true;
13389 let mut selected_text = None;
13390
13391 let mut selections_iter = selections.iter().peekable();
13392 while let Some(selection) = selections_iter.next() {
13393 if selection.start != selection.end {
13394 only_carets = false;
13395 }
13396
13397 if same_text_selected {
13398 if selected_text.is_none() {
13399 selected_text =
13400 Some(buffer.text_for_range(selection.range()).collect::<String>());
13401 }
13402
13403 if let Some(next_selection) = selections_iter.peek() {
13404 if next_selection.range().len() == selection.range().len() {
13405 let next_selected_text = buffer
13406 .text_for_range(next_selection.range())
13407 .collect::<String>();
13408 if Some(next_selected_text) != selected_text {
13409 same_text_selected = false;
13410 selected_text = None;
13411 }
13412 } else {
13413 same_text_selected = false;
13414 selected_text = None;
13415 }
13416 }
13417 }
13418 }
13419
13420 if only_carets {
13421 for selection in &mut selections {
13422 let word_range = movement::surrounding_word(
13423 &display_map,
13424 selection.start.to_display_point(&display_map),
13425 );
13426 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13427 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13428 selection.goal = SelectionGoal::None;
13429 selection.reversed = false;
13430 self.select_match_ranges(
13431 selection.start..selection.end,
13432 selection.reversed,
13433 action.replace_newest,
13434 Some(Autoscroll::newest()),
13435 window,
13436 cx,
13437 );
13438 }
13439 if selections.len() == 1 {
13440 let selection = selections
13441 .last()
13442 .expect("ensured that there's only one selection");
13443 let query = buffer
13444 .text_for_range(selection.start..selection.end)
13445 .collect::<String>();
13446 let is_empty = query.is_empty();
13447 let select_state = SelectNextState {
13448 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13449 wordwise: true,
13450 done: is_empty,
13451 };
13452 self.select_prev_state = Some(select_state);
13453 } else {
13454 self.select_prev_state = None;
13455 }
13456 } else if let Some(selected_text) = selected_text {
13457 self.select_prev_state = Some(SelectNextState {
13458 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13459 wordwise: false,
13460 done: false,
13461 });
13462 self.select_previous(action, window, cx)?;
13463 }
13464 }
13465 Ok(())
13466 }
13467
13468 pub fn find_next_match(
13469 &mut self,
13470 _: &FindNextMatch,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) -> Result<()> {
13474 let selections = self.selections.disjoint_anchors();
13475 match selections.first() {
13476 Some(first) if selections.len() >= 2 => {
13477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13478 s.select_ranges([first.range()]);
13479 });
13480 }
13481 _ => self.select_next(
13482 &SelectNext {
13483 replace_newest: true,
13484 },
13485 window,
13486 cx,
13487 )?,
13488 }
13489 Ok(())
13490 }
13491
13492 pub fn find_previous_match(
13493 &mut self,
13494 _: &FindPreviousMatch,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) -> Result<()> {
13498 let selections = self.selections.disjoint_anchors();
13499 match selections.last() {
13500 Some(last) if selections.len() >= 2 => {
13501 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13502 s.select_ranges([last.range()]);
13503 });
13504 }
13505 _ => self.select_previous(
13506 &SelectPrevious {
13507 replace_newest: true,
13508 },
13509 window,
13510 cx,
13511 )?,
13512 }
13513 Ok(())
13514 }
13515
13516 pub fn toggle_comments(
13517 &mut self,
13518 action: &ToggleComments,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if self.read_only(cx) {
13523 return;
13524 }
13525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13526 let text_layout_details = &self.text_layout_details(window);
13527 self.transact(window, cx, |this, window, cx| {
13528 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13529 let mut edits = Vec::new();
13530 let mut selection_edit_ranges = Vec::new();
13531 let mut last_toggled_row = None;
13532 let snapshot = this.buffer.read(cx).read(cx);
13533 let empty_str: Arc<str> = Arc::default();
13534 let mut suffixes_inserted = Vec::new();
13535 let ignore_indent = action.ignore_indent;
13536
13537 fn comment_prefix_range(
13538 snapshot: &MultiBufferSnapshot,
13539 row: MultiBufferRow,
13540 comment_prefix: &str,
13541 comment_prefix_whitespace: &str,
13542 ignore_indent: bool,
13543 ) -> Range<Point> {
13544 let indent_size = if ignore_indent {
13545 0
13546 } else {
13547 snapshot.indent_size_for_line(row).len
13548 };
13549
13550 let start = Point::new(row.0, indent_size);
13551
13552 let mut line_bytes = snapshot
13553 .bytes_in_range(start..snapshot.max_point())
13554 .flatten()
13555 .copied();
13556
13557 // If this line currently begins with the line comment prefix, then record
13558 // the range containing the prefix.
13559 if line_bytes
13560 .by_ref()
13561 .take(comment_prefix.len())
13562 .eq(comment_prefix.bytes())
13563 {
13564 // Include any whitespace that matches the comment prefix.
13565 let matching_whitespace_len = line_bytes
13566 .zip(comment_prefix_whitespace.bytes())
13567 .take_while(|(a, b)| a == b)
13568 .count() as u32;
13569 let end = Point::new(
13570 start.row,
13571 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13572 );
13573 start..end
13574 } else {
13575 start..start
13576 }
13577 }
13578
13579 fn comment_suffix_range(
13580 snapshot: &MultiBufferSnapshot,
13581 row: MultiBufferRow,
13582 comment_suffix: &str,
13583 comment_suffix_has_leading_space: bool,
13584 ) -> Range<Point> {
13585 let end = Point::new(row.0, snapshot.line_len(row));
13586 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13587
13588 let mut line_end_bytes = snapshot
13589 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13590 .flatten()
13591 .copied();
13592
13593 let leading_space_len = if suffix_start_column > 0
13594 && line_end_bytes.next() == Some(b' ')
13595 && comment_suffix_has_leading_space
13596 {
13597 1
13598 } else {
13599 0
13600 };
13601
13602 // If this line currently begins with the line comment prefix, then record
13603 // the range containing the prefix.
13604 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13605 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13606 start..end
13607 } else {
13608 end..end
13609 }
13610 }
13611
13612 // TODO: Handle selections that cross excerpts
13613 for selection in &mut selections {
13614 let start_column = snapshot
13615 .indent_size_for_line(MultiBufferRow(selection.start.row))
13616 .len;
13617 let language = if let Some(language) =
13618 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13619 {
13620 language
13621 } else {
13622 continue;
13623 };
13624
13625 selection_edit_ranges.clear();
13626
13627 // If multiple selections contain a given row, avoid processing that
13628 // row more than once.
13629 let mut start_row = MultiBufferRow(selection.start.row);
13630 if last_toggled_row == Some(start_row) {
13631 start_row = start_row.next_row();
13632 }
13633 let end_row =
13634 if selection.end.row > selection.start.row && selection.end.column == 0 {
13635 MultiBufferRow(selection.end.row - 1)
13636 } else {
13637 MultiBufferRow(selection.end.row)
13638 };
13639 last_toggled_row = Some(end_row);
13640
13641 if start_row > end_row {
13642 continue;
13643 }
13644
13645 // If the language has line comments, toggle those.
13646 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13647
13648 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13649 if ignore_indent {
13650 full_comment_prefixes = full_comment_prefixes
13651 .into_iter()
13652 .map(|s| Arc::from(s.trim_end()))
13653 .collect();
13654 }
13655
13656 if !full_comment_prefixes.is_empty() {
13657 let first_prefix = full_comment_prefixes
13658 .first()
13659 .expect("prefixes is non-empty");
13660 let prefix_trimmed_lengths = full_comment_prefixes
13661 .iter()
13662 .map(|p| p.trim_end_matches(' ').len())
13663 .collect::<SmallVec<[usize; 4]>>();
13664
13665 let mut all_selection_lines_are_comments = true;
13666
13667 for row in start_row.0..=end_row.0 {
13668 let row = MultiBufferRow(row);
13669 if start_row < end_row && snapshot.is_line_blank(row) {
13670 continue;
13671 }
13672
13673 let prefix_range = full_comment_prefixes
13674 .iter()
13675 .zip(prefix_trimmed_lengths.iter().copied())
13676 .map(|(prefix, trimmed_prefix_len)| {
13677 comment_prefix_range(
13678 snapshot.deref(),
13679 row,
13680 &prefix[..trimmed_prefix_len],
13681 &prefix[trimmed_prefix_len..],
13682 ignore_indent,
13683 )
13684 })
13685 .max_by_key(|range| range.end.column - range.start.column)
13686 .expect("prefixes is non-empty");
13687
13688 if prefix_range.is_empty() {
13689 all_selection_lines_are_comments = false;
13690 }
13691
13692 selection_edit_ranges.push(prefix_range);
13693 }
13694
13695 if all_selection_lines_are_comments {
13696 edits.extend(
13697 selection_edit_ranges
13698 .iter()
13699 .cloned()
13700 .map(|range| (range, empty_str.clone())),
13701 );
13702 } else {
13703 let min_column = selection_edit_ranges
13704 .iter()
13705 .map(|range| range.start.column)
13706 .min()
13707 .unwrap_or(0);
13708 edits.extend(selection_edit_ranges.iter().map(|range| {
13709 let position = Point::new(range.start.row, min_column);
13710 (position..position, first_prefix.clone())
13711 }));
13712 }
13713 } else if let Some((full_comment_prefix, comment_suffix)) =
13714 language.block_comment_delimiters()
13715 {
13716 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13717 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13718 let prefix_range = comment_prefix_range(
13719 snapshot.deref(),
13720 start_row,
13721 comment_prefix,
13722 comment_prefix_whitespace,
13723 ignore_indent,
13724 );
13725 let suffix_range = comment_suffix_range(
13726 snapshot.deref(),
13727 end_row,
13728 comment_suffix.trim_start_matches(' '),
13729 comment_suffix.starts_with(' '),
13730 );
13731
13732 if prefix_range.is_empty() || suffix_range.is_empty() {
13733 edits.push((
13734 prefix_range.start..prefix_range.start,
13735 full_comment_prefix.clone(),
13736 ));
13737 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13738 suffixes_inserted.push((end_row, comment_suffix.len()));
13739 } else {
13740 edits.push((prefix_range, empty_str.clone()));
13741 edits.push((suffix_range, empty_str.clone()));
13742 }
13743 } else {
13744 continue;
13745 }
13746 }
13747
13748 drop(snapshot);
13749 this.buffer.update(cx, |buffer, cx| {
13750 buffer.edit(edits, None, cx);
13751 });
13752
13753 // Adjust selections so that they end before any comment suffixes that
13754 // were inserted.
13755 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13756 let mut selections = this.selections.all::<Point>(cx);
13757 let snapshot = this.buffer.read(cx).read(cx);
13758 for selection in &mut selections {
13759 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13760 match row.cmp(&MultiBufferRow(selection.end.row)) {
13761 Ordering::Less => {
13762 suffixes_inserted.next();
13763 continue;
13764 }
13765 Ordering::Greater => break,
13766 Ordering::Equal => {
13767 if selection.end.column == snapshot.line_len(row) {
13768 if selection.is_empty() {
13769 selection.start.column -= suffix_len as u32;
13770 }
13771 selection.end.column -= suffix_len as u32;
13772 }
13773 break;
13774 }
13775 }
13776 }
13777 }
13778
13779 drop(snapshot);
13780 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13781 s.select(selections)
13782 });
13783
13784 let selections = this.selections.all::<Point>(cx);
13785 let selections_on_single_row = selections.windows(2).all(|selections| {
13786 selections[0].start.row == selections[1].start.row
13787 && selections[0].end.row == selections[1].end.row
13788 && selections[0].start.row == selections[0].end.row
13789 });
13790 let selections_selecting = selections
13791 .iter()
13792 .any(|selection| selection.start != selection.end);
13793 let advance_downwards = action.advance_downwards
13794 && selections_on_single_row
13795 && !selections_selecting
13796 && !matches!(this.mode, EditorMode::SingleLine { .. });
13797
13798 if advance_downwards {
13799 let snapshot = this.buffer.read(cx).snapshot(cx);
13800
13801 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13802 s.move_cursors_with(|display_snapshot, display_point, _| {
13803 let mut point = display_point.to_point(display_snapshot);
13804 point.row += 1;
13805 point = snapshot.clip_point(point, Bias::Left);
13806 let display_point = point.to_display_point(display_snapshot);
13807 let goal = SelectionGoal::HorizontalPosition(
13808 display_snapshot
13809 .x_for_display_point(display_point, text_layout_details)
13810 .into(),
13811 );
13812 (display_point, goal)
13813 })
13814 });
13815 }
13816 });
13817 }
13818
13819 pub fn select_enclosing_symbol(
13820 &mut self,
13821 _: &SelectEnclosingSymbol,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13826
13827 let buffer = self.buffer.read(cx).snapshot(cx);
13828 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13829
13830 fn update_selection(
13831 selection: &Selection<usize>,
13832 buffer_snap: &MultiBufferSnapshot,
13833 ) -> Option<Selection<usize>> {
13834 let cursor = selection.head();
13835 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13836 for symbol in symbols.iter().rev() {
13837 let start = symbol.range.start.to_offset(buffer_snap);
13838 let end = symbol.range.end.to_offset(buffer_snap);
13839 let new_range = start..end;
13840 if start < selection.start || end > selection.end {
13841 return Some(Selection {
13842 id: selection.id,
13843 start: new_range.start,
13844 end: new_range.end,
13845 goal: SelectionGoal::None,
13846 reversed: selection.reversed,
13847 });
13848 }
13849 }
13850 None
13851 }
13852
13853 let mut selected_larger_symbol = false;
13854 let new_selections = old_selections
13855 .iter()
13856 .map(|selection| match update_selection(selection, &buffer) {
13857 Some(new_selection) => {
13858 if new_selection.range() != selection.range() {
13859 selected_larger_symbol = true;
13860 }
13861 new_selection
13862 }
13863 None => selection.clone(),
13864 })
13865 .collect::<Vec<_>>();
13866
13867 if selected_larger_symbol {
13868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13869 s.select(new_selections);
13870 });
13871 }
13872 }
13873
13874 pub fn select_larger_syntax_node(
13875 &mut self,
13876 _: &SelectLargerSyntaxNode,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let Some(visible_row_count) = self.visible_row_count() else {
13881 return;
13882 };
13883 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13884 if old_selections.is_empty() {
13885 return;
13886 }
13887
13888 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13889
13890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13891 let buffer = self.buffer.read(cx).snapshot(cx);
13892
13893 let mut selected_larger_node = false;
13894 let mut new_selections = old_selections
13895 .iter()
13896 .map(|selection| {
13897 let old_range = selection.start..selection.end;
13898
13899 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13900 // manually select word at selection
13901 if ["string_content", "inline"].contains(&node.kind()) {
13902 let word_range = {
13903 let display_point = buffer
13904 .offset_to_point(old_range.start)
13905 .to_display_point(&display_map);
13906 let Range { start, end } =
13907 movement::surrounding_word(&display_map, display_point);
13908 start.to_point(&display_map).to_offset(&buffer)
13909 ..end.to_point(&display_map).to_offset(&buffer)
13910 };
13911 // ignore if word is already selected
13912 if !word_range.is_empty() && old_range != word_range {
13913 let last_word_range = {
13914 let display_point = buffer
13915 .offset_to_point(old_range.end)
13916 .to_display_point(&display_map);
13917 let Range { start, end } =
13918 movement::surrounding_word(&display_map, display_point);
13919 start.to_point(&display_map).to_offset(&buffer)
13920 ..end.to_point(&display_map).to_offset(&buffer)
13921 };
13922 // only select word if start and end point belongs to same word
13923 if word_range == last_word_range {
13924 selected_larger_node = true;
13925 return Selection {
13926 id: selection.id,
13927 start: word_range.start,
13928 end: word_range.end,
13929 goal: SelectionGoal::None,
13930 reversed: selection.reversed,
13931 };
13932 }
13933 }
13934 }
13935 }
13936
13937 let mut new_range = old_range.clone();
13938 while let Some((_node, containing_range)) =
13939 buffer.syntax_ancestor(new_range.clone())
13940 {
13941 new_range = match containing_range {
13942 MultiOrSingleBufferOffsetRange::Single(_) => break,
13943 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13944 };
13945 if !display_map.intersects_fold(new_range.start)
13946 && !display_map.intersects_fold(new_range.end)
13947 {
13948 break;
13949 }
13950 }
13951
13952 selected_larger_node |= new_range != old_range;
13953 Selection {
13954 id: selection.id,
13955 start: new_range.start,
13956 end: new_range.end,
13957 goal: SelectionGoal::None,
13958 reversed: selection.reversed,
13959 }
13960 })
13961 .collect::<Vec<_>>();
13962
13963 if !selected_larger_node {
13964 return; // don't put this call in the history
13965 }
13966
13967 // scroll based on transformation done to the last selection created by the user
13968 let (last_old, last_new) = old_selections
13969 .last()
13970 .zip(new_selections.last().cloned())
13971 .expect("old_selections isn't empty");
13972
13973 // revert selection
13974 let is_selection_reversed = {
13975 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13976 new_selections.last_mut().expect("checked above").reversed =
13977 should_newest_selection_be_reversed;
13978 should_newest_selection_be_reversed
13979 };
13980
13981 if selected_larger_node {
13982 self.select_syntax_node_history.disable_clearing = true;
13983 self.change_selections(None, window, cx, |s| {
13984 s.select(new_selections.clone());
13985 });
13986 self.select_syntax_node_history.disable_clearing = false;
13987 }
13988
13989 let start_row = last_new.start.to_display_point(&display_map).row().0;
13990 let end_row = last_new.end.to_display_point(&display_map).row().0;
13991 let selection_height = end_row - start_row + 1;
13992 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13993
13994 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13995 let scroll_behavior = if fits_on_the_screen {
13996 self.request_autoscroll(Autoscroll::fit(), cx);
13997 SelectSyntaxNodeScrollBehavior::FitSelection
13998 } else if is_selection_reversed {
13999 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14000 SelectSyntaxNodeScrollBehavior::CursorTop
14001 } else {
14002 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14003 SelectSyntaxNodeScrollBehavior::CursorBottom
14004 };
14005
14006 self.select_syntax_node_history.push((
14007 old_selections,
14008 scroll_behavior,
14009 is_selection_reversed,
14010 ));
14011 }
14012
14013 pub fn select_smaller_syntax_node(
14014 &mut self,
14015 _: &SelectSmallerSyntaxNode,
14016 window: &mut Window,
14017 cx: &mut Context<Self>,
14018 ) {
14019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14020
14021 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14022 self.select_syntax_node_history.pop()
14023 {
14024 if let Some(selection) = selections.last_mut() {
14025 selection.reversed = is_selection_reversed;
14026 }
14027
14028 self.select_syntax_node_history.disable_clearing = true;
14029 self.change_selections(None, window, cx, |s| {
14030 s.select(selections.to_vec());
14031 });
14032 self.select_syntax_node_history.disable_clearing = false;
14033
14034 match scroll_behavior {
14035 SelectSyntaxNodeScrollBehavior::CursorTop => {
14036 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14037 }
14038 SelectSyntaxNodeScrollBehavior::FitSelection => {
14039 self.request_autoscroll(Autoscroll::fit(), cx);
14040 }
14041 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14042 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14043 }
14044 }
14045 }
14046 }
14047
14048 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14049 if !EditorSettings::get_global(cx).gutter.runnables {
14050 self.clear_tasks();
14051 return Task::ready(());
14052 }
14053 let project = self.project.as_ref().map(Entity::downgrade);
14054 let task_sources = self.lsp_task_sources(cx);
14055 let multi_buffer = self.buffer.downgrade();
14056 cx.spawn_in(window, async move |editor, cx| {
14057 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14058 let Some(project) = project.and_then(|p| p.upgrade()) else {
14059 return;
14060 };
14061 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14062 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14063 }) else {
14064 return;
14065 };
14066
14067 let hide_runnables = project
14068 .update(cx, |project, cx| {
14069 // Do not display any test indicators in non-dev server remote projects.
14070 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14071 })
14072 .unwrap_or(true);
14073 if hide_runnables {
14074 return;
14075 }
14076 let new_rows =
14077 cx.background_spawn({
14078 let snapshot = display_snapshot.clone();
14079 async move {
14080 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14081 }
14082 })
14083 .await;
14084 let Ok(lsp_tasks) =
14085 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14086 else {
14087 return;
14088 };
14089 let lsp_tasks = lsp_tasks.await;
14090
14091 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14092 lsp_tasks
14093 .into_iter()
14094 .flat_map(|(kind, tasks)| {
14095 tasks.into_iter().filter_map(move |(location, task)| {
14096 Some((kind.clone(), location?, task))
14097 })
14098 })
14099 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14100 let buffer = location.target.buffer;
14101 let buffer_snapshot = buffer.read(cx).snapshot();
14102 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14103 |(excerpt_id, snapshot, _)| {
14104 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14105 display_snapshot
14106 .buffer_snapshot
14107 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14108 } else {
14109 None
14110 }
14111 },
14112 );
14113 if let Some(offset) = offset {
14114 let task_buffer_range =
14115 location.target.range.to_point(&buffer_snapshot);
14116 let context_buffer_range =
14117 task_buffer_range.to_offset(&buffer_snapshot);
14118 let context_range = BufferOffset(context_buffer_range.start)
14119 ..BufferOffset(context_buffer_range.end);
14120
14121 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14122 .or_insert_with(|| RunnableTasks {
14123 templates: Vec::new(),
14124 offset,
14125 column: task_buffer_range.start.column,
14126 extra_variables: HashMap::default(),
14127 context_range,
14128 })
14129 .templates
14130 .push((kind, task.original_task().clone()));
14131 }
14132
14133 acc
14134 })
14135 }) else {
14136 return;
14137 };
14138
14139 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14140 buffer.language_settings(cx).tasks.prefer_lsp
14141 }) else {
14142 return;
14143 };
14144
14145 let rows = Self::runnable_rows(
14146 project,
14147 display_snapshot,
14148 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14149 new_rows,
14150 cx.clone(),
14151 )
14152 .await;
14153 editor
14154 .update(cx, |editor, _| {
14155 editor.clear_tasks();
14156 for (key, mut value) in rows {
14157 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14158 value.templates.extend(lsp_tasks.templates);
14159 }
14160
14161 editor.insert_tasks(key, value);
14162 }
14163 for (key, value) in lsp_tasks_by_rows {
14164 editor.insert_tasks(key, value);
14165 }
14166 })
14167 .ok();
14168 })
14169 }
14170 fn fetch_runnable_ranges(
14171 snapshot: &DisplaySnapshot,
14172 range: Range<Anchor>,
14173 ) -> Vec<language::RunnableRange> {
14174 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14175 }
14176
14177 fn runnable_rows(
14178 project: Entity<Project>,
14179 snapshot: DisplaySnapshot,
14180 prefer_lsp: bool,
14181 runnable_ranges: Vec<RunnableRange>,
14182 cx: AsyncWindowContext,
14183 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14184 cx.spawn(async move |cx| {
14185 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14186 for mut runnable in runnable_ranges {
14187 let Some(tasks) = cx
14188 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14189 .ok()
14190 else {
14191 continue;
14192 };
14193 let mut tasks = tasks.await;
14194
14195 if prefer_lsp {
14196 tasks.retain(|(task_kind, _)| {
14197 !matches!(task_kind, TaskSourceKind::Language { .. })
14198 });
14199 }
14200 if tasks.is_empty() {
14201 continue;
14202 }
14203
14204 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14205 let Some(row) = snapshot
14206 .buffer_snapshot
14207 .buffer_line_for_row(MultiBufferRow(point.row))
14208 .map(|(_, range)| range.start.row)
14209 else {
14210 continue;
14211 };
14212
14213 let context_range =
14214 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14215 runnable_rows.push((
14216 (runnable.buffer_id, row),
14217 RunnableTasks {
14218 templates: tasks,
14219 offset: snapshot
14220 .buffer_snapshot
14221 .anchor_before(runnable.run_range.start),
14222 context_range,
14223 column: point.column,
14224 extra_variables: runnable.extra_captures,
14225 },
14226 ));
14227 }
14228 runnable_rows
14229 })
14230 }
14231
14232 fn templates_with_tags(
14233 project: &Entity<Project>,
14234 runnable: &mut Runnable,
14235 cx: &mut App,
14236 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14237 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14238 let (worktree_id, file) = project
14239 .buffer_for_id(runnable.buffer, cx)
14240 .and_then(|buffer| buffer.read(cx).file())
14241 .map(|file| (file.worktree_id(cx), file.clone()))
14242 .unzip();
14243
14244 (
14245 project.task_store().read(cx).task_inventory().cloned(),
14246 worktree_id,
14247 file,
14248 )
14249 });
14250
14251 let tags = mem::take(&mut runnable.tags);
14252 let language = runnable.language.clone();
14253 cx.spawn(async move |cx| {
14254 let mut templates_with_tags = Vec::new();
14255 if let Some(inventory) = inventory {
14256 for RunnableTag(tag) in tags {
14257 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14258 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14259 }) else {
14260 return templates_with_tags;
14261 };
14262 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14263 move |(_, template)| {
14264 template.tags.iter().any(|source_tag| source_tag == &tag)
14265 },
14266 ));
14267 }
14268 }
14269 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14270
14271 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14272 // Strongest source wins; if we have worktree tag binding, prefer that to
14273 // global and language bindings;
14274 // if we have a global binding, prefer that to language binding.
14275 let first_mismatch = templates_with_tags
14276 .iter()
14277 .position(|(tag_source, _)| tag_source != leading_tag_source);
14278 if let Some(index) = first_mismatch {
14279 templates_with_tags.truncate(index);
14280 }
14281 }
14282
14283 templates_with_tags
14284 })
14285 }
14286
14287 pub fn move_to_enclosing_bracket(
14288 &mut self,
14289 _: &MoveToEnclosingBracket,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) {
14293 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14295 s.move_offsets_with(|snapshot, selection| {
14296 let Some(enclosing_bracket_ranges) =
14297 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14298 else {
14299 return;
14300 };
14301
14302 let mut best_length = usize::MAX;
14303 let mut best_inside = false;
14304 let mut best_in_bracket_range = false;
14305 let mut best_destination = None;
14306 for (open, close) in enclosing_bracket_ranges {
14307 let close = close.to_inclusive();
14308 let length = close.end() - open.start;
14309 let inside = selection.start >= open.end && selection.end <= *close.start();
14310 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14311 || close.contains(&selection.head());
14312
14313 // If best is next to a bracket and current isn't, skip
14314 if !in_bracket_range && best_in_bracket_range {
14315 continue;
14316 }
14317
14318 // Prefer smaller lengths unless best is inside and current isn't
14319 if length > best_length && (best_inside || !inside) {
14320 continue;
14321 }
14322
14323 best_length = length;
14324 best_inside = inside;
14325 best_in_bracket_range = in_bracket_range;
14326 best_destination = Some(
14327 if close.contains(&selection.start) && close.contains(&selection.end) {
14328 if inside { open.end } else { open.start }
14329 } else if inside {
14330 *close.start()
14331 } else {
14332 *close.end()
14333 },
14334 );
14335 }
14336
14337 if let Some(destination) = best_destination {
14338 selection.collapse_to(destination, SelectionGoal::None);
14339 }
14340 })
14341 });
14342 }
14343
14344 pub fn undo_selection(
14345 &mut self,
14346 _: &UndoSelection,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 ) {
14350 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14351 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14352 self.selection_history.mode = SelectionHistoryMode::Undoing;
14353 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14354 this.end_selection(window, cx);
14355 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14356 s.select_anchors(entry.selections.to_vec())
14357 });
14358 });
14359 self.selection_history.mode = SelectionHistoryMode::Normal;
14360
14361 self.select_next_state = entry.select_next_state;
14362 self.select_prev_state = entry.select_prev_state;
14363 self.add_selections_state = entry.add_selections_state;
14364 }
14365 }
14366
14367 pub fn redo_selection(
14368 &mut self,
14369 _: &RedoSelection,
14370 window: &mut Window,
14371 cx: &mut Context<Self>,
14372 ) {
14373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14374 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14375 self.selection_history.mode = SelectionHistoryMode::Redoing;
14376 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14377 this.end_selection(window, cx);
14378 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14379 s.select_anchors(entry.selections.to_vec())
14380 });
14381 });
14382 self.selection_history.mode = SelectionHistoryMode::Normal;
14383
14384 self.select_next_state = entry.select_next_state;
14385 self.select_prev_state = entry.select_prev_state;
14386 self.add_selections_state = entry.add_selections_state;
14387 }
14388 }
14389
14390 pub fn expand_excerpts(
14391 &mut self,
14392 action: &ExpandExcerpts,
14393 _: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14397 }
14398
14399 pub fn expand_excerpts_down(
14400 &mut self,
14401 action: &ExpandExcerptsDown,
14402 _: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) {
14405 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14406 }
14407
14408 pub fn expand_excerpts_up(
14409 &mut self,
14410 action: &ExpandExcerptsUp,
14411 _: &mut Window,
14412 cx: &mut Context<Self>,
14413 ) {
14414 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14415 }
14416
14417 pub fn expand_excerpts_for_direction(
14418 &mut self,
14419 lines: u32,
14420 direction: ExpandExcerptDirection,
14421
14422 cx: &mut Context<Self>,
14423 ) {
14424 let selections = self.selections.disjoint_anchors();
14425
14426 let lines = if lines == 0 {
14427 EditorSettings::get_global(cx).expand_excerpt_lines
14428 } else {
14429 lines
14430 };
14431
14432 self.buffer.update(cx, |buffer, cx| {
14433 let snapshot = buffer.snapshot(cx);
14434 let mut excerpt_ids = selections
14435 .iter()
14436 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14437 .collect::<Vec<_>>();
14438 excerpt_ids.sort();
14439 excerpt_ids.dedup();
14440 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14441 })
14442 }
14443
14444 pub fn expand_excerpt(
14445 &mut self,
14446 excerpt: ExcerptId,
14447 direction: ExpandExcerptDirection,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 let current_scroll_position = self.scroll_position(cx);
14452 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14453 let mut should_scroll_up = false;
14454
14455 if direction == ExpandExcerptDirection::Down {
14456 let multi_buffer = self.buffer.read(cx);
14457 let snapshot = multi_buffer.snapshot(cx);
14458 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14459 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14460 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14461 let buffer_snapshot = buffer.read(cx).snapshot();
14462 let excerpt_end_row =
14463 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14464 let last_row = buffer_snapshot.max_point().row;
14465 let lines_below = last_row.saturating_sub(excerpt_end_row);
14466 should_scroll_up = lines_below >= lines_to_expand;
14467 }
14468 }
14469 }
14470 }
14471
14472 self.buffer.update(cx, |buffer, cx| {
14473 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14474 });
14475
14476 if should_scroll_up {
14477 let new_scroll_position =
14478 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14479 self.set_scroll_position(new_scroll_position, window, cx);
14480 }
14481 }
14482
14483 pub fn go_to_singleton_buffer_point(
14484 &mut self,
14485 point: Point,
14486 window: &mut Window,
14487 cx: &mut Context<Self>,
14488 ) {
14489 self.go_to_singleton_buffer_range(point..point, window, cx);
14490 }
14491
14492 pub fn go_to_singleton_buffer_range(
14493 &mut self,
14494 range: Range<Point>,
14495 window: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 let multibuffer = self.buffer().read(cx);
14499 let Some(buffer) = multibuffer.as_singleton() else {
14500 return;
14501 };
14502 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14503 return;
14504 };
14505 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14506 return;
14507 };
14508 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14509 s.select_anchor_ranges([start..end])
14510 });
14511 }
14512
14513 pub fn go_to_diagnostic(
14514 &mut self,
14515 _: &GoToDiagnostic,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) {
14519 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14520 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14521 }
14522
14523 pub fn go_to_prev_diagnostic(
14524 &mut self,
14525 _: &GoToPreviousDiagnostic,
14526 window: &mut Window,
14527 cx: &mut Context<Self>,
14528 ) {
14529 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14530 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14531 }
14532
14533 pub fn go_to_diagnostic_impl(
14534 &mut self,
14535 direction: Direction,
14536 window: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) {
14539 let buffer = self.buffer.read(cx).snapshot(cx);
14540 let selection = self.selections.newest::<usize>(cx);
14541
14542 let mut active_group_id = None;
14543 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14544 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14545 active_group_id = Some(active_group.group_id);
14546 }
14547 }
14548
14549 fn filtered(
14550 snapshot: EditorSnapshot,
14551 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14552 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14553 diagnostics
14554 .filter(|entry| entry.range.start != entry.range.end)
14555 .filter(|entry| !entry.diagnostic.is_unnecessary)
14556 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14557 }
14558
14559 let snapshot = self.snapshot(window, cx);
14560 let before = filtered(
14561 snapshot.clone(),
14562 buffer
14563 .diagnostics_in_range(0..selection.start)
14564 .filter(|entry| entry.range.start <= selection.start),
14565 );
14566 let after = filtered(
14567 snapshot,
14568 buffer
14569 .diagnostics_in_range(selection.start..buffer.len())
14570 .filter(|entry| entry.range.start >= selection.start),
14571 );
14572
14573 let mut found: Option<DiagnosticEntry<usize>> = None;
14574 if direction == Direction::Prev {
14575 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14576 {
14577 for diagnostic in prev_diagnostics.into_iter().rev() {
14578 if diagnostic.range.start != selection.start
14579 || active_group_id
14580 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14581 {
14582 found = Some(diagnostic);
14583 break 'outer;
14584 }
14585 }
14586 }
14587 } else {
14588 for diagnostic in after.chain(before) {
14589 if diagnostic.range.start != selection.start
14590 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14591 {
14592 found = Some(diagnostic);
14593 break;
14594 }
14595 }
14596 }
14597 let Some(next_diagnostic) = found else {
14598 return;
14599 };
14600
14601 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14602 return;
14603 };
14604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14605 s.select_ranges(vec![
14606 next_diagnostic.range.start..next_diagnostic.range.start,
14607 ])
14608 });
14609 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14610 self.refresh_inline_completion(false, true, window, cx);
14611 }
14612
14613 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14614 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14615 let snapshot = self.snapshot(window, cx);
14616 let selection = self.selections.newest::<Point>(cx);
14617 self.go_to_hunk_before_or_after_position(
14618 &snapshot,
14619 selection.head(),
14620 Direction::Next,
14621 window,
14622 cx,
14623 );
14624 }
14625
14626 pub fn go_to_hunk_before_or_after_position(
14627 &mut self,
14628 snapshot: &EditorSnapshot,
14629 position: Point,
14630 direction: Direction,
14631 window: &mut Window,
14632 cx: &mut Context<Editor>,
14633 ) {
14634 let row = if direction == Direction::Next {
14635 self.hunk_after_position(snapshot, position)
14636 .map(|hunk| hunk.row_range.start)
14637 } else {
14638 self.hunk_before_position(snapshot, position)
14639 };
14640
14641 if let Some(row) = row {
14642 let destination = Point::new(row.0, 0);
14643 let autoscroll = Autoscroll::center();
14644
14645 self.unfold_ranges(&[destination..destination], false, false, cx);
14646 self.change_selections(Some(autoscroll), window, cx, |s| {
14647 s.select_ranges([destination..destination]);
14648 });
14649 }
14650 }
14651
14652 fn hunk_after_position(
14653 &mut self,
14654 snapshot: &EditorSnapshot,
14655 position: Point,
14656 ) -> Option<MultiBufferDiffHunk> {
14657 snapshot
14658 .buffer_snapshot
14659 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14660 .find(|hunk| hunk.row_range.start.0 > position.row)
14661 .or_else(|| {
14662 snapshot
14663 .buffer_snapshot
14664 .diff_hunks_in_range(Point::zero()..position)
14665 .find(|hunk| hunk.row_range.end.0 < position.row)
14666 })
14667 }
14668
14669 fn go_to_prev_hunk(
14670 &mut self,
14671 _: &GoToPreviousHunk,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14676 let snapshot = self.snapshot(window, cx);
14677 let selection = self.selections.newest::<Point>(cx);
14678 self.go_to_hunk_before_or_after_position(
14679 &snapshot,
14680 selection.head(),
14681 Direction::Prev,
14682 window,
14683 cx,
14684 );
14685 }
14686
14687 fn hunk_before_position(
14688 &mut self,
14689 snapshot: &EditorSnapshot,
14690 position: Point,
14691 ) -> Option<MultiBufferRow> {
14692 snapshot
14693 .buffer_snapshot
14694 .diff_hunk_before(position)
14695 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14696 }
14697
14698 fn go_to_next_change(
14699 &mut self,
14700 _: &GoToNextChange,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) {
14704 if let Some(selections) = self
14705 .change_list
14706 .next_change(1, Direction::Next)
14707 .map(|s| s.to_vec())
14708 {
14709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14710 let map = s.display_map();
14711 s.select_display_ranges(selections.iter().map(|a| {
14712 let point = a.to_display_point(&map);
14713 point..point
14714 }))
14715 })
14716 }
14717 }
14718
14719 fn go_to_previous_change(
14720 &mut self,
14721 _: &GoToPreviousChange,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) {
14725 if let Some(selections) = self
14726 .change_list
14727 .next_change(1, Direction::Prev)
14728 .map(|s| s.to_vec())
14729 {
14730 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14731 let map = s.display_map();
14732 s.select_display_ranges(selections.iter().map(|a| {
14733 let point = a.to_display_point(&map);
14734 point..point
14735 }))
14736 })
14737 }
14738 }
14739
14740 fn go_to_line<T: 'static>(
14741 &mut self,
14742 position: Anchor,
14743 highlight_color: Option<Hsla>,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 let snapshot = self.snapshot(window, cx).display_snapshot;
14748 let position = position.to_point(&snapshot.buffer_snapshot);
14749 let start = snapshot
14750 .buffer_snapshot
14751 .clip_point(Point::new(position.row, 0), Bias::Left);
14752 let end = start + Point::new(1, 0);
14753 let start = snapshot.buffer_snapshot.anchor_before(start);
14754 let end = snapshot.buffer_snapshot.anchor_before(end);
14755
14756 self.highlight_rows::<T>(
14757 start..end,
14758 highlight_color
14759 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14760 Default::default(),
14761 cx,
14762 );
14763
14764 if self.buffer.read(cx).is_singleton() {
14765 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14766 }
14767 }
14768
14769 pub fn go_to_definition(
14770 &mut self,
14771 _: &GoToDefinition,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) -> Task<Result<Navigated>> {
14775 let definition =
14776 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14777 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14778 cx.spawn_in(window, async move |editor, cx| {
14779 if definition.await? == Navigated::Yes {
14780 return Ok(Navigated::Yes);
14781 }
14782 match fallback_strategy {
14783 GoToDefinitionFallback::None => Ok(Navigated::No),
14784 GoToDefinitionFallback::FindAllReferences => {
14785 match editor.update_in(cx, |editor, window, cx| {
14786 editor.find_all_references(&FindAllReferences, window, cx)
14787 })? {
14788 Some(references) => references.await,
14789 None => Ok(Navigated::No),
14790 }
14791 }
14792 }
14793 })
14794 }
14795
14796 pub fn go_to_declaration(
14797 &mut self,
14798 _: &GoToDeclaration,
14799 window: &mut Window,
14800 cx: &mut Context<Self>,
14801 ) -> Task<Result<Navigated>> {
14802 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14803 }
14804
14805 pub fn go_to_declaration_split(
14806 &mut self,
14807 _: &GoToDeclaration,
14808 window: &mut Window,
14809 cx: &mut Context<Self>,
14810 ) -> Task<Result<Navigated>> {
14811 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14812 }
14813
14814 pub fn go_to_implementation(
14815 &mut self,
14816 _: &GoToImplementation,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) -> Task<Result<Navigated>> {
14820 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14821 }
14822
14823 pub fn go_to_implementation_split(
14824 &mut self,
14825 _: &GoToImplementationSplit,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) -> Task<Result<Navigated>> {
14829 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14830 }
14831
14832 pub fn go_to_type_definition(
14833 &mut self,
14834 _: &GoToTypeDefinition,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) -> Task<Result<Navigated>> {
14838 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14839 }
14840
14841 pub fn go_to_definition_split(
14842 &mut self,
14843 _: &GoToDefinitionSplit,
14844 window: &mut Window,
14845 cx: &mut Context<Self>,
14846 ) -> Task<Result<Navigated>> {
14847 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14848 }
14849
14850 pub fn go_to_type_definition_split(
14851 &mut self,
14852 _: &GoToTypeDefinitionSplit,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) -> Task<Result<Navigated>> {
14856 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14857 }
14858
14859 fn go_to_definition_of_kind(
14860 &mut self,
14861 kind: GotoDefinitionKind,
14862 split: bool,
14863 window: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) -> Task<Result<Navigated>> {
14866 let Some(provider) = self.semantics_provider.clone() else {
14867 return Task::ready(Ok(Navigated::No));
14868 };
14869 let head = self.selections.newest::<usize>(cx).head();
14870 let buffer = self.buffer.read(cx);
14871 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14872 text_anchor
14873 } else {
14874 return Task::ready(Ok(Navigated::No));
14875 };
14876
14877 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14878 return Task::ready(Ok(Navigated::No));
14879 };
14880
14881 cx.spawn_in(window, async move |editor, cx| {
14882 let definitions = definitions.await?;
14883 let navigated = editor
14884 .update_in(cx, |editor, window, cx| {
14885 editor.navigate_to_hover_links(
14886 Some(kind),
14887 definitions
14888 .into_iter()
14889 .filter(|location| {
14890 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14891 })
14892 .map(HoverLink::Text)
14893 .collect::<Vec<_>>(),
14894 split,
14895 window,
14896 cx,
14897 )
14898 })?
14899 .await?;
14900 anyhow::Ok(navigated)
14901 })
14902 }
14903
14904 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14905 let selection = self.selections.newest_anchor();
14906 let head = selection.head();
14907 let tail = selection.tail();
14908
14909 let Some((buffer, start_position)) =
14910 self.buffer.read(cx).text_anchor_for_position(head, cx)
14911 else {
14912 return;
14913 };
14914
14915 let end_position = if head != tail {
14916 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14917 return;
14918 };
14919 Some(pos)
14920 } else {
14921 None
14922 };
14923
14924 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14925 let url = if let Some(end_pos) = end_position {
14926 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14927 } else {
14928 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14929 };
14930
14931 if let Some(url) = url {
14932 editor.update(cx, |_, cx| {
14933 cx.open_url(&url);
14934 })
14935 } else {
14936 Ok(())
14937 }
14938 });
14939
14940 url_finder.detach();
14941 }
14942
14943 pub fn open_selected_filename(
14944 &mut self,
14945 _: &OpenSelectedFilename,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) {
14949 let Some(workspace) = self.workspace() else {
14950 return;
14951 };
14952
14953 let position = self.selections.newest_anchor().head();
14954
14955 let Some((buffer, buffer_position)) =
14956 self.buffer.read(cx).text_anchor_for_position(position, cx)
14957 else {
14958 return;
14959 };
14960
14961 let project = self.project.clone();
14962
14963 cx.spawn_in(window, async move |_, cx| {
14964 let result = find_file(&buffer, project, buffer_position, cx).await;
14965
14966 if let Some((_, path)) = result {
14967 workspace
14968 .update_in(cx, |workspace, window, cx| {
14969 workspace.open_resolved_path(path, window, cx)
14970 })?
14971 .await?;
14972 }
14973 anyhow::Ok(())
14974 })
14975 .detach();
14976 }
14977
14978 pub(crate) fn navigate_to_hover_links(
14979 &mut self,
14980 kind: Option<GotoDefinitionKind>,
14981 mut definitions: Vec<HoverLink>,
14982 split: bool,
14983 window: &mut Window,
14984 cx: &mut Context<Editor>,
14985 ) -> Task<Result<Navigated>> {
14986 // If there is one definition, just open it directly
14987 if definitions.len() == 1 {
14988 let definition = definitions.pop().unwrap();
14989
14990 enum TargetTaskResult {
14991 Location(Option<Location>),
14992 AlreadyNavigated,
14993 }
14994
14995 let target_task = match definition {
14996 HoverLink::Text(link) => {
14997 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14998 }
14999 HoverLink::InlayHint(lsp_location, server_id) => {
15000 let computation =
15001 self.compute_target_location(lsp_location, server_id, window, cx);
15002 cx.background_spawn(async move {
15003 let location = computation.await?;
15004 Ok(TargetTaskResult::Location(location))
15005 })
15006 }
15007 HoverLink::Url(url) => {
15008 cx.open_url(&url);
15009 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15010 }
15011 HoverLink::File(path) => {
15012 if let Some(workspace) = self.workspace() {
15013 cx.spawn_in(window, async move |_, cx| {
15014 workspace
15015 .update_in(cx, |workspace, window, cx| {
15016 workspace.open_resolved_path(path, window, cx)
15017 })?
15018 .await
15019 .map(|_| TargetTaskResult::AlreadyNavigated)
15020 })
15021 } else {
15022 Task::ready(Ok(TargetTaskResult::Location(None)))
15023 }
15024 }
15025 };
15026 cx.spawn_in(window, async move |editor, cx| {
15027 let target = match target_task.await.context("target resolution task")? {
15028 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15029 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15030 TargetTaskResult::Location(Some(target)) => target,
15031 };
15032
15033 editor.update_in(cx, |editor, window, cx| {
15034 let Some(workspace) = editor.workspace() else {
15035 return Navigated::No;
15036 };
15037 let pane = workspace.read(cx).active_pane().clone();
15038
15039 let range = target.range.to_point(target.buffer.read(cx));
15040 let range = editor.range_for_match(&range);
15041 let range = collapse_multiline_range(range);
15042
15043 if !split
15044 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15045 {
15046 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15047 } else {
15048 window.defer(cx, move |window, cx| {
15049 let target_editor: Entity<Self> =
15050 workspace.update(cx, |workspace, cx| {
15051 let pane = if split {
15052 workspace.adjacent_pane(window, cx)
15053 } else {
15054 workspace.active_pane().clone()
15055 };
15056
15057 workspace.open_project_item(
15058 pane,
15059 target.buffer.clone(),
15060 true,
15061 true,
15062 window,
15063 cx,
15064 )
15065 });
15066 target_editor.update(cx, |target_editor, cx| {
15067 // When selecting a definition in a different buffer, disable the nav history
15068 // to avoid creating a history entry at the previous cursor location.
15069 pane.update(cx, |pane, _| pane.disable_history());
15070 target_editor.go_to_singleton_buffer_range(range, window, cx);
15071 pane.update(cx, |pane, _| pane.enable_history());
15072 });
15073 });
15074 }
15075 Navigated::Yes
15076 })
15077 })
15078 } else if !definitions.is_empty() {
15079 cx.spawn_in(window, async move |editor, cx| {
15080 let (title, location_tasks, workspace) = editor
15081 .update_in(cx, |editor, window, cx| {
15082 let tab_kind = match kind {
15083 Some(GotoDefinitionKind::Implementation) => "Implementations",
15084 _ => "Definitions",
15085 };
15086 let title = definitions
15087 .iter()
15088 .find_map(|definition| match definition {
15089 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15090 let buffer = origin.buffer.read(cx);
15091 format!(
15092 "{} for {}",
15093 tab_kind,
15094 buffer
15095 .text_for_range(origin.range.clone())
15096 .collect::<String>()
15097 )
15098 }),
15099 HoverLink::InlayHint(_, _) => None,
15100 HoverLink::Url(_) => None,
15101 HoverLink::File(_) => None,
15102 })
15103 .unwrap_or(tab_kind.to_string());
15104 let location_tasks = definitions
15105 .into_iter()
15106 .map(|definition| match definition {
15107 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15108 HoverLink::InlayHint(lsp_location, server_id) => editor
15109 .compute_target_location(lsp_location, server_id, window, cx),
15110 HoverLink::Url(_) => Task::ready(Ok(None)),
15111 HoverLink::File(_) => Task::ready(Ok(None)),
15112 })
15113 .collect::<Vec<_>>();
15114 (title, location_tasks, editor.workspace().clone())
15115 })
15116 .context("location tasks preparation")?;
15117
15118 let locations = future::join_all(location_tasks)
15119 .await
15120 .into_iter()
15121 .filter_map(|location| location.transpose())
15122 .collect::<Result<_>>()
15123 .context("location tasks")?;
15124
15125 let Some(workspace) = workspace else {
15126 return Ok(Navigated::No);
15127 };
15128 let opened = workspace
15129 .update_in(cx, |workspace, window, cx| {
15130 Self::open_locations_in_multibuffer(
15131 workspace,
15132 locations,
15133 title,
15134 split,
15135 MultibufferSelectionMode::First,
15136 window,
15137 cx,
15138 )
15139 })
15140 .ok();
15141
15142 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15143 })
15144 } else {
15145 Task::ready(Ok(Navigated::No))
15146 }
15147 }
15148
15149 fn compute_target_location(
15150 &self,
15151 lsp_location: lsp::Location,
15152 server_id: LanguageServerId,
15153 window: &mut Window,
15154 cx: &mut Context<Self>,
15155 ) -> Task<anyhow::Result<Option<Location>>> {
15156 let Some(project) = self.project.clone() else {
15157 return Task::ready(Ok(None));
15158 };
15159
15160 cx.spawn_in(window, async move |editor, cx| {
15161 let location_task = editor.update(cx, |_, cx| {
15162 project.update(cx, |project, cx| {
15163 let language_server_name = project
15164 .language_server_statuses(cx)
15165 .find(|(id, _)| server_id == *id)
15166 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15167 language_server_name.map(|language_server_name| {
15168 project.open_local_buffer_via_lsp(
15169 lsp_location.uri.clone(),
15170 server_id,
15171 language_server_name,
15172 cx,
15173 )
15174 })
15175 })
15176 })?;
15177 let location = match location_task {
15178 Some(task) => Some({
15179 let target_buffer_handle = task.await.context("open local buffer")?;
15180 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15181 let target_start = target_buffer
15182 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15183 let target_end = target_buffer
15184 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15185 target_buffer.anchor_after(target_start)
15186 ..target_buffer.anchor_before(target_end)
15187 })?;
15188 Location {
15189 buffer: target_buffer_handle,
15190 range,
15191 }
15192 }),
15193 None => None,
15194 };
15195 Ok(location)
15196 })
15197 }
15198
15199 pub fn find_all_references(
15200 &mut self,
15201 _: &FindAllReferences,
15202 window: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) -> Option<Task<Result<Navigated>>> {
15205 let selection = self.selections.newest::<usize>(cx);
15206 let multi_buffer = self.buffer.read(cx);
15207 let head = selection.head();
15208
15209 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15210 let head_anchor = multi_buffer_snapshot.anchor_at(
15211 head,
15212 if head < selection.tail() {
15213 Bias::Right
15214 } else {
15215 Bias::Left
15216 },
15217 );
15218
15219 match self
15220 .find_all_references_task_sources
15221 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15222 {
15223 Ok(_) => {
15224 log::info!(
15225 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15226 );
15227 return None;
15228 }
15229 Err(i) => {
15230 self.find_all_references_task_sources.insert(i, head_anchor);
15231 }
15232 }
15233
15234 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15235 let workspace = self.workspace()?;
15236 let project = workspace.read(cx).project().clone();
15237 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15238 Some(cx.spawn_in(window, async move |editor, cx| {
15239 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15240 if let Ok(i) = editor
15241 .find_all_references_task_sources
15242 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15243 {
15244 editor.find_all_references_task_sources.remove(i);
15245 }
15246 });
15247
15248 let locations = references.await?;
15249 if locations.is_empty() {
15250 return anyhow::Ok(Navigated::No);
15251 }
15252
15253 workspace.update_in(cx, |workspace, window, cx| {
15254 let title = locations
15255 .first()
15256 .as_ref()
15257 .map(|location| {
15258 let buffer = location.buffer.read(cx);
15259 format!(
15260 "References to `{}`",
15261 buffer
15262 .text_for_range(location.range.clone())
15263 .collect::<String>()
15264 )
15265 })
15266 .unwrap();
15267 Self::open_locations_in_multibuffer(
15268 workspace,
15269 locations,
15270 title,
15271 false,
15272 MultibufferSelectionMode::First,
15273 window,
15274 cx,
15275 );
15276 Navigated::Yes
15277 })
15278 }))
15279 }
15280
15281 /// Opens a multibuffer with the given project locations in it
15282 pub fn open_locations_in_multibuffer(
15283 workspace: &mut Workspace,
15284 mut locations: Vec<Location>,
15285 title: String,
15286 split: bool,
15287 multibuffer_selection_mode: MultibufferSelectionMode,
15288 window: &mut Window,
15289 cx: &mut Context<Workspace>,
15290 ) {
15291 // If there are multiple definitions, open them in a multibuffer
15292 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15293 let mut locations = locations.into_iter().peekable();
15294 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15295 let capability = workspace.project().read(cx).capability();
15296
15297 let excerpt_buffer = cx.new(|cx| {
15298 let mut multibuffer = MultiBuffer::new(capability);
15299 while let Some(location) = locations.next() {
15300 let buffer = location.buffer.read(cx);
15301 let mut ranges_for_buffer = Vec::new();
15302 let range = location.range.to_point(buffer);
15303 ranges_for_buffer.push(range.clone());
15304
15305 while let Some(next_location) = locations.peek() {
15306 if next_location.buffer == location.buffer {
15307 ranges_for_buffer.push(next_location.range.to_point(buffer));
15308 locations.next();
15309 } else {
15310 break;
15311 }
15312 }
15313
15314 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15315 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15316 PathKey::for_buffer(&location.buffer, cx),
15317 location.buffer.clone(),
15318 ranges_for_buffer,
15319 DEFAULT_MULTIBUFFER_CONTEXT,
15320 cx,
15321 );
15322 ranges.extend(new_ranges)
15323 }
15324
15325 multibuffer.with_title(title)
15326 });
15327
15328 let editor = cx.new(|cx| {
15329 Editor::for_multibuffer(
15330 excerpt_buffer,
15331 Some(workspace.project().clone()),
15332 window,
15333 cx,
15334 )
15335 });
15336 editor.update(cx, |editor, cx| {
15337 match multibuffer_selection_mode {
15338 MultibufferSelectionMode::First => {
15339 if let Some(first_range) = ranges.first() {
15340 editor.change_selections(None, window, cx, |selections| {
15341 selections.clear_disjoint();
15342 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15343 });
15344 }
15345 editor.highlight_background::<Self>(
15346 &ranges,
15347 |theme| theme.editor_highlighted_line_background,
15348 cx,
15349 );
15350 }
15351 MultibufferSelectionMode::All => {
15352 editor.change_selections(None, window, cx, |selections| {
15353 selections.clear_disjoint();
15354 selections.select_anchor_ranges(ranges);
15355 });
15356 }
15357 }
15358 editor.register_buffers_with_language_servers(cx);
15359 });
15360
15361 let item = Box::new(editor);
15362 let item_id = item.item_id();
15363
15364 if split {
15365 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15366 } else {
15367 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15368 let (preview_item_id, preview_item_idx) =
15369 workspace.active_pane().read_with(cx, |pane, _| {
15370 (pane.preview_item_id(), pane.preview_item_idx())
15371 });
15372
15373 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15374
15375 if let Some(preview_item_id) = preview_item_id {
15376 workspace.active_pane().update(cx, |pane, cx| {
15377 pane.remove_item(preview_item_id, false, false, window, cx);
15378 });
15379 }
15380 } else {
15381 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15382 }
15383 }
15384 workspace.active_pane().update(cx, |pane, cx| {
15385 pane.set_preview_item_id(Some(item_id), cx);
15386 });
15387 }
15388
15389 pub fn rename(
15390 &mut self,
15391 _: &Rename,
15392 window: &mut Window,
15393 cx: &mut Context<Self>,
15394 ) -> Option<Task<Result<()>>> {
15395 use language::ToOffset as _;
15396
15397 let provider = self.semantics_provider.clone()?;
15398 let selection = self.selections.newest_anchor().clone();
15399 let (cursor_buffer, cursor_buffer_position) = self
15400 .buffer
15401 .read(cx)
15402 .text_anchor_for_position(selection.head(), cx)?;
15403 let (tail_buffer, cursor_buffer_position_end) = self
15404 .buffer
15405 .read(cx)
15406 .text_anchor_for_position(selection.tail(), cx)?;
15407 if tail_buffer != cursor_buffer {
15408 return None;
15409 }
15410
15411 let snapshot = cursor_buffer.read(cx).snapshot();
15412 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15413 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15414 let prepare_rename = provider
15415 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15416 .unwrap_or_else(|| Task::ready(Ok(None)));
15417 drop(snapshot);
15418
15419 Some(cx.spawn_in(window, async move |this, cx| {
15420 let rename_range = if let Some(range) = prepare_rename.await? {
15421 Some(range)
15422 } else {
15423 this.update(cx, |this, cx| {
15424 let buffer = this.buffer.read(cx).snapshot(cx);
15425 let mut buffer_highlights = this
15426 .document_highlights_for_position(selection.head(), &buffer)
15427 .filter(|highlight| {
15428 highlight.start.excerpt_id == selection.head().excerpt_id
15429 && highlight.end.excerpt_id == selection.head().excerpt_id
15430 });
15431 buffer_highlights
15432 .next()
15433 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15434 })?
15435 };
15436 if let Some(rename_range) = rename_range {
15437 this.update_in(cx, |this, window, cx| {
15438 let snapshot = cursor_buffer.read(cx).snapshot();
15439 let rename_buffer_range = rename_range.to_offset(&snapshot);
15440 let cursor_offset_in_rename_range =
15441 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15442 let cursor_offset_in_rename_range_end =
15443 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15444
15445 this.take_rename(false, window, cx);
15446 let buffer = this.buffer.read(cx).read(cx);
15447 let cursor_offset = selection.head().to_offset(&buffer);
15448 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15449 let rename_end = rename_start + rename_buffer_range.len();
15450 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15451 let mut old_highlight_id = None;
15452 let old_name: Arc<str> = buffer
15453 .chunks(rename_start..rename_end, true)
15454 .map(|chunk| {
15455 if old_highlight_id.is_none() {
15456 old_highlight_id = chunk.syntax_highlight_id;
15457 }
15458 chunk.text
15459 })
15460 .collect::<String>()
15461 .into();
15462
15463 drop(buffer);
15464
15465 // Position the selection in the rename editor so that it matches the current selection.
15466 this.show_local_selections = false;
15467 let rename_editor = cx.new(|cx| {
15468 let mut editor = Editor::single_line(window, cx);
15469 editor.buffer.update(cx, |buffer, cx| {
15470 buffer.edit([(0..0, old_name.clone())], None, cx)
15471 });
15472 let rename_selection_range = match cursor_offset_in_rename_range
15473 .cmp(&cursor_offset_in_rename_range_end)
15474 {
15475 Ordering::Equal => {
15476 editor.select_all(&SelectAll, window, cx);
15477 return editor;
15478 }
15479 Ordering::Less => {
15480 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15481 }
15482 Ordering::Greater => {
15483 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15484 }
15485 };
15486 if rename_selection_range.end > old_name.len() {
15487 editor.select_all(&SelectAll, window, cx);
15488 } else {
15489 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15490 s.select_ranges([rename_selection_range]);
15491 });
15492 }
15493 editor
15494 });
15495 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15496 if e == &EditorEvent::Focused {
15497 cx.emit(EditorEvent::FocusedIn)
15498 }
15499 })
15500 .detach();
15501
15502 let write_highlights =
15503 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15504 let read_highlights =
15505 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15506 let ranges = write_highlights
15507 .iter()
15508 .flat_map(|(_, ranges)| ranges.iter())
15509 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15510 .cloned()
15511 .collect();
15512
15513 this.highlight_text::<Rename>(
15514 ranges,
15515 HighlightStyle {
15516 fade_out: Some(0.6),
15517 ..Default::default()
15518 },
15519 cx,
15520 );
15521 let rename_focus_handle = rename_editor.focus_handle(cx);
15522 window.focus(&rename_focus_handle);
15523 let block_id = this.insert_blocks(
15524 [BlockProperties {
15525 style: BlockStyle::Flex,
15526 placement: BlockPlacement::Below(range.start),
15527 height: Some(1),
15528 render: Arc::new({
15529 let rename_editor = rename_editor.clone();
15530 move |cx: &mut BlockContext| {
15531 let mut text_style = cx.editor_style.text.clone();
15532 if let Some(highlight_style) = old_highlight_id
15533 .and_then(|h| h.style(&cx.editor_style.syntax))
15534 {
15535 text_style = text_style.highlight(highlight_style);
15536 }
15537 div()
15538 .block_mouse_except_scroll()
15539 .pl(cx.anchor_x)
15540 .child(EditorElement::new(
15541 &rename_editor,
15542 EditorStyle {
15543 background: cx.theme().system().transparent,
15544 local_player: cx.editor_style.local_player,
15545 text: text_style,
15546 scrollbar_width: cx.editor_style.scrollbar_width,
15547 syntax: cx.editor_style.syntax.clone(),
15548 status: cx.editor_style.status.clone(),
15549 inlay_hints_style: HighlightStyle {
15550 font_weight: Some(FontWeight::BOLD),
15551 ..make_inlay_hints_style(cx.app)
15552 },
15553 inline_completion_styles: make_suggestion_styles(
15554 cx.app,
15555 ),
15556 ..EditorStyle::default()
15557 },
15558 ))
15559 .into_any_element()
15560 }
15561 }),
15562 priority: 0,
15563 render_in_minimap: true,
15564 }],
15565 Some(Autoscroll::fit()),
15566 cx,
15567 )[0];
15568 this.pending_rename = Some(RenameState {
15569 range,
15570 old_name,
15571 editor: rename_editor,
15572 block_id,
15573 });
15574 })?;
15575 }
15576
15577 Ok(())
15578 }))
15579 }
15580
15581 pub fn confirm_rename(
15582 &mut self,
15583 _: &ConfirmRename,
15584 window: &mut Window,
15585 cx: &mut Context<Self>,
15586 ) -> Option<Task<Result<()>>> {
15587 let rename = self.take_rename(false, window, cx)?;
15588 let workspace = self.workspace()?.downgrade();
15589 let (buffer, start) = self
15590 .buffer
15591 .read(cx)
15592 .text_anchor_for_position(rename.range.start, cx)?;
15593 let (end_buffer, _) = self
15594 .buffer
15595 .read(cx)
15596 .text_anchor_for_position(rename.range.end, cx)?;
15597 if buffer != end_buffer {
15598 return None;
15599 }
15600
15601 let old_name = rename.old_name;
15602 let new_name = rename.editor.read(cx).text(cx);
15603
15604 let rename = self.semantics_provider.as_ref()?.perform_rename(
15605 &buffer,
15606 start,
15607 new_name.clone(),
15608 cx,
15609 )?;
15610
15611 Some(cx.spawn_in(window, async move |editor, cx| {
15612 let project_transaction = rename.await?;
15613 Self::open_project_transaction(
15614 &editor,
15615 workspace,
15616 project_transaction,
15617 format!("Rename: {} → {}", old_name, new_name),
15618 cx,
15619 )
15620 .await?;
15621
15622 editor.update(cx, |editor, cx| {
15623 editor.refresh_document_highlights(cx);
15624 })?;
15625 Ok(())
15626 }))
15627 }
15628
15629 fn take_rename(
15630 &mut self,
15631 moving_cursor: bool,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) -> Option<RenameState> {
15635 let rename = self.pending_rename.take()?;
15636 if rename.editor.focus_handle(cx).is_focused(window) {
15637 window.focus(&self.focus_handle);
15638 }
15639
15640 self.remove_blocks(
15641 [rename.block_id].into_iter().collect(),
15642 Some(Autoscroll::fit()),
15643 cx,
15644 );
15645 self.clear_highlights::<Rename>(cx);
15646 self.show_local_selections = true;
15647
15648 if moving_cursor {
15649 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15650 editor.selections.newest::<usize>(cx).head()
15651 });
15652
15653 // Update the selection to match the position of the selection inside
15654 // the rename editor.
15655 let snapshot = self.buffer.read(cx).read(cx);
15656 let rename_range = rename.range.to_offset(&snapshot);
15657 let cursor_in_editor = snapshot
15658 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15659 .min(rename_range.end);
15660 drop(snapshot);
15661
15662 self.change_selections(None, window, cx, |s| {
15663 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15664 });
15665 } else {
15666 self.refresh_document_highlights(cx);
15667 }
15668
15669 Some(rename)
15670 }
15671
15672 pub fn pending_rename(&self) -> Option<&RenameState> {
15673 self.pending_rename.as_ref()
15674 }
15675
15676 fn format(
15677 &mut self,
15678 _: &Format,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) -> Option<Task<Result<()>>> {
15682 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15683
15684 let project = match &self.project {
15685 Some(project) => project.clone(),
15686 None => return None,
15687 };
15688
15689 Some(self.perform_format(
15690 project,
15691 FormatTrigger::Manual,
15692 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15693 window,
15694 cx,
15695 ))
15696 }
15697
15698 fn format_selections(
15699 &mut self,
15700 _: &FormatSelections,
15701 window: &mut Window,
15702 cx: &mut Context<Self>,
15703 ) -> Option<Task<Result<()>>> {
15704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15705
15706 let project = match &self.project {
15707 Some(project) => project.clone(),
15708 None => return None,
15709 };
15710
15711 let ranges = self
15712 .selections
15713 .all_adjusted(cx)
15714 .into_iter()
15715 .map(|selection| selection.range())
15716 .collect_vec();
15717
15718 Some(self.perform_format(
15719 project,
15720 FormatTrigger::Manual,
15721 FormatTarget::Ranges(ranges),
15722 window,
15723 cx,
15724 ))
15725 }
15726
15727 fn perform_format(
15728 &mut self,
15729 project: Entity<Project>,
15730 trigger: FormatTrigger,
15731 target: FormatTarget,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) -> Task<Result<()>> {
15735 let buffer = self.buffer.clone();
15736 let (buffers, target) = match target {
15737 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15738 FormatTarget::Ranges(selection_ranges) => {
15739 let multi_buffer = buffer.read(cx);
15740 let snapshot = multi_buffer.read(cx);
15741 let mut buffers = HashSet::default();
15742 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15743 BTreeMap::new();
15744 for selection_range in selection_ranges {
15745 for (buffer, buffer_range, _) in
15746 snapshot.range_to_buffer_ranges(selection_range)
15747 {
15748 let buffer_id = buffer.remote_id();
15749 let start = buffer.anchor_before(buffer_range.start);
15750 let end = buffer.anchor_after(buffer_range.end);
15751 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15752 buffer_id_to_ranges
15753 .entry(buffer_id)
15754 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15755 .or_insert_with(|| vec![start..end]);
15756 }
15757 }
15758 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15759 }
15760 };
15761
15762 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15763 let selections_prev = transaction_id_prev
15764 .and_then(|transaction_id_prev| {
15765 // default to selections as they were after the last edit, if we have them,
15766 // instead of how they are now.
15767 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15768 // will take you back to where you made the last edit, instead of staying where you scrolled
15769 self.selection_history
15770 .transaction(transaction_id_prev)
15771 .map(|t| t.0.clone())
15772 })
15773 .unwrap_or_else(|| {
15774 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15775 self.selections.disjoint_anchors()
15776 });
15777
15778 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15779 let format = project.update(cx, |project, cx| {
15780 project.format(buffers, target, true, trigger, cx)
15781 });
15782
15783 cx.spawn_in(window, async move |editor, cx| {
15784 let transaction = futures::select_biased! {
15785 transaction = format.log_err().fuse() => transaction,
15786 () = timeout => {
15787 log::warn!("timed out waiting for formatting");
15788 None
15789 }
15790 };
15791
15792 buffer
15793 .update(cx, |buffer, cx| {
15794 if let Some(transaction) = transaction {
15795 if !buffer.is_singleton() {
15796 buffer.push_transaction(&transaction.0, cx);
15797 }
15798 }
15799 cx.notify();
15800 })
15801 .ok();
15802
15803 if let Some(transaction_id_now) =
15804 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15805 {
15806 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15807 if has_new_transaction {
15808 _ = editor.update(cx, |editor, _| {
15809 editor
15810 .selection_history
15811 .insert_transaction(transaction_id_now, selections_prev);
15812 });
15813 }
15814 }
15815
15816 Ok(())
15817 })
15818 }
15819
15820 fn organize_imports(
15821 &mut self,
15822 _: &OrganizeImports,
15823 window: &mut Window,
15824 cx: &mut Context<Self>,
15825 ) -> Option<Task<Result<()>>> {
15826 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15827 let project = match &self.project {
15828 Some(project) => project.clone(),
15829 None => return None,
15830 };
15831 Some(self.perform_code_action_kind(
15832 project,
15833 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15834 window,
15835 cx,
15836 ))
15837 }
15838
15839 fn perform_code_action_kind(
15840 &mut self,
15841 project: Entity<Project>,
15842 kind: CodeActionKind,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) -> Task<Result<()>> {
15846 let buffer = self.buffer.clone();
15847 let buffers = buffer.read(cx).all_buffers();
15848 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15849 let apply_action = project.update(cx, |project, cx| {
15850 project.apply_code_action_kind(buffers, kind, true, cx)
15851 });
15852 cx.spawn_in(window, async move |_, cx| {
15853 let transaction = futures::select_biased! {
15854 () = timeout => {
15855 log::warn!("timed out waiting for executing code action");
15856 None
15857 }
15858 transaction = apply_action.log_err().fuse() => transaction,
15859 };
15860 buffer
15861 .update(cx, |buffer, cx| {
15862 // check if we need this
15863 if let Some(transaction) = transaction {
15864 if !buffer.is_singleton() {
15865 buffer.push_transaction(&transaction.0, cx);
15866 }
15867 }
15868 cx.notify();
15869 })
15870 .ok();
15871 Ok(())
15872 })
15873 }
15874
15875 fn restart_language_server(
15876 &mut self,
15877 _: &RestartLanguageServer,
15878 _: &mut Window,
15879 cx: &mut Context<Self>,
15880 ) {
15881 if let Some(project) = self.project.clone() {
15882 self.buffer.update(cx, |multi_buffer, cx| {
15883 project.update(cx, |project, cx| {
15884 project.restart_language_servers_for_buffers(
15885 multi_buffer.all_buffers().into_iter().collect(),
15886 cx,
15887 );
15888 });
15889 })
15890 }
15891 }
15892
15893 fn stop_language_server(
15894 &mut self,
15895 _: &StopLanguageServer,
15896 _: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) {
15899 if let Some(project) = self.project.clone() {
15900 self.buffer.update(cx, |multi_buffer, cx| {
15901 project.update(cx, |project, cx| {
15902 project.stop_language_servers_for_buffers(
15903 multi_buffer.all_buffers().into_iter().collect(),
15904 cx,
15905 );
15906 cx.emit(project::Event::RefreshInlayHints);
15907 });
15908 });
15909 }
15910 }
15911
15912 fn cancel_language_server_work(
15913 workspace: &mut Workspace,
15914 _: &actions::CancelLanguageServerWork,
15915 _: &mut Window,
15916 cx: &mut Context<Workspace>,
15917 ) {
15918 let project = workspace.project();
15919 let buffers = workspace
15920 .active_item(cx)
15921 .and_then(|item| item.act_as::<Editor>(cx))
15922 .map_or(HashSet::default(), |editor| {
15923 editor.read(cx).buffer.read(cx).all_buffers()
15924 });
15925 project.update(cx, |project, cx| {
15926 project.cancel_language_server_work_for_buffers(buffers, cx);
15927 });
15928 }
15929
15930 fn show_character_palette(
15931 &mut self,
15932 _: &ShowCharacterPalette,
15933 window: &mut Window,
15934 _: &mut Context<Self>,
15935 ) {
15936 window.show_character_palette();
15937 }
15938
15939 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15940 if self.mode.is_minimap() {
15941 return;
15942 }
15943
15944 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15945 let buffer = self.buffer.read(cx).snapshot(cx);
15946 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15947 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15948 let is_valid = buffer
15949 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15950 .any(|entry| {
15951 entry.diagnostic.is_primary
15952 && !entry.range.is_empty()
15953 && entry.range.start == primary_range_start
15954 && entry.diagnostic.message == active_diagnostics.active_message
15955 });
15956
15957 if !is_valid {
15958 self.dismiss_diagnostics(cx);
15959 }
15960 }
15961 }
15962
15963 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15964 match &self.active_diagnostics {
15965 ActiveDiagnostic::Group(group) => Some(group),
15966 _ => None,
15967 }
15968 }
15969
15970 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15971 self.dismiss_diagnostics(cx);
15972 self.active_diagnostics = ActiveDiagnostic::All;
15973 }
15974
15975 fn activate_diagnostics(
15976 &mut self,
15977 buffer_id: BufferId,
15978 diagnostic: DiagnosticEntry<usize>,
15979 window: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) {
15982 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15983 return;
15984 }
15985 self.dismiss_diagnostics(cx);
15986 let snapshot = self.snapshot(window, cx);
15987 let buffer = self.buffer.read(cx).snapshot(cx);
15988 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15989 return;
15990 };
15991
15992 let diagnostic_group = buffer
15993 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15994 .collect::<Vec<_>>();
15995
15996 let blocks =
15997 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15998
15999 let blocks = self.display_map.update(cx, |display_map, cx| {
16000 display_map.insert_blocks(blocks, cx).into_iter().collect()
16001 });
16002 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16003 active_range: buffer.anchor_before(diagnostic.range.start)
16004 ..buffer.anchor_after(diagnostic.range.end),
16005 active_message: diagnostic.diagnostic.message.clone(),
16006 group_id: diagnostic.diagnostic.group_id,
16007 blocks,
16008 });
16009 cx.notify();
16010 }
16011
16012 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16013 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16014 return;
16015 };
16016
16017 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16018 if let ActiveDiagnostic::Group(group) = prev {
16019 self.display_map.update(cx, |display_map, cx| {
16020 display_map.remove_blocks(group.blocks, cx);
16021 });
16022 cx.notify();
16023 }
16024 }
16025
16026 /// Disable inline diagnostics rendering for this editor.
16027 pub fn disable_inline_diagnostics(&mut self) {
16028 self.inline_diagnostics_enabled = false;
16029 self.inline_diagnostics_update = Task::ready(());
16030 self.inline_diagnostics.clear();
16031 }
16032
16033 pub fn diagnostics_enabled(&self) -> bool {
16034 self.mode.is_full()
16035 }
16036
16037 pub fn inline_diagnostics_enabled(&self) -> bool {
16038 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16039 }
16040
16041 pub fn show_inline_diagnostics(&self) -> bool {
16042 self.show_inline_diagnostics
16043 }
16044
16045 pub fn toggle_inline_diagnostics(
16046 &mut self,
16047 _: &ToggleInlineDiagnostics,
16048 window: &mut Window,
16049 cx: &mut Context<Editor>,
16050 ) {
16051 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16052 self.refresh_inline_diagnostics(false, window, cx);
16053 }
16054
16055 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16056 self.diagnostics_max_severity = severity;
16057 self.display_map.update(cx, |display_map, _| {
16058 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16059 });
16060 }
16061
16062 pub fn toggle_diagnostics(
16063 &mut self,
16064 _: &ToggleDiagnostics,
16065 window: &mut Window,
16066 cx: &mut Context<Editor>,
16067 ) {
16068 if !self.diagnostics_enabled() {
16069 return;
16070 }
16071
16072 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16073 EditorSettings::get_global(cx)
16074 .diagnostics_max_severity
16075 .filter(|severity| severity != &DiagnosticSeverity::Off)
16076 .unwrap_or(DiagnosticSeverity::Hint)
16077 } else {
16078 DiagnosticSeverity::Off
16079 };
16080 self.set_max_diagnostics_severity(new_severity, cx);
16081 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16082 self.active_diagnostics = ActiveDiagnostic::None;
16083 self.inline_diagnostics_update = Task::ready(());
16084 self.inline_diagnostics.clear();
16085 } else {
16086 self.refresh_inline_diagnostics(false, window, cx);
16087 }
16088
16089 cx.notify();
16090 }
16091
16092 pub fn toggle_minimap(
16093 &mut self,
16094 _: &ToggleMinimap,
16095 window: &mut Window,
16096 cx: &mut Context<Editor>,
16097 ) {
16098 if self.supports_minimap(cx) {
16099 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16100 }
16101 }
16102
16103 fn refresh_inline_diagnostics(
16104 &mut self,
16105 debounce: bool,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) {
16109 let max_severity = ProjectSettings::get_global(cx)
16110 .diagnostics
16111 .inline
16112 .max_severity
16113 .unwrap_or(self.diagnostics_max_severity);
16114
16115 if !self.inline_diagnostics_enabled()
16116 || !self.show_inline_diagnostics
16117 || max_severity == DiagnosticSeverity::Off
16118 {
16119 self.inline_diagnostics_update = Task::ready(());
16120 self.inline_diagnostics.clear();
16121 return;
16122 }
16123
16124 let debounce_ms = ProjectSettings::get_global(cx)
16125 .diagnostics
16126 .inline
16127 .update_debounce_ms;
16128 let debounce = if debounce && debounce_ms > 0 {
16129 Some(Duration::from_millis(debounce_ms))
16130 } else {
16131 None
16132 };
16133 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16134 if let Some(debounce) = debounce {
16135 cx.background_executor().timer(debounce).await;
16136 }
16137 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16138 editor
16139 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16140 .ok()
16141 }) else {
16142 return;
16143 };
16144
16145 let new_inline_diagnostics = cx
16146 .background_spawn(async move {
16147 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16148 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16149 let message = diagnostic_entry
16150 .diagnostic
16151 .message
16152 .split_once('\n')
16153 .map(|(line, _)| line)
16154 .map(SharedString::new)
16155 .unwrap_or_else(|| {
16156 SharedString::from(diagnostic_entry.diagnostic.message)
16157 });
16158 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16159 let (Ok(i) | Err(i)) = inline_diagnostics
16160 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16161 inline_diagnostics.insert(
16162 i,
16163 (
16164 start_anchor,
16165 InlineDiagnostic {
16166 message,
16167 group_id: diagnostic_entry.diagnostic.group_id,
16168 start: diagnostic_entry.range.start.to_point(&snapshot),
16169 is_primary: diagnostic_entry.diagnostic.is_primary,
16170 severity: diagnostic_entry.diagnostic.severity,
16171 },
16172 ),
16173 );
16174 }
16175 inline_diagnostics
16176 })
16177 .await;
16178
16179 editor
16180 .update(cx, |editor, cx| {
16181 editor.inline_diagnostics = new_inline_diagnostics;
16182 cx.notify();
16183 })
16184 .ok();
16185 });
16186 }
16187
16188 fn pull_diagnostics(
16189 &mut self,
16190 buffer_id: Option<BufferId>,
16191 window: &Window,
16192 cx: &mut Context<Self>,
16193 ) -> Option<()> {
16194 if !self.mode().is_full() {
16195 return None;
16196 }
16197 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16198 .diagnostics
16199 .lsp_pull_diagnostics;
16200 if !pull_diagnostics_settings.enabled {
16201 return None;
16202 }
16203 let project = self.project.as_ref()?.downgrade();
16204 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16205 let mut buffers = self.buffer.read(cx).all_buffers();
16206 if let Some(buffer_id) = buffer_id {
16207 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16208 }
16209
16210 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16211 cx.background_executor().timer(debounce).await;
16212
16213 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16214 buffers
16215 .into_iter()
16216 .flat_map(|buffer| {
16217 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16218 })
16219 .collect::<FuturesUnordered<_>>()
16220 }) else {
16221 return;
16222 };
16223
16224 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16225 match pull_task {
16226 Ok(()) => {
16227 if editor
16228 .update_in(cx, |editor, window, cx| {
16229 editor.update_diagnostics_state(window, cx);
16230 })
16231 .is_err()
16232 {
16233 return;
16234 }
16235 }
16236 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16237 }
16238 }
16239 });
16240
16241 Some(())
16242 }
16243
16244 pub fn set_selections_from_remote(
16245 &mut self,
16246 selections: Vec<Selection<Anchor>>,
16247 pending_selection: Option<Selection<Anchor>>,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) {
16251 let old_cursor_position = self.selections.newest_anchor().head();
16252 self.selections.change_with(cx, |s| {
16253 s.select_anchors(selections);
16254 if let Some(pending_selection) = pending_selection {
16255 s.set_pending(pending_selection, SelectMode::Character);
16256 } else {
16257 s.clear_pending();
16258 }
16259 });
16260 self.selections_did_change(
16261 false,
16262 &old_cursor_position,
16263 SelectionEffects::default(),
16264 window,
16265 cx,
16266 );
16267 }
16268
16269 pub fn transact(
16270 &mut self,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16274 ) -> Option<TransactionId> {
16275 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16276 this.start_transaction_at(Instant::now(), window, cx);
16277 update(this, window, cx);
16278 this.end_transaction_at(Instant::now(), cx)
16279 })
16280 }
16281
16282 pub fn start_transaction_at(
16283 &mut self,
16284 now: Instant,
16285 window: &mut Window,
16286 cx: &mut Context<Self>,
16287 ) {
16288 self.end_selection(window, cx);
16289 if let Some(tx_id) = self
16290 .buffer
16291 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16292 {
16293 self.selection_history
16294 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16295 cx.emit(EditorEvent::TransactionBegun {
16296 transaction_id: tx_id,
16297 })
16298 }
16299 }
16300
16301 pub fn end_transaction_at(
16302 &mut self,
16303 now: Instant,
16304 cx: &mut Context<Self>,
16305 ) -> Option<TransactionId> {
16306 if let Some(transaction_id) = self
16307 .buffer
16308 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16309 {
16310 if let Some((_, end_selections)) =
16311 self.selection_history.transaction_mut(transaction_id)
16312 {
16313 *end_selections = Some(self.selections.disjoint_anchors());
16314 } else {
16315 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16316 }
16317
16318 cx.emit(EditorEvent::Edited { transaction_id });
16319 Some(transaction_id)
16320 } else {
16321 None
16322 }
16323 }
16324
16325 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16326 if self.selection_mark_mode {
16327 self.change_selections(None, window, cx, |s| {
16328 s.move_with(|_, sel| {
16329 sel.collapse_to(sel.head(), SelectionGoal::None);
16330 });
16331 })
16332 }
16333 self.selection_mark_mode = true;
16334 cx.notify();
16335 }
16336
16337 pub fn swap_selection_ends(
16338 &mut self,
16339 _: &actions::SwapSelectionEnds,
16340 window: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) {
16343 self.change_selections(None, window, cx, |s| {
16344 s.move_with(|_, sel| {
16345 if sel.start != sel.end {
16346 sel.reversed = !sel.reversed
16347 }
16348 });
16349 });
16350 self.request_autoscroll(Autoscroll::newest(), cx);
16351 cx.notify();
16352 }
16353
16354 pub fn toggle_fold(
16355 &mut self,
16356 _: &actions::ToggleFold,
16357 window: &mut Window,
16358 cx: &mut Context<Self>,
16359 ) {
16360 if self.is_singleton(cx) {
16361 let selection = self.selections.newest::<Point>(cx);
16362
16363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16364 let range = if selection.is_empty() {
16365 let point = selection.head().to_display_point(&display_map);
16366 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16367 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16368 .to_point(&display_map);
16369 start..end
16370 } else {
16371 selection.range()
16372 };
16373 if display_map.folds_in_range(range).next().is_some() {
16374 self.unfold_lines(&Default::default(), window, cx)
16375 } else {
16376 self.fold(&Default::default(), window, cx)
16377 }
16378 } else {
16379 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16380 let buffer_ids: HashSet<_> = self
16381 .selections
16382 .disjoint_anchor_ranges()
16383 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16384 .collect();
16385
16386 let should_unfold = buffer_ids
16387 .iter()
16388 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16389
16390 for buffer_id in buffer_ids {
16391 if should_unfold {
16392 self.unfold_buffer(buffer_id, cx);
16393 } else {
16394 self.fold_buffer(buffer_id, cx);
16395 }
16396 }
16397 }
16398 }
16399
16400 pub fn toggle_fold_recursive(
16401 &mut self,
16402 _: &actions::ToggleFoldRecursive,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) {
16406 let selection = self.selections.newest::<Point>(cx);
16407
16408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16409 let range = if selection.is_empty() {
16410 let point = selection.head().to_display_point(&display_map);
16411 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16412 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16413 .to_point(&display_map);
16414 start..end
16415 } else {
16416 selection.range()
16417 };
16418 if display_map.folds_in_range(range).next().is_some() {
16419 self.unfold_recursive(&Default::default(), window, cx)
16420 } else {
16421 self.fold_recursive(&Default::default(), window, cx)
16422 }
16423 }
16424
16425 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16426 if self.is_singleton(cx) {
16427 let mut to_fold = Vec::new();
16428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16429 let selections = self.selections.all_adjusted(cx);
16430
16431 for selection in selections {
16432 let range = selection.range().sorted();
16433 let buffer_start_row = range.start.row;
16434
16435 if range.start.row != range.end.row {
16436 let mut found = false;
16437 let mut row = range.start.row;
16438 while row <= range.end.row {
16439 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16440 {
16441 found = true;
16442 row = crease.range().end.row + 1;
16443 to_fold.push(crease);
16444 } else {
16445 row += 1
16446 }
16447 }
16448 if found {
16449 continue;
16450 }
16451 }
16452
16453 for row in (0..=range.start.row).rev() {
16454 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16455 if crease.range().end.row >= buffer_start_row {
16456 to_fold.push(crease);
16457 if row <= range.start.row {
16458 break;
16459 }
16460 }
16461 }
16462 }
16463 }
16464
16465 self.fold_creases(to_fold, true, window, cx);
16466 } else {
16467 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16468 let buffer_ids = self
16469 .selections
16470 .disjoint_anchor_ranges()
16471 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16472 .collect::<HashSet<_>>();
16473 for buffer_id in buffer_ids {
16474 self.fold_buffer(buffer_id, cx);
16475 }
16476 }
16477 }
16478
16479 fn fold_at_level(
16480 &mut self,
16481 fold_at: &FoldAtLevel,
16482 window: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) {
16485 if !self.buffer.read(cx).is_singleton() {
16486 return;
16487 }
16488
16489 let fold_at_level = fold_at.0;
16490 let snapshot = self.buffer.read(cx).snapshot(cx);
16491 let mut to_fold = Vec::new();
16492 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16493
16494 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16495 while start_row < end_row {
16496 match self
16497 .snapshot(window, cx)
16498 .crease_for_buffer_row(MultiBufferRow(start_row))
16499 {
16500 Some(crease) => {
16501 let nested_start_row = crease.range().start.row + 1;
16502 let nested_end_row = crease.range().end.row;
16503
16504 if current_level < fold_at_level {
16505 stack.push((nested_start_row, nested_end_row, current_level + 1));
16506 } else if current_level == fold_at_level {
16507 to_fold.push(crease);
16508 }
16509
16510 start_row = nested_end_row + 1;
16511 }
16512 None => start_row += 1,
16513 }
16514 }
16515 }
16516
16517 self.fold_creases(to_fold, true, window, cx);
16518 }
16519
16520 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16521 if self.buffer.read(cx).is_singleton() {
16522 let mut fold_ranges = Vec::new();
16523 let snapshot = self.buffer.read(cx).snapshot(cx);
16524
16525 for row in 0..snapshot.max_row().0 {
16526 if let Some(foldable_range) = self
16527 .snapshot(window, cx)
16528 .crease_for_buffer_row(MultiBufferRow(row))
16529 {
16530 fold_ranges.push(foldable_range);
16531 }
16532 }
16533
16534 self.fold_creases(fold_ranges, true, window, cx);
16535 } else {
16536 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16537 editor
16538 .update_in(cx, |editor, _, cx| {
16539 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16540 editor.fold_buffer(buffer_id, cx);
16541 }
16542 })
16543 .ok();
16544 });
16545 }
16546 }
16547
16548 pub fn fold_function_bodies(
16549 &mut self,
16550 _: &actions::FoldFunctionBodies,
16551 window: &mut Window,
16552 cx: &mut Context<Self>,
16553 ) {
16554 let snapshot = self.buffer.read(cx).snapshot(cx);
16555
16556 let ranges = snapshot
16557 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16558 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16559 .collect::<Vec<_>>();
16560
16561 let creases = ranges
16562 .into_iter()
16563 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16564 .collect();
16565
16566 self.fold_creases(creases, true, window, cx);
16567 }
16568
16569 pub fn fold_recursive(
16570 &mut self,
16571 _: &actions::FoldRecursive,
16572 window: &mut Window,
16573 cx: &mut Context<Self>,
16574 ) {
16575 let mut to_fold = Vec::new();
16576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16577 let selections = self.selections.all_adjusted(cx);
16578
16579 for selection in selections {
16580 let range = selection.range().sorted();
16581 let buffer_start_row = range.start.row;
16582
16583 if range.start.row != range.end.row {
16584 let mut found = false;
16585 for row in range.start.row..=range.end.row {
16586 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16587 found = true;
16588 to_fold.push(crease);
16589 }
16590 }
16591 if found {
16592 continue;
16593 }
16594 }
16595
16596 for row in (0..=range.start.row).rev() {
16597 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16598 if crease.range().end.row >= buffer_start_row {
16599 to_fold.push(crease);
16600 } else {
16601 break;
16602 }
16603 }
16604 }
16605 }
16606
16607 self.fold_creases(to_fold, true, window, cx);
16608 }
16609
16610 pub fn fold_at(
16611 &mut self,
16612 buffer_row: MultiBufferRow,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16617
16618 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16619 let autoscroll = self
16620 .selections
16621 .all::<Point>(cx)
16622 .iter()
16623 .any(|selection| crease.range().overlaps(&selection.range()));
16624
16625 self.fold_creases(vec![crease], autoscroll, window, cx);
16626 }
16627 }
16628
16629 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16630 if self.is_singleton(cx) {
16631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16632 let buffer = &display_map.buffer_snapshot;
16633 let selections = self.selections.all::<Point>(cx);
16634 let ranges = selections
16635 .iter()
16636 .map(|s| {
16637 let range = s.display_range(&display_map).sorted();
16638 let mut start = range.start.to_point(&display_map);
16639 let mut end = range.end.to_point(&display_map);
16640 start.column = 0;
16641 end.column = buffer.line_len(MultiBufferRow(end.row));
16642 start..end
16643 })
16644 .collect::<Vec<_>>();
16645
16646 self.unfold_ranges(&ranges, true, true, cx);
16647 } else {
16648 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16649 let buffer_ids = self
16650 .selections
16651 .disjoint_anchor_ranges()
16652 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16653 .collect::<HashSet<_>>();
16654 for buffer_id in buffer_ids {
16655 self.unfold_buffer(buffer_id, cx);
16656 }
16657 }
16658 }
16659
16660 pub fn unfold_recursive(
16661 &mut self,
16662 _: &UnfoldRecursive,
16663 _window: &mut Window,
16664 cx: &mut Context<Self>,
16665 ) {
16666 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16667 let selections = self.selections.all::<Point>(cx);
16668 let ranges = selections
16669 .iter()
16670 .map(|s| {
16671 let mut range = s.display_range(&display_map).sorted();
16672 *range.start.column_mut() = 0;
16673 *range.end.column_mut() = display_map.line_len(range.end.row());
16674 let start = range.start.to_point(&display_map);
16675 let end = range.end.to_point(&display_map);
16676 start..end
16677 })
16678 .collect::<Vec<_>>();
16679
16680 self.unfold_ranges(&ranges, true, true, cx);
16681 }
16682
16683 pub fn unfold_at(
16684 &mut self,
16685 buffer_row: MultiBufferRow,
16686 _window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16690
16691 let intersection_range = Point::new(buffer_row.0, 0)
16692 ..Point::new(
16693 buffer_row.0,
16694 display_map.buffer_snapshot.line_len(buffer_row),
16695 );
16696
16697 let autoscroll = self
16698 .selections
16699 .all::<Point>(cx)
16700 .iter()
16701 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16702
16703 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16704 }
16705
16706 pub fn unfold_all(
16707 &mut self,
16708 _: &actions::UnfoldAll,
16709 _window: &mut Window,
16710 cx: &mut Context<Self>,
16711 ) {
16712 if self.buffer.read(cx).is_singleton() {
16713 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16714 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16715 } else {
16716 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16717 editor
16718 .update(cx, |editor, cx| {
16719 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16720 editor.unfold_buffer(buffer_id, cx);
16721 }
16722 })
16723 .ok();
16724 });
16725 }
16726 }
16727
16728 pub fn fold_selected_ranges(
16729 &mut self,
16730 _: &FoldSelectedRanges,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) {
16734 let selections = self.selections.all_adjusted(cx);
16735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16736 let ranges = selections
16737 .into_iter()
16738 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16739 .collect::<Vec<_>>();
16740 self.fold_creases(ranges, true, window, cx);
16741 }
16742
16743 pub fn fold_ranges<T: ToOffset + Clone>(
16744 &mut self,
16745 ranges: Vec<Range<T>>,
16746 auto_scroll: bool,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16751 let ranges = ranges
16752 .into_iter()
16753 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16754 .collect::<Vec<_>>();
16755 self.fold_creases(ranges, auto_scroll, window, cx);
16756 }
16757
16758 pub fn fold_creases<T: ToOffset + Clone>(
16759 &mut self,
16760 creases: Vec<Crease<T>>,
16761 auto_scroll: bool,
16762 _window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) {
16765 if creases.is_empty() {
16766 return;
16767 }
16768
16769 let mut buffers_affected = HashSet::default();
16770 let multi_buffer = self.buffer().read(cx);
16771 for crease in &creases {
16772 if let Some((_, buffer, _)) =
16773 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16774 {
16775 buffers_affected.insert(buffer.read(cx).remote_id());
16776 };
16777 }
16778
16779 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16780
16781 if auto_scroll {
16782 self.request_autoscroll(Autoscroll::fit(), cx);
16783 }
16784
16785 cx.notify();
16786
16787 self.scrollbar_marker_state.dirty = true;
16788 self.folds_did_change(cx);
16789 }
16790
16791 /// Removes any folds whose ranges intersect any of the given ranges.
16792 pub fn unfold_ranges<T: ToOffset + Clone>(
16793 &mut self,
16794 ranges: &[Range<T>],
16795 inclusive: bool,
16796 auto_scroll: bool,
16797 cx: &mut Context<Self>,
16798 ) {
16799 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16800 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16801 });
16802 self.folds_did_change(cx);
16803 }
16804
16805 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16806 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16807 return;
16808 }
16809 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16810 self.display_map.update(cx, |display_map, cx| {
16811 display_map.fold_buffers([buffer_id], cx)
16812 });
16813 cx.emit(EditorEvent::BufferFoldToggled {
16814 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16815 folded: true,
16816 });
16817 cx.notify();
16818 }
16819
16820 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16821 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16822 return;
16823 }
16824 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16825 self.display_map.update(cx, |display_map, cx| {
16826 display_map.unfold_buffers([buffer_id], cx);
16827 });
16828 cx.emit(EditorEvent::BufferFoldToggled {
16829 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16830 folded: false,
16831 });
16832 cx.notify();
16833 }
16834
16835 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16836 self.display_map.read(cx).is_buffer_folded(buffer)
16837 }
16838
16839 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16840 self.display_map.read(cx).folded_buffers()
16841 }
16842
16843 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16844 self.display_map.update(cx, |display_map, cx| {
16845 display_map.disable_header_for_buffer(buffer_id, cx);
16846 });
16847 cx.notify();
16848 }
16849
16850 /// Removes any folds with the given ranges.
16851 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16852 &mut self,
16853 ranges: &[Range<T>],
16854 type_id: TypeId,
16855 auto_scroll: bool,
16856 cx: &mut Context<Self>,
16857 ) {
16858 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16859 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16860 });
16861 self.folds_did_change(cx);
16862 }
16863
16864 fn remove_folds_with<T: ToOffset + Clone>(
16865 &mut self,
16866 ranges: &[Range<T>],
16867 auto_scroll: bool,
16868 cx: &mut Context<Self>,
16869 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16870 ) {
16871 if ranges.is_empty() {
16872 return;
16873 }
16874
16875 let mut buffers_affected = HashSet::default();
16876 let multi_buffer = self.buffer().read(cx);
16877 for range in ranges {
16878 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16879 buffers_affected.insert(buffer.read(cx).remote_id());
16880 };
16881 }
16882
16883 self.display_map.update(cx, update);
16884
16885 if auto_scroll {
16886 self.request_autoscroll(Autoscroll::fit(), cx);
16887 }
16888
16889 cx.notify();
16890 self.scrollbar_marker_state.dirty = true;
16891 self.active_indent_guides_state.dirty = true;
16892 }
16893
16894 pub fn update_fold_widths(
16895 &mut self,
16896 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16897 cx: &mut Context<Self>,
16898 ) -> bool {
16899 self.display_map
16900 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16901 }
16902
16903 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16904 self.display_map.read(cx).fold_placeholder.clone()
16905 }
16906
16907 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16908 self.buffer.update(cx, |buffer, cx| {
16909 buffer.set_all_diff_hunks_expanded(cx);
16910 });
16911 }
16912
16913 pub fn expand_all_diff_hunks(
16914 &mut self,
16915 _: &ExpandAllDiffHunks,
16916 _window: &mut Window,
16917 cx: &mut Context<Self>,
16918 ) {
16919 self.buffer.update(cx, |buffer, cx| {
16920 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16921 });
16922 }
16923
16924 pub fn toggle_selected_diff_hunks(
16925 &mut self,
16926 _: &ToggleSelectedDiffHunks,
16927 _window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16931 self.toggle_diff_hunks_in_ranges(ranges, cx);
16932 }
16933
16934 pub fn diff_hunks_in_ranges<'a>(
16935 &'a self,
16936 ranges: &'a [Range<Anchor>],
16937 buffer: &'a MultiBufferSnapshot,
16938 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16939 ranges.iter().flat_map(move |range| {
16940 let end_excerpt_id = range.end.excerpt_id;
16941 let range = range.to_point(buffer);
16942 let mut peek_end = range.end;
16943 if range.end.row < buffer.max_row().0 {
16944 peek_end = Point::new(range.end.row + 1, 0);
16945 }
16946 buffer
16947 .diff_hunks_in_range(range.start..peek_end)
16948 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16949 })
16950 }
16951
16952 pub fn has_stageable_diff_hunks_in_ranges(
16953 &self,
16954 ranges: &[Range<Anchor>],
16955 snapshot: &MultiBufferSnapshot,
16956 ) -> bool {
16957 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16958 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16959 }
16960
16961 pub fn toggle_staged_selected_diff_hunks(
16962 &mut self,
16963 _: &::git::ToggleStaged,
16964 _: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 let snapshot = self.buffer.read(cx).snapshot(cx);
16968 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16969 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16970 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16971 }
16972
16973 pub fn set_render_diff_hunk_controls(
16974 &mut self,
16975 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16976 cx: &mut Context<Self>,
16977 ) {
16978 self.render_diff_hunk_controls = render_diff_hunk_controls;
16979 cx.notify();
16980 }
16981
16982 pub fn stage_and_next(
16983 &mut self,
16984 _: &::git::StageAndNext,
16985 window: &mut Window,
16986 cx: &mut Context<Self>,
16987 ) {
16988 self.do_stage_or_unstage_and_next(true, window, cx);
16989 }
16990
16991 pub fn unstage_and_next(
16992 &mut self,
16993 _: &::git::UnstageAndNext,
16994 window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 self.do_stage_or_unstage_and_next(false, window, cx);
16998 }
16999
17000 pub fn stage_or_unstage_diff_hunks(
17001 &mut self,
17002 stage: bool,
17003 ranges: Vec<Range<Anchor>>,
17004 cx: &mut Context<Self>,
17005 ) {
17006 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17007 cx.spawn(async move |this, cx| {
17008 task.await?;
17009 this.update(cx, |this, cx| {
17010 let snapshot = this.buffer.read(cx).snapshot(cx);
17011 let chunk_by = this
17012 .diff_hunks_in_ranges(&ranges, &snapshot)
17013 .chunk_by(|hunk| hunk.buffer_id);
17014 for (buffer_id, hunks) in &chunk_by {
17015 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17016 }
17017 })
17018 })
17019 .detach_and_log_err(cx);
17020 }
17021
17022 fn save_buffers_for_ranges_if_needed(
17023 &mut self,
17024 ranges: &[Range<Anchor>],
17025 cx: &mut Context<Editor>,
17026 ) -> Task<Result<()>> {
17027 let multibuffer = self.buffer.read(cx);
17028 let snapshot = multibuffer.read(cx);
17029 let buffer_ids: HashSet<_> = ranges
17030 .iter()
17031 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17032 .collect();
17033 drop(snapshot);
17034
17035 let mut buffers = HashSet::default();
17036 for buffer_id in buffer_ids {
17037 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17038 let buffer = buffer_entity.read(cx);
17039 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17040 {
17041 buffers.insert(buffer_entity);
17042 }
17043 }
17044 }
17045
17046 if let Some(project) = &self.project {
17047 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17048 } else {
17049 Task::ready(Ok(()))
17050 }
17051 }
17052
17053 fn do_stage_or_unstage_and_next(
17054 &mut self,
17055 stage: bool,
17056 window: &mut Window,
17057 cx: &mut Context<Self>,
17058 ) {
17059 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17060
17061 if ranges.iter().any(|range| range.start != range.end) {
17062 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17063 return;
17064 }
17065
17066 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17067 let snapshot = self.snapshot(window, cx);
17068 let position = self.selections.newest::<Point>(cx).head();
17069 let mut row = snapshot
17070 .buffer_snapshot
17071 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17072 .find(|hunk| hunk.row_range.start.0 > position.row)
17073 .map(|hunk| hunk.row_range.start);
17074
17075 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17076 // Outside of the project diff editor, wrap around to the beginning.
17077 if !all_diff_hunks_expanded {
17078 row = row.or_else(|| {
17079 snapshot
17080 .buffer_snapshot
17081 .diff_hunks_in_range(Point::zero()..position)
17082 .find(|hunk| hunk.row_range.end.0 < position.row)
17083 .map(|hunk| hunk.row_range.start)
17084 });
17085 }
17086
17087 if let Some(row) = row {
17088 let destination = Point::new(row.0, 0);
17089 let autoscroll = Autoscroll::center();
17090
17091 self.unfold_ranges(&[destination..destination], false, false, cx);
17092 self.change_selections(Some(autoscroll), window, cx, |s| {
17093 s.select_ranges([destination..destination]);
17094 });
17095 }
17096 }
17097
17098 fn do_stage_or_unstage(
17099 &self,
17100 stage: bool,
17101 buffer_id: BufferId,
17102 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17103 cx: &mut App,
17104 ) -> Option<()> {
17105 let project = self.project.as_ref()?;
17106 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17107 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17108 let buffer_snapshot = buffer.read(cx).snapshot();
17109 let file_exists = buffer_snapshot
17110 .file()
17111 .is_some_and(|file| file.disk_state().exists());
17112 diff.update(cx, |diff, cx| {
17113 diff.stage_or_unstage_hunks(
17114 stage,
17115 &hunks
17116 .map(|hunk| buffer_diff::DiffHunk {
17117 buffer_range: hunk.buffer_range,
17118 diff_base_byte_range: hunk.diff_base_byte_range,
17119 secondary_status: hunk.secondary_status,
17120 range: Point::zero()..Point::zero(), // unused
17121 })
17122 .collect::<Vec<_>>(),
17123 &buffer_snapshot,
17124 file_exists,
17125 cx,
17126 )
17127 });
17128 None
17129 }
17130
17131 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17132 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17133 self.buffer
17134 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17135 }
17136
17137 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17138 self.buffer.update(cx, |buffer, cx| {
17139 let ranges = vec![Anchor::min()..Anchor::max()];
17140 if !buffer.all_diff_hunks_expanded()
17141 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17142 {
17143 buffer.collapse_diff_hunks(ranges, cx);
17144 true
17145 } else {
17146 false
17147 }
17148 })
17149 }
17150
17151 fn toggle_diff_hunks_in_ranges(
17152 &mut self,
17153 ranges: Vec<Range<Anchor>>,
17154 cx: &mut Context<Editor>,
17155 ) {
17156 self.buffer.update(cx, |buffer, cx| {
17157 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17158 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17159 })
17160 }
17161
17162 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17163 self.buffer.update(cx, |buffer, cx| {
17164 let snapshot = buffer.snapshot(cx);
17165 let excerpt_id = range.end.excerpt_id;
17166 let point_range = range.to_point(&snapshot);
17167 let expand = !buffer.single_hunk_is_expanded(range, cx);
17168 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17169 })
17170 }
17171
17172 pub(crate) fn apply_all_diff_hunks(
17173 &mut self,
17174 _: &ApplyAllDiffHunks,
17175 window: &mut Window,
17176 cx: &mut Context<Self>,
17177 ) {
17178 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17179
17180 let buffers = self.buffer.read(cx).all_buffers();
17181 for branch_buffer in buffers {
17182 branch_buffer.update(cx, |branch_buffer, cx| {
17183 branch_buffer.merge_into_base(Vec::new(), cx);
17184 });
17185 }
17186
17187 if let Some(project) = self.project.clone() {
17188 self.save(
17189 SaveOptions {
17190 format: true,
17191 autosave: false,
17192 },
17193 project,
17194 window,
17195 cx,
17196 )
17197 .detach_and_log_err(cx);
17198 }
17199 }
17200
17201 pub(crate) fn apply_selected_diff_hunks(
17202 &mut self,
17203 _: &ApplyDiffHunk,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 ) {
17207 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17208 let snapshot = self.snapshot(window, cx);
17209 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17210 let mut ranges_by_buffer = HashMap::default();
17211 self.transact(window, cx, |editor, _window, cx| {
17212 for hunk in hunks {
17213 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17214 ranges_by_buffer
17215 .entry(buffer.clone())
17216 .or_insert_with(Vec::new)
17217 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17218 }
17219 }
17220
17221 for (buffer, ranges) in ranges_by_buffer {
17222 buffer.update(cx, |buffer, cx| {
17223 buffer.merge_into_base(ranges, cx);
17224 });
17225 }
17226 });
17227
17228 if let Some(project) = self.project.clone() {
17229 self.save(
17230 SaveOptions {
17231 format: true,
17232 autosave: false,
17233 },
17234 project,
17235 window,
17236 cx,
17237 )
17238 .detach_and_log_err(cx);
17239 }
17240 }
17241
17242 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17243 if hovered != self.gutter_hovered {
17244 self.gutter_hovered = hovered;
17245 cx.notify();
17246 }
17247 }
17248
17249 pub fn insert_blocks(
17250 &mut self,
17251 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17252 autoscroll: Option<Autoscroll>,
17253 cx: &mut Context<Self>,
17254 ) -> Vec<CustomBlockId> {
17255 let blocks = self
17256 .display_map
17257 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17258 if let Some(autoscroll) = autoscroll {
17259 self.request_autoscroll(autoscroll, cx);
17260 }
17261 cx.notify();
17262 blocks
17263 }
17264
17265 pub fn resize_blocks(
17266 &mut self,
17267 heights: HashMap<CustomBlockId, u32>,
17268 autoscroll: Option<Autoscroll>,
17269 cx: &mut Context<Self>,
17270 ) {
17271 self.display_map
17272 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17273 if let Some(autoscroll) = autoscroll {
17274 self.request_autoscroll(autoscroll, cx);
17275 }
17276 cx.notify();
17277 }
17278
17279 pub fn replace_blocks(
17280 &mut self,
17281 renderers: HashMap<CustomBlockId, RenderBlock>,
17282 autoscroll: Option<Autoscroll>,
17283 cx: &mut Context<Self>,
17284 ) {
17285 self.display_map
17286 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17287 if let Some(autoscroll) = autoscroll {
17288 self.request_autoscroll(autoscroll, cx);
17289 }
17290 cx.notify();
17291 }
17292
17293 pub fn remove_blocks(
17294 &mut self,
17295 block_ids: HashSet<CustomBlockId>,
17296 autoscroll: Option<Autoscroll>,
17297 cx: &mut Context<Self>,
17298 ) {
17299 self.display_map.update(cx, |display_map, cx| {
17300 display_map.remove_blocks(block_ids, cx)
17301 });
17302 if let Some(autoscroll) = autoscroll {
17303 self.request_autoscroll(autoscroll, cx);
17304 }
17305 cx.notify();
17306 }
17307
17308 pub fn row_for_block(
17309 &self,
17310 block_id: CustomBlockId,
17311 cx: &mut Context<Self>,
17312 ) -> Option<DisplayRow> {
17313 self.display_map
17314 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17315 }
17316
17317 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17318 self.focused_block = Some(focused_block);
17319 }
17320
17321 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17322 self.focused_block.take()
17323 }
17324
17325 pub fn insert_creases(
17326 &mut self,
17327 creases: impl IntoIterator<Item = Crease<Anchor>>,
17328 cx: &mut Context<Self>,
17329 ) -> Vec<CreaseId> {
17330 self.display_map
17331 .update(cx, |map, cx| map.insert_creases(creases, cx))
17332 }
17333
17334 pub fn remove_creases(
17335 &mut self,
17336 ids: impl IntoIterator<Item = CreaseId>,
17337 cx: &mut Context<Self>,
17338 ) -> Vec<(CreaseId, Range<Anchor>)> {
17339 self.display_map
17340 .update(cx, |map, cx| map.remove_creases(ids, cx))
17341 }
17342
17343 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17344 self.display_map
17345 .update(cx, |map, cx| map.snapshot(cx))
17346 .longest_row()
17347 }
17348
17349 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17350 self.display_map
17351 .update(cx, |map, cx| map.snapshot(cx))
17352 .max_point()
17353 }
17354
17355 pub fn text(&self, cx: &App) -> String {
17356 self.buffer.read(cx).read(cx).text()
17357 }
17358
17359 pub fn is_empty(&self, cx: &App) -> bool {
17360 self.buffer.read(cx).read(cx).is_empty()
17361 }
17362
17363 pub fn text_option(&self, cx: &App) -> Option<String> {
17364 let text = self.text(cx);
17365 let text = text.trim();
17366
17367 if text.is_empty() {
17368 return None;
17369 }
17370
17371 Some(text.to_string())
17372 }
17373
17374 pub fn set_text(
17375 &mut self,
17376 text: impl Into<Arc<str>>,
17377 window: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 self.transact(window, cx, |this, _, cx| {
17381 this.buffer
17382 .read(cx)
17383 .as_singleton()
17384 .expect("you can only call set_text on editors for singleton buffers")
17385 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17386 });
17387 }
17388
17389 pub fn display_text(&self, cx: &mut App) -> String {
17390 self.display_map
17391 .update(cx, |map, cx| map.snapshot(cx))
17392 .text()
17393 }
17394
17395 fn create_minimap(
17396 &self,
17397 minimap_settings: MinimapSettings,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) -> Option<Entity<Self>> {
17401 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17402 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17403 }
17404
17405 fn initialize_new_minimap(
17406 &self,
17407 minimap_settings: MinimapSettings,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) -> Entity<Self> {
17411 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17412
17413 let mut minimap = Editor::new_internal(
17414 EditorMode::Minimap {
17415 parent: cx.weak_entity(),
17416 },
17417 self.buffer.clone(),
17418 self.project.clone(),
17419 Some(self.display_map.clone()),
17420 window,
17421 cx,
17422 );
17423 minimap.scroll_manager.clone_state(&self.scroll_manager);
17424 minimap.set_text_style_refinement(TextStyleRefinement {
17425 font_size: Some(MINIMAP_FONT_SIZE),
17426 font_weight: Some(MINIMAP_FONT_WEIGHT),
17427 ..Default::default()
17428 });
17429 minimap.update_minimap_configuration(minimap_settings, cx);
17430 cx.new(|_| minimap)
17431 }
17432
17433 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17434 let current_line_highlight = minimap_settings
17435 .current_line_highlight
17436 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17437 self.set_current_line_highlight(Some(current_line_highlight));
17438 }
17439
17440 pub fn minimap(&self) -> Option<&Entity<Self>> {
17441 self.minimap
17442 .as_ref()
17443 .filter(|_| self.minimap_visibility.visible())
17444 }
17445
17446 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17447 let mut wrap_guides = smallvec![];
17448
17449 if self.show_wrap_guides == Some(false) {
17450 return wrap_guides;
17451 }
17452
17453 let settings = self.buffer.read(cx).language_settings(cx);
17454 if settings.show_wrap_guides {
17455 match self.soft_wrap_mode(cx) {
17456 SoftWrap::Column(soft_wrap) => {
17457 wrap_guides.push((soft_wrap as usize, true));
17458 }
17459 SoftWrap::Bounded(soft_wrap) => {
17460 wrap_guides.push((soft_wrap as usize, true));
17461 }
17462 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17463 }
17464 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17465 }
17466
17467 wrap_guides
17468 }
17469
17470 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17471 let settings = self.buffer.read(cx).language_settings(cx);
17472 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17473 match mode {
17474 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17475 SoftWrap::None
17476 }
17477 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17478 language_settings::SoftWrap::PreferredLineLength => {
17479 SoftWrap::Column(settings.preferred_line_length)
17480 }
17481 language_settings::SoftWrap::Bounded => {
17482 SoftWrap::Bounded(settings.preferred_line_length)
17483 }
17484 }
17485 }
17486
17487 pub fn set_soft_wrap_mode(
17488 &mut self,
17489 mode: language_settings::SoftWrap,
17490
17491 cx: &mut Context<Self>,
17492 ) {
17493 self.soft_wrap_mode_override = Some(mode);
17494 cx.notify();
17495 }
17496
17497 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17498 self.hard_wrap = hard_wrap;
17499 cx.notify();
17500 }
17501
17502 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17503 self.text_style_refinement = Some(style);
17504 }
17505
17506 /// called by the Element so we know what style we were most recently rendered with.
17507 pub(crate) fn set_style(
17508 &mut self,
17509 style: EditorStyle,
17510 window: &mut Window,
17511 cx: &mut Context<Self>,
17512 ) {
17513 // We intentionally do not inform the display map about the minimap style
17514 // so that wrapping is not recalculated and stays consistent for the editor
17515 // and its linked minimap.
17516 if !self.mode.is_minimap() {
17517 let rem_size = window.rem_size();
17518 self.display_map.update(cx, |map, cx| {
17519 map.set_font(
17520 style.text.font(),
17521 style.text.font_size.to_pixels(rem_size),
17522 cx,
17523 )
17524 });
17525 }
17526 self.style = Some(style);
17527 }
17528
17529 pub fn style(&self) -> Option<&EditorStyle> {
17530 self.style.as_ref()
17531 }
17532
17533 // Called by the element. This method is not designed to be called outside of the editor
17534 // element's layout code because it does not notify when rewrapping is computed synchronously.
17535 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17536 self.display_map
17537 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17538 }
17539
17540 pub fn set_soft_wrap(&mut self) {
17541 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17542 }
17543
17544 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17545 if self.soft_wrap_mode_override.is_some() {
17546 self.soft_wrap_mode_override.take();
17547 } else {
17548 let soft_wrap = match self.soft_wrap_mode(cx) {
17549 SoftWrap::GitDiff => return,
17550 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17551 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17552 language_settings::SoftWrap::None
17553 }
17554 };
17555 self.soft_wrap_mode_override = Some(soft_wrap);
17556 }
17557 cx.notify();
17558 }
17559
17560 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17561 let Some(workspace) = self.workspace() else {
17562 return;
17563 };
17564 let fs = workspace.read(cx).app_state().fs.clone();
17565 let current_show = TabBarSettings::get_global(cx).show;
17566 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17567 setting.show = Some(!current_show);
17568 });
17569 }
17570
17571 pub fn toggle_indent_guides(
17572 &mut self,
17573 _: &ToggleIndentGuides,
17574 _: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17578 self.buffer
17579 .read(cx)
17580 .language_settings(cx)
17581 .indent_guides
17582 .enabled
17583 });
17584 self.show_indent_guides = Some(!currently_enabled);
17585 cx.notify();
17586 }
17587
17588 fn should_show_indent_guides(&self) -> Option<bool> {
17589 self.show_indent_guides
17590 }
17591
17592 pub fn toggle_line_numbers(
17593 &mut self,
17594 _: &ToggleLineNumbers,
17595 _: &mut Window,
17596 cx: &mut Context<Self>,
17597 ) {
17598 let mut editor_settings = EditorSettings::get_global(cx).clone();
17599 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17600 EditorSettings::override_global(editor_settings, cx);
17601 }
17602
17603 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17604 if let Some(show_line_numbers) = self.show_line_numbers {
17605 return show_line_numbers;
17606 }
17607 EditorSettings::get_global(cx).gutter.line_numbers
17608 }
17609
17610 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17611 self.use_relative_line_numbers
17612 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17613 }
17614
17615 pub fn toggle_relative_line_numbers(
17616 &mut self,
17617 _: &ToggleRelativeLineNumbers,
17618 _: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 let is_relative = self.should_use_relative_line_numbers(cx);
17622 self.set_relative_line_number(Some(!is_relative), cx)
17623 }
17624
17625 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17626 self.use_relative_line_numbers = is_relative;
17627 cx.notify();
17628 }
17629
17630 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17631 self.show_gutter = show_gutter;
17632 cx.notify();
17633 }
17634
17635 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17636 self.show_scrollbars = ScrollbarAxes {
17637 horizontal: show,
17638 vertical: show,
17639 };
17640 cx.notify();
17641 }
17642
17643 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17644 self.show_scrollbars.vertical = show;
17645 cx.notify();
17646 }
17647
17648 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17649 self.show_scrollbars.horizontal = show;
17650 cx.notify();
17651 }
17652
17653 pub fn set_minimap_visibility(
17654 &mut self,
17655 minimap_visibility: MinimapVisibility,
17656 window: &mut Window,
17657 cx: &mut Context<Self>,
17658 ) {
17659 if self.minimap_visibility != minimap_visibility {
17660 if minimap_visibility.visible() && self.minimap.is_none() {
17661 let minimap_settings = EditorSettings::get_global(cx).minimap;
17662 self.minimap =
17663 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17664 }
17665 self.minimap_visibility = minimap_visibility;
17666 cx.notify();
17667 }
17668 }
17669
17670 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17671 self.set_show_scrollbars(false, cx);
17672 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17673 }
17674
17675 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17676 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17677 }
17678
17679 /// Normally the text in full mode and auto height editors is padded on the
17680 /// left side by roughly half a character width for improved hit testing.
17681 ///
17682 /// Use this method to disable this for cases where this is not wanted (e.g.
17683 /// if you want to align the editor text with some other text above or below)
17684 /// or if you want to add this padding to single-line editors.
17685 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17686 self.offset_content = offset_content;
17687 cx.notify();
17688 }
17689
17690 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17691 self.show_line_numbers = Some(show_line_numbers);
17692 cx.notify();
17693 }
17694
17695 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17696 self.disable_expand_excerpt_buttons = true;
17697 cx.notify();
17698 }
17699
17700 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17701 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17702 cx.notify();
17703 }
17704
17705 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17706 self.show_code_actions = Some(show_code_actions);
17707 cx.notify();
17708 }
17709
17710 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17711 self.show_runnables = Some(show_runnables);
17712 cx.notify();
17713 }
17714
17715 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17716 self.show_breakpoints = Some(show_breakpoints);
17717 cx.notify();
17718 }
17719
17720 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17721 if self.display_map.read(cx).masked != masked {
17722 self.display_map.update(cx, |map, _| map.masked = masked);
17723 }
17724 cx.notify()
17725 }
17726
17727 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17728 self.show_wrap_guides = Some(show_wrap_guides);
17729 cx.notify();
17730 }
17731
17732 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17733 self.show_indent_guides = Some(show_indent_guides);
17734 cx.notify();
17735 }
17736
17737 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17738 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17739 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17740 if let Some(dir) = file.abs_path(cx).parent() {
17741 return Some(dir.to_owned());
17742 }
17743 }
17744
17745 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17746 return Some(project_path.path.to_path_buf());
17747 }
17748 }
17749
17750 None
17751 }
17752
17753 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17754 self.active_excerpt(cx)?
17755 .1
17756 .read(cx)
17757 .file()
17758 .and_then(|f| f.as_local())
17759 }
17760
17761 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17762 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17763 let buffer = buffer.read(cx);
17764 if let Some(project_path) = buffer.project_path(cx) {
17765 let project = self.project.as_ref()?.read(cx);
17766 project.absolute_path(&project_path, cx)
17767 } else {
17768 buffer
17769 .file()
17770 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17771 }
17772 })
17773 }
17774
17775 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17776 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17777 let project_path = buffer.read(cx).project_path(cx)?;
17778 let project = self.project.as_ref()?.read(cx);
17779 let entry = project.entry_for_path(&project_path, cx)?;
17780 let path = entry.path.to_path_buf();
17781 Some(path)
17782 })
17783 }
17784
17785 pub fn reveal_in_finder(
17786 &mut self,
17787 _: &RevealInFileManager,
17788 _window: &mut Window,
17789 cx: &mut Context<Self>,
17790 ) {
17791 if let Some(target) = self.target_file(cx) {
17792 cx.reveal_path(&target.abs_path(cx));
17793 }
17794 }
17795
17796 pub fn copy_path(
17797 &mut self,
17798 _: &zed_actions::workspace::CopyPath,
17799 _window: &mut Window,
17800 cx: &mut Context<Self>,
17801 ) {
17802 if let Some(path) = self.target_file_abs_path(cx) {
17803 if let Some(path) = path.to_str() {
17804 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17805 }
17806 }
17807 }
17808
17809 pub fn copy_relative_path(
17810 &mut self,
17811 _: &zed_actions::workspace::CopyRelativePath,
17812 _window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) {
17815 if let Some(path) = self.target_file_path(cx) {
17816 if let Some(path) = path.to_str() {
17817 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17818 }
17819 }
17820 }
17821
17822 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17823 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17824 buffer.read(cx).project_path(cx)
17825 } else {
17826 None
17827 }
17828 }
17829
17830 // Returns true if the editor handled a go-to-line request
17831 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17832 maybe!({
17833 let breakpoint_store = self.breakpoint_store.as_ref()?;
17834
17835 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17836 else {
17837 self.clear_row_highlights::<ActiveDebugLine>();
17838 return None;
17839 };
17840
17841 let position = active_stack_frame.position;
17842 let buffer_id = position.buffer_id?;
17843 let snapshot = self
17844 .project
17845 .as_ref()?
17846 .read(cx)
17847 .buffer_for_id(buffer_id, cx)?
17848 .read(cx)
17849 .snapshot();
17850
17851 let mut handled = false;
17852 for (id, ExcerptRange { context, .. }) in
17853 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17854 {
17855 if context.start.cmp(&position, &snapshot).is_ge()
17856 || context.end.cmp(&position, &snapshot).is_lt()
17857 {
17858 continue;
17859 }
17860 let snapshot = self.buffer.read(cx).snapshot(cx);
17861 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17862
17863 handled = true;
17864 self.clear_row_highlights::<ActiveDebugLine>();
17865
17866 self.go_to_line::<ActiveDebugLine>(
17867 multibuffer_anchor,
17868 Some(cx.theme().colors().editor_debugger_active_line_background),
17869 window,
17870 cx,
17871 );
17872
17873 cx.notify();
17874 }
17875
17876 handled.then_some(())
17877 })
17878 .is_some()
17879 }
17880
17881 pub fn copy_file_name_without_extension(
17882 &mut self,
17883 _: &CopyFileNameWithoutExtension,
17884 _: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 if let Some(file) = self.target_file(cx) {
17888 if let Some(file_stem) = file.path().file_stem() {
17889 if let Some(name) = file_stem.to_str() {
17890 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17891 }
17892 }
17893 }
17894 }
17895
17896 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17897 if let Some(file) = self.target_file(cx) {
17898 if let Some(file_name) = file.path().file_name() {
17899 if let Some(name) = file_name.to_str() {
17900 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17901 }
17902 }
17903 }
17904 }
17905
17906 pub fn toggle_git_blame(
17907 &mut self,
17908 _: &::git::Blame,
17909 window: &mut Window,
17910 cx: &mut Context<Self>,
17911 ) {
17912 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17913
17914 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17915 self.start_git_blame(true, window, cx);
17916 }
17917
17918 cx.notify();
17919 }
17920
17921 pub fn toggle_git_blame_inline(
17922 &mut self,
17923 _: &ToggleGitBlameInline,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 ) {
17927 self.toggle_git_blame_inline_internal(true, window, cx);
17928 cx.notify();
17929 }
17930
17931 pub fn open_git_blame_commit(
17932 &mut self,
17933 _: &OpenGitBlameCommit,
17934 window: &mut Window,
17935 cx: &mut Context<Self>,
17936 ) {
17937 self.open_git_blame_commit_internal(window, cx);
17938 }
17939
17940 fn open_git_blame_commit_internal(
17941 &mut self,
17942 window: &mut Window,
17943 cx: &mut Context<Self>,
17944 ) -> Option<()> {
17945 let blame = self.blame.as_ref()?;
17946 let snapshot = self.snapshot(window, cx);
17947 let cursor = self.selections.newest::<Point>(cx).head();
17948 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17949 let blame_entry = blame
17950 .update(cx, |blame, cx| {
17951 blame
17952 .blame_for_rows(
17953 &[RowInfo {
17954 buffer_id: Some(buffer.remote_id()),
17955 buffer_row: Some(point.row),
17956 ..Default::default()
17957 }],
17958 cx,
17959 )
17960 .next()
17961 })
17962 .flatten()?;
17963 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17964 let repo = blame.read(cx).repository(cx)?;
17965 let workspace = self.workspace()?.downgrade();
17966 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17967 None
17968 }
17969
17970 pub fn git_blame_inline_enabled(&self) -> bool {
17971 self.git_blame_inline_enabled
17972 }
17973
17974 pub fn toggle_selection_menu(
17975 &mut self,
17976 _: &ToggleSelectionMenu,
17977 _: &mut Window,
17978 cx: &mut Context<Self>,
17979 ) {
17980 self.show_selection_menu = self
17981 .show_selection_menu
17982 .map(|show_selections_menu| !show_selections_menu)
17983 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17984
17985 cx.notify();
17986 }
17987
17988 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17989 self.show_selection_menu
17990 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17991 }
17992
17993 fn start_git_blame(
17994 &mut self,
17995 user_triggered: bool,
17996 window: &mut Window,
17997 cx: &mut Context<Self>,
17998 ) {
17999 if let Some(project) = self.project.as_ref() {
18000 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18001 return;
18002 };
18003
18004 if buffer.read(cx).file().is_none() {
18005 return;
18006 }
18007
18008 let focused = self.focus_handle(cx).contains_focused(window, cx);
18009
18010 let project = project.clone();
18011 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18012 self.blame_subscription =
18013 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18014 self.blame = Some(blame);
18015 }
18016 }
18017
18018 fn toggle_git_blame_inline_internal(
18019 &mut self,
18020 user_triggered: bool,
18021 window: &mut Window,
18022 cx: &mut Context<Self>,
18023 ) {
18024 if self.git_blame_inline_enabled {
18025 self.git_blame_inline_enabled = false;
18026 self.show_git_blame_inline = false;
18027 self.show_git_blame_inline_delay_task.take();
18028 } else {
18029 self.git_blame_inline_enabled = true;
18030 self.start_git_blame_inline(user_triggered, window, cx);
18031 }
18032
18033 cx.notify();
18034 }
18035
18036 fn start_git_blame_inline(
18037 &mut self,
18038 user_triggered: bool,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 self.start_git_blame(user_triggered, window, cx);
18043
18044 if ProjectSettings::get_global(cx)
18045 .git
18046 .inline_blame_delay()
18047 .is_some()
18048 {
18049 self.start_inline_blame_timer(window, cx);
18050 } else {
18051 self.show_git_blame_inline = true
18052 }
18053 }
18054
18055 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18056 self.blame.as_ref()
18057 }
18058
18059 pub fn show_git_blame_gutter(&self) -> bool {
18060 self.show_git_blame_gutter
18061 }
18062
18063 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18064 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18065 }
18066
18067 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18068 self.show_git_blame_inline
18069 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18070 && !self.newest_selection_head_on_empty_line(cx)
18071 && self.has_blame_entries(cx)
18072 }
18073
18074 fn has_blame_entries(&self, cx: &App) -> bool {
18075 self.blame()
18076 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18077 }
18078
18079 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18080 let cursor_anchor = self.selections.newest_anchor().head();
18081
18082 let snapshot = self.buffer.read(cx).snapshot(cx);
18083 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18084
18085 snapshot.line_len(buffer_row) == 0
18086 }
18087
18088 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18089 let buffer_and_selection = maybe!({
18090 let selection = self.selections.newest::<Point>(cx);
18091 let selection_range = selection.range();
18092
18093 let multi_buffer = self.buffer().read(cx);
18094 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18095 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18096
18097 let (buffer, range, _) = if selection.reversed {
18098 buffer_ranges.first()
18099 } else {
18100 buffer_ranges.last()
18101 }?;
18102
18103 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18104 ..text::ToPoint::to_point(&range.end, &buffer).row;
18105 Some((
18106 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18107 selection,
18108 ))
18109 });
18110
18111 let Some((buffer, selection)) = buffer_and_selection else {
18112 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18113 };
18114
18115 let Some(project) = self.project.as_ref() else {
18116 return Task::ready(Err(anyhow!("editor does not have project")));
18117 };
18118
18119 project.update(cx, |project, cx| {
18120 project.get_permalink_to_line(&buffer, selection, cx)
18121 })
18122 }
18123
18124 pub fn copy_permalink_to_line(
18125 &mut self,
18126 _: &CopyPermalinkToLine,
18127 window: &mut Window,
18128 cx: &mut Context<Self>,
18129 ) {
18130 let permalink_task = self.get_permalink_to_line(cx);
18131 let workspace = self.workspace();
18132
18133 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18134 Ok(permalink) => {
18135 cx.update(|_, cx| {
18136 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18137 })
18138 .ok();
18139 }
18140 Err(err) => {
18141 let message = format!("Failed to copy permalink: {err}");
18142
18143 anyhow::Result::<()>::Err(err).log_err();
18144
18145 if let Some(workspace) = workspace {
18146 workspace
18147 .update_in(cx, |workspace, _, cx| {
18148 struct CopyPermalinkToLine;
18149
18150 workspace.show_toast(
18151 Toast::new(
18152 NotificationId::unique::<CopyPermalinkToLine>(),
18153 message,
18154 ),
18155 cx,
18156 )
18157 })
18158 .ok();
18159 }
18160 }
18161 })
18162 .detach();
18163 }
18164
18165 pub fn copy_file_location(
18166 &mut self,
18167 _: &CopyFileLocation,
18168 _: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) {
18171 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18172 if let Some(file) = self.target_file(cx) {
18173 if let Some(path) = file.path().to_str() {
18174 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18175 }
18176 }
18177 }
18178
18179 pub fn open_permalink_to_line(
18180 &mut self,
18181 _: &OpenPermalinkToLine,
18182 window: &mut Window,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let permalink_task = self.get_permalink_to_line(cx);
18186 let workspace = self.workspace();
18187
18188 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18189 Ok(permalink) => {
18190 cx.update(|_, cx| {
18191 cx.open_url(permalink.as_ref());
18192 })
18193 .ok();
18194 }
18195 Err(err) => {
18196 let message = format!("Failed to open permalink: {err}");
18197
18198 anyhow::Result::<()>::Err(err).log_err();
18199
18200 if let Some(workspace) = workspace {
18201 workspace
18202 .update(cx, |workspace, cx| {
18203 struct OpenPermalinkToLine;
18204
18205 workspace.show_toast(
18206 Toast::new(
18207 NotificationId::unique::<OpenPermalinkToLine>(),
18208 message,
18209 ),
18210 cx,
18211 )
18212 })
18213 .ok();
18214 }
18215 }
18216 })
18217 .detach();
18218 }
18219
18220 pub fn insert_uuid_v4(
18221 &mut self,
18222 _: &InsertUuidV4,
18223 window: &mut Window,
18224 cx: &mut Context<Self>,
18225 ) {
18226 self.insert_uuid(UuidVersion::V4, window, cx);
18227 }
18228
18229 pub fn insert_uuid_v7(
18230 &mut self,
18231 _: &InsertUuidV7,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) {
18235 self.insert_uuid(UuidVersion::V7, window, cx);
18236 }
18237
18238 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18240 self.transact(window, cx, |this, window, cx| {
18241 let edits = this
18242 .selections
18243 .all::<Point>(cx)
18244 .into_iter()
18245 .map(|selection| {
18246 let uuid = match version {
18247 UuidVersion::V4 => uuid::Uuid::new_v4(),
18248 UuidVersion::V7 => uuid::Uuid::now_v7(),
18249 };
18250
18251 (selection.range(), uuid.to_string())
18252 });
18253 this.edit(edits, cx);
18254 this.refresh_inline_completion(true, false, window, cx);
18255 });
18256 }
18257
18258 pub fn open_selections_in_multibuffer(
18259 &mut self,
18260 _: &OpenSelectionsInMultibuffer,
18261 window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) {
18264 let multibuffer = self.buffer.read(cx);
18265
18266 let Some(buffer) = multibuffer.as_singleton() else {
18267 return;
18268 };
18269
18270 let Some(workspace) = self.workspace() else {
18271 return;
18272 };
18273
18274 let locations = self
18275 .selections
18276 .disjoint_anchors()
18277 .iter()
18278 .map(|range| Location {
18279 buffer: buffer.clone(),
18280 range: range.start.text_anchor..range.end.text_anchor,
18281 })
18282 .collect::<Vec<_>>();
18283
18284 let title = multibuffer.title(cx).to_string();
18285
18286 cx.spawn_in(window, async move |_, cx| {
18287 workspace.update_in(cx, |workspace, window, cx| {
18288 Self::open_locations_in_multibuffer(
18289 workspace,
18290 locations,
18291 format!("Selections for '{title}'"),
18292 false,
18293 MultibufferSelectionMode::All,
18294 window,
18295 cx,
18296 );
18297 })
18298 })
18299 .detach();
18300 }
18301
18302 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18303 /// last highlight added will be used.
18304 ///
18305 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18306 pub fn highlight_rows<T: 'static>(
18307 &mut self,
18308 range: Range<Anchor>,
18309 color: Hsla,
18310 options: RowHighlightOptions,
18311 cx: &mut Context<Self>,
18312 ) {
18313 let snapshot = self.buffer().read(cx).snapshot(cx);
18314 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18315 let ix = row_highlights.binary_search_by(|highlight| {
18316 Ordering::Equal
18317 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18318 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18319 });
18320
18321 if let Err(mut ix) = ix {
18322 let index = post_inc(&mut self.highlight_order);
18323
18324 // If this range intersects with the preceding highlight, then merge it with
18325 // the preceding highlight. Otherwise insert a new highlight.
18326 let mut merged = false;
18327 if ix > 0 {
18328 let prev_highlight = &mut row_highlights[ix - 1];
18329 if prev_highlight
18330 .range
18331 .end
18332 .cmp(&range.start, &snapshot)
18333 .is_ge()
18334 {
18335 ix -= 1;
18336 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18337 prev_highlight.range.end = range.end;
18338 }
18339 merged = true;
18340 prev_highlight.index = index;
18341 prev_highlight.color = color;
18342 prev_highlight.options = options;
18343 }
18344 }
18345
18346 if !merged {
18347 row_highlights.insert(
18348 ix,
18349 RowHighlight {
18350 range: range.clone(),
18351 index,
18352 color,
18353 options,
18354 type_id: TypeId::of::<T>(),
18355 },
18356 );
18357 }
18358
18359 // If any of the following highlights intersect with this one, merge them.
18360 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18361 let highlight = &row_highlights[ix];
18362 if next_highlight
18363 .range
18364 .start
18365 .cmp(&highlight.range.end, &snapshot)
18366 .is_le()
18367 {
18368 if next_highlight
18369 .range
18370 .end
18371 .cmp(&highlight.range.end, &snapshot)
18372 .is_gt()
18373 {
18374 row_highlights[ix].range.end = next_highlight.range.end;
18375 }
18376 row_highlights.remove(ix + 1);
18377 } else {
18378 break;
18379 }
18380 }
18381 }
18382 }
18383
18384 /// Remove any highlighted row ranges of the given type that intersect the
18385 /// given ranges.
18386 pub fn remove_highlighted_rows<T: 'static>(
18387 &mut self,
18388 ranges_to_remove: Vec<Range<Anchor>>,
18389 cx: &mut Context<Self>,
18390 ) {
18391 let snapshot = self.buffer().read(cx).snapshot(cx);
18392 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18393 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18394 row_highlights.retain(|highlight| {
18395 while let Some(range_to_remove) = ranges_to_remove.peek() {
18396 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18397 Ordering::Less | Ordering::Equal => {
18398 ranges_to_remove.next();
18399 }
18400 Ordering::Greater => {
18401 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18402 Ordering::Less | Ordering::Equal => {
18403 return false;
18404 }
18405 Ordering::Greater => break,
18406 }
18407 }
18408 }
18409 }
18410
18411 true
18412 })
18413 }
18414
18415 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18416 pub fn clear_row_highlights<T: 'static>(&mut self) {
18417 self.highlighted_rows.remove(&TypeId::of::<T>());
18418 }
18419
18420 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18421 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18422 self.highlighted_rows
18423 .get(&TypeId::of::<T>())
18424 .map_or(&[] as &[_], |vec| vec.as_slice())
18425 .iter()
18426 .map(|highlight| (highlight.range.clone(), highlight.color))
18427 }
18428
18429 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18430 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18431 /// Allows to ignore certain kinds of highlights.
18432 pub fn highlighted_display_rows(
18433 &self,
18434 window: &mut Window,
18435 cx: &mut App,
18436 ) -> BTreeMap<DisplayRow, LineHighlight> {
18437 let snapshot = self.snapshot(window, cx);
18438 let mut used_highlight_orders = HashMap::default();
18439 self.highlighted_rows
18440 .iter()
18441 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18442 .fold(
18443 BTreeMap::<DisplayRow, LineHighlight>::new(),
18444 |mut unique_rows, highlight| {
18445 let start = highlight.range.start.to_display_point(&snapshot);
18446 let end = highlight.range.end.to_display_point(&snapshot);
18447 let start_row = start.row().0;
18448 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18449 && end.column() == 0
18450 {
18451 end.row().0.saturating_sub(1)
18452 } else {
18453 end.row().0
18454 };
18455 for row in start_row..=end_row {
18456 let used_index =
18457 used_highlight_orders.entry(row).or_insert(highlight.index);
18458 if highlight.index >= *used_index {
18459 *used_index = highlight.index;
18460 unique_rows.insert(
18461 DisplayRow(row),
18462 LineHighlight {
18463 include_gutter: highlight.options.include_gutter,
18464 border: None,
18465 background: highlight.color.into(),
18466 type_id: Some(highlight.type_id),
18467 },
18468 );
18469 }
18470 }
18471 unique_rows
18472 },
18473 )
18474 }
18475
18476 pub fn highlighted_display_row_for_autoscroll(
18477 &self,
18478 snapshot: &DisplaySnapshot,
18479 ) -> Option<DisplayRow> {
18480 self.highlighted_rows
18481 .values()
18482 .flat_map(|highlighted_rows| highlighted_rows.iter())
18483 .filter_map(|highlight| {
18484 if highlight.options.autoscroll {
18485 Some(highlight.range.start.to_display_point(snapshot).row())
18486 } else {
18487 None
18488 }
18489 })
18490 .min()
18491 }
18492
18493 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18494 self.highlight_background::<SearchWithinRange>(
18495 ranges,
18496 |colors| colors.editor_document_highlight_read_background,
18497 cx,
18498 )
18499 }
18500
18501 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18502 self.breadcrumb_header = Some(new_header);
18503 }
18504
18505 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18506 self.clear_background_highlights::<SearchWithinRange>(cx);
18507 }
18508
18509 pub fn highlight_background<T: 'static>(
18510 &mut self,
18511 ranges: &[Range<Anchor>],
18512 color_fetcher: fn(&ThemeColors) -> Hsla,
18513 cx: &mut Context<Self>,
18514 ) {
18515 self.background_highlights
18516 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18517 self.scrollbar_marker_state.dirty = true;
18518 cx.notify();
18519 }
18520
18521 pub fn clear_background_highlights<T: 'static>(
18522 &mut self,
18523 cx: &mut Context<Self>,
18524 ) -> Option<BackgroundHighlight> {
18525 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18526 if !text_highlights.1.is_empty() {
18527 self.scrollbar_marker_state.dirty = true;
18528 cx.notify();
18529 }
18530 Some(text_highlights)
18531 }
18532
18533 pub fn highlight_gutter<T: 'static>(
18534 &mut self,
18535 ranges: impl Into<Vec<Range<Anchor>>>,
18536 color_fetcher: fn(&App) -> Hsla,
18537 cx: &mut Context<Self>,
18538 ) {
18539 self.gutter_highlights
18540 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18541 cx.notify();
18542 }
18543
18544 pub fn clear_gutter_highlights<T: 'static>(
18545 &mut self,
18546 cx: &mut Context<Self>,
18547 ) -> Option<GutterHighlight> {
18548 cx.notify();
18549 self.gutter_highlights.remove(&TypeId::of::<T>())
18550 }
18551
18552 pub fn insert_gutter_highlight<T: 'static>(
18553 &mut self,
18554 range: Range<Anchor>,
18555 color_fetcher: fn(&App) -> Hsla,
18556 cx: &mut Context<Self>,
18557 ) {
18558 let snapshot = self.buffer().read(cx).snapshot(cx);
18559 let mut highlights = self
18560 .gutter_highlights
18561 .remove(&TypeId::of::<T>())
18562 .map(|(_, highlights)| highlights)
18563 .unwrap_or_default();
18564 let ix = highlights.binary_search_by(|highlight| {
18565 Ordering::Equal
18566 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18567 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18568 });
18569 if let Err(ix) = ix {
18570 highlights.insert(ix, range);
18571 }
18572 self.gutter_highlights
18573 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18574 }
18575
18576 pub fn remove_gutter_highlights<T: 'static>(
18577 &mut self,
18578 ranges_to_remove: Vec<Range<Anchor>>,
18579 cx: &mut Context<Self>,
18580 ) {
18581 let snapshot = self.buffer().read(cx).snapshot(cx);
18582 let Some((color_fetcher, mut gutter_highlights)) =
18583 self.gutter_highlights.remove(&TypeId::of::<T>())
18584 else {
18585 return;
18586 };
18587 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18588 gutter_highlights.retain(|highlight| {
18589 while let Some(range_to_remove) = ranges_to_remove.peek() {
18590 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18591 Ordering::Less | Ordering::Equal => {
18592 ranges_to_remove.next();
18593 }
18594 Ordering::Greater => {
18595 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18596 Ordering::Less | Ordering::Equal => {
18597 return false;
18598 }
18599 Ordering::Greater => break,
18600 }
18601 }
18602 }
18603 }
18604
18605 true
18606 });
18607 self.gutter_highlights
18608 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18609 }
18610
18611 #[cfg(feature = "test-support")]
18612 pub fn all_text_background_highlights(
18613 &self,
18614 window: &mut Window,
18615 cx: &mut Context<Self>,
18616 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18617 let snapshot = self.snapshot(window, cx);
18618 let buffer = &snapshot.buffer_snapshot;
18619 let start = buffer.anchor_before(0);
18620 let end = buffer.anchor_after(buffer.len());
18621 let theme = cx.theme().colors();
18622 self.background_highlights_in_range(start..end, &snapshot, theme)
18623 }
18624
18625 #[cfg(feature = "test-support")]
18626 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18627 let snapshot = self.buffer().read(cx).snapshot(cx);
18628
18629 let highlights = self
18630 .background_highlights
18631 .get(&TypeId::of::<items::BufferSearchHighlights>());
18632
18633 if let Some((_color, ranges)) = highlights {
18634 ranges
18635 .iter()
18636 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18637 .collect_vec()
18638 } else {
18639 vec![]
18640 }
18641 }
18642
18643 fn document_highlights_for_position<'a>(
18644 &'a self,
18645 position: Anchor,
18646 buffer: &'a MultiBufferSnapshot,
18647 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18648 let read_highlights = self
18649 .background_highlights
18650 .get(&TypeId::of::<DocumentHighlightRead>())
18651 .map(|h| &h.1);
18652 let write_highlights = self
18653 .background_highlights
18654 .get(&TypeId::of::<DocumentHighlightWrite>())
18655 .map(|h| &h.1);
18656 let left_position = position.bias_left(buffer);
18657 let right_position = position.bias_right(buffer);
18658 read_highlights
18659 .into_iter()
18660 .chain(write_highlights)
18661 .flat_map(move |ranges| {
18662 let start_ix = match ranges.binary_search_by(|probe| {
18663 let cmp = probe.end.cmp(&left_position, buffer);
18664 if cmp.is_ge() {
18665 Ordering::Greater
18666 } else {
18667 Ordering::Less
18668 }
18669 }) {
18670 Ok(i) | Err(i) => i,
18671 };
18672
18673 ranges[start_ix..]
18674 .iter()
18675 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18676 })
18677 }
18678
18679 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18680 self.background_highlights
18681 .get(&TypeId::of::<T>())
18682 .map_or(false, |(_, highlights)| !highlights.is_empty())
18683 }
18684
18685 pub fn background_highlights_in_range(
18686 &self,
18687 search_range: Range<Anchor>,
18688 display_snapshot: &DisplaySnapshot,
18689 theme: &ThemeColors,
18690 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18691 let mut results = Vec::new();
18692 for (color_fetcher, ranges) in self.background_highlights.values() {
18693 let color = color_fetcher(theme);
18694 let start_ix = match ranges.binary_search_by(|probe| {
18695 let cmp = probe
18696 .end
18697 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18698 if cmp.is_gt() {
18699 Ordering::Greater
18700 } else {
18701 Ordering::Less
18702 }
18703 }) {
18704 Ok(i) | Err(i) => i,
18705 };
18706 for range in &ranges[start_ix..] {
18707 if range
18708 .start
18709 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18710 .is_ge()
18711 {
18712 break;
18713 }
18714
18715 let start = range.start.to_display_point(display_snapshot);
18716 let end = range.end.to_display_point(display_snapshot);
18717 results.push((start..end, color))
18718 }
18719 }
18720 results
18721 }
18722
18723 pub fn background_highlight_row_ranges<T: 'static>(
18724 &self,
18725 search_range: Range<Anchor>,
18726 display_snapshot: &DisplaySnapshot,
18727 count: usize,
18728 ) -> Vec<RangeInclusive<DisplayPoint>> {
18729 let mut results = Vec::new();
18730 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18731 return vec![];
18732 };
18733
18734 let start_ix = match ranges.binary_search_by(|probe| {
18735 let cmp = probe
18736 .end
18737 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18738 if cmp.is_gt() {
18739 Ordering::Greater
18740 } else {
18741 Ordering::Less
18742 }
18743 }) {
18744 Ok(i) | Err(i) => i,
18745 };
18746 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18747 if let (Some(start_display), Some(end_display)) = (start, end) {
18748 results.push(
18749 start_display.to_display_point(display_snapshot)
18750 ..=end_display.to_display_point(display_snapshot),
18751 );
18752 }
18753 };
18754 let mut start_row: Option<Point> = None;
18755 let mut end_row: Option<Point> = None;
18756 if ranges.len() > count {
18757 return Vec::new();
18758 }
18759 for range in &ranges[start_ix..] {
18760 if range
18761 .start
18762 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18763 .is_ge()
18764 {
18765 break;
18766 }
18767 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18768 if let Some(current_row) = &end_row {
18769 if end.row == current_row.row {
18770 continue;
18771 }
18772 }
18773 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18774 if start_row.is_none() {
18775 assert_eq!(end_row, None);
18776 start_row = Some(start);
18777 end_row = Some(end);
18778 continue;
18779 }
18780 if let Some(current_end) = end_row.as_mut() {
18781 if start.row > current_end.row + 1 {
18782 push_region(start_row, end_row);
18783 start_row = Some(start);
18784 end_row = Some(end);
18785 } else {
18786 // Merge two hunks.
18787 *current_end = end;
18788 }
18789 } else {
18790 unreachable!();
18791 }
18792 }
18793 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18794 push_region(start_row, end_row);
18795 results
18796 }
18797
18798 pub fn gutter_highlights_in_range(
18799 &self,
18800 search_range: Range<Anchor>,
18801 display_snapshot: &DisplaySnapshot,
18802 cx: &App,
18803 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18804 let mut results = Vec::new();
18805 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18806 let color = color_fetcher(cx);
18807 let start_ix = match ranges.binary_search_by(|probe| {
18808 let cmp = probe
18809 .end
18810 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18811 if cmp.is_gt() {
18812 Ordering::Greater
18813 } else {
18814 Ordering::Less
18815 }
18816 }) {
18817 Ok(i) | Err(i) => i,
18818 };
18819 for range in &ranges[start_ix..] {
18820 if range
18821 .start
18822 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18823 .is_ge()
18824 {
18825 break;
18826 }
18827
18828 let start = range.start.to_display_point(display_snapshot);
18829 let end = range.end.to_display_point(display_snapshot);
18830 results.push((start..end, color))
18831 }
18832 }
18833 results
18834 }
18835
18836 /// Get the text ranges corresponding to the redaction query
18837 pub fn redacted_ranges(
18838 &self,
18839 search_range: Range<Anchor>,
18840 display_snapshot: &DisplaySnapshot,
18841 cx: &App,
18842 ) -> Vec<Range<DisplayPoint>> {
18843 display_snapshot
18844 .buffer_snapshot
18845 .redacted_ranges(search_range, |file| {
18846 if let Some(file) = file {
18847 file.is_private()
18848 && EditorSettings::get(
18849 Some(SettingsLocation {
18850 worktree_id: file.worktree_id(cx),
18851 path: file.path().as_ref(),
18852 }),
18853 cx,
18854 )
18855 .redact_private_values
18856 } else {
18857 false
18858 }
18859 })
18860 .map(|range| {
18861 range.start.to_display_point(display_snapshot)
18862 ..range.end.to_display_point(display_snapshot)
18863 })
18864 .collect()
18865 }
18866
18867 pub fn highlight_text<T: 'static>(
18868 &mut self,
18869 ranges: Vec<Range<Anchor>>,
18870 style: HighlightStyle,
18871 cx: &mut Context<Self>,
18872 ) {
18873 self.display_map.update(cx, |map, _| {
18874 map.highlight_text(TypeId::of::<T>(), ranges, style)
18875 });
18876 cx.notify();
18877 }
18878
18879 pub(crate) fn highlight_inlays<T: 'static>(
18880 &mut self,
18881 highlights: Vec<InlayHighlight>,
18882 style: HighlightStyle,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.display_map.update(cx, |map, _| {
18886 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18887 });
18888 cx.notify();
18889 }
18890
18891 pub fn text_highlights<'a, T: 'static>(
18892 &'a self,
18893 cx: &'a App,
18894 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18895 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18896 }
18897
18898 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18899 let cleared = self
18900 .display_map
18901 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18902 if cleared {
18903 cx.notify();
18904 }
18905 }
18906
18907 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18908 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18909 && self.focus_handle.is_focused(window)
18910 }
18911
18912 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18913 self.show_cursor_when_unfocused = is_enabled;
18914 cx.notify();
18915 }
18916
18917 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18918 cx.notify();
18919 }
18920
18921 fn on_debug_session_event(
18922 &mut self,
18923 _session: Entity<Session>,
18924 event: &SessionEvent,
18925 cx: &mut Context<Self>,
18926 ) {
18927 match event {
18928 SessionEvent::InvalidateInlineValue => {
18929 self.refresh_inline_values(cx);
18930 }
18931 _ => {}
18932 }
18933 }
18934
18935 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18936 let Some(project) = self.project.clone() else {
18937 return;
18938 };
18939
18940 if !self.inline_value_cache.enabled {
18941 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18942 self.splice_inlays(&inlays, Vec::new(), cx);
18943 return;
18944 }
18945
18946 let current_execution_position = self
18947 .highlighted_rows
18948 .get(&TypeId::of::<ActiveDebugLine>())
18949 .and_then(|lines| lines.last().map(|line| line.range.start));
18950
18951 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18952 let inline_values = editor
18953 .update(cx, |editor, cx| {
18954 let Some(current_execution_position) = current_execution_position else {
18955 return Some(Task::ready(Ok(Vec::new())));
18956 };
18957
18958 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18959 let snapshot = buffer.snapshot(cx);
18960
18961 let excerpt = snapshot.excerpt_containing(
18962 current_execution_position..current_execution_position,
18963 )?;
18964
18965 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18966 })?;
18967
18968 let range =
18969 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18970
18971 project.inline_values(buffer, range, cx)
18972 })
18973 .ok()
18974 .flatten()?
18975 .await
18976 .context("refreshing debugger inlays")
18977 .log_err()?;
18978
18979 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18980
18981 for (buffer_id, inline_value) in inline_values
18982 .into_iter()
18983 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18984 {
18985 buffer_inline_values
18986 .entry(buffer_id)
18987 .or_default()
18988 .push(inline_value);
18989 }
18990
18991 editor
18992 .update(cx, |editor, cx| {
18993 let snapshot = editor.buffer.read(cx).snapshot(cx);
18994 let mut new_inlays = Vec::default();
18995
18996 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18997 let buffer_id = buffer_snapshot.remote_id();
18998 buffer_inline_values
18999 .get(&buffer_id)
19000 .into_iter()
19001 .flatten()
19002 .for_each(|hint| {
19003 let inlay = Inlay::debugger_hint(
19004 post_inc(&mut editor.next_inlay_id),
19005 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19006 hint.text(),
19007 );
19008
19009 new_inlays.push(inlay);
19010 });
19011 }
19012
19013 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19014 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19015
19016 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19017 })
19018 .ok()?;
19019 Some(())
19020 });
19021 }
19022
19023 fn on_buffer_event(
19024 &mut self,
19025 multibuffer: &Entity<MultiBuffer>,
19026 event: &multi_buffer::Event,
19027 window: &mut Window,
19028 cx: &mut Context<Self>,
19029 ) {
19030 match event {
19031 multi_buffer::Event::Edited {
19032 singleton_buffer_edited,
19033 edited_buffer,
19034 } => {
19035 self.scrollbar_marker_state.dirty = true;
19036 self.active_indent_guides_state.dirty = true;
19037 self.refresh_active_diagnostics(cx);
19038 self.refresh_code_actions(window, cx);
19039 self.refresh_selected_text_highlights(true, window, cx);
19040 refresh_matching_bracket_highlights(self, window, cx);
19041 if self.has_active_inline_completion() {
19042 self.update_visible_inline_completion(window, cx);
19043 }
19044 if let Some(project) = self.project.as_ref() {
19045 if let Some(edited_buffer) = edited_buffer {
19046 project.update(cx, |project, cx| {
19047 self.registered_buffers
19048 .entry(edited_buffer.read(cx).remote_id())
19049 .or_insert_with(|| {
19050 project
19051 .register_buffer_with_language_servers(&edited_buffer, cx)
19052 });
19053 });
19054 if edited_buffer.read(cx).file().is_some() {
19055 self.pull_diagnostics(
19056 Some(edited_buffer.read(cx).remote_id()),
19057 window,
19058 cx,
19059 );
19060 }
19061 }
19062 }
19063 cx.emit(EditorEvent::BufferEdited);
19064 cx.emit(SearchEvent::MatchesInvalidated);
19065 if *singleton_buffer_edited {
19066 if let Some(buffer) = edited_buffer {
19067 if buffer.read(cx).file().is_none() {
19068 cx.emit(EditorEvent::TitleChanged);
19069 }
19070 }
19071 if let Some(project) = &self.project {
19072 #[allow(clippy::mutable_key_type)]
19073 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19074 multibuffer
19075 .all_buffers()
19076 .into_iter()
19077 .filter_map(|buffer| {
19078 buffer.update(cx, |buffer, cx| {
19079 let language = buffer.language()?;
19080 let should_discard = project.update(cx, |project, cx| {
19081 project.is_local()
19082 && !project.has_language_servers_for(buffer, cx)
19083 });
19084 should_discard.not().then_some(language.clone())
19085 })
19086 })
19087 .collect::<HashSet<_>>()
19088 });
19089 if !languages_affected.is_empty() {
19090 self.refresh_inlay_hints(
19091 InlayHintRefreshReason::BufferEdited(languages_affected),
19092 cx,
19093 );
19094 }
19095 }
19096 }
19097
19098 let Some(project) = &self.project else { return };
19099 let (telemetry, is_via_ssh) = {
19100 let project = project.read(cx);
19101 let telemetry = project.client().telemetry().clone();
19102 let is_via_ssh = project.is_via_ssh();
19103 (telemetry, is_via_ssh)
19104 };
19105 refresh_linked_ranges(self, window, cx);
19106 telemetry.log_edit_event("editor", is_via_ssh);
19107 }
19108 multi_buffer::Event::ExcerptsAdded {
19109 buffer,
19110 predecessor,
19111 excerpts,
19112 } => {
19113 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19114 let buffer_id = buffer.read(cx).remote_id();
19115 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19116 if let Some(project) = &self.project {
19117 update_uncommitted_diff_for_buffer(
19118 cx.entity(),
19119 project,
19120 [buffer.clone()],
19121 self.buffer.clone(),
19122 cx,
19123 )
19124 .detach();
19125 }
19126 }
19127 cx.emit(EditorEvent::ExcerptsAdded {
19128 buffer: buffer.clone(),
19129 predecessor: *predecessor,
19130 excerpts: excerpts.clone(),
19131 });
19132 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19133 }
19134 multi_buffer::Event::ExcerptsRemoved {
19135 ids,
19136 removed_buffer_ids,
19137 } => {
19138 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19139 let buffer = self.buffer.read(cx);
19140 self.registered_buffers
19141 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19142 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19143 cx.emit(EditorEvent::ExcerptsRemoved {
19144 ids: ids.clone(),
19145 removed_buffer_ids: removed_buffer_ids.clone(),
19146 })
19147 }
19148 multi_buffer::Event::ExcerptsEdited {
19149 excerpt_ids,
19150 buffer_ids,
19151 } => {
19152 self.display_map.update(cx, |map, cx| {
19153 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19154 });
19155 cx.emit(EditorEvent::ExcerptsEdited {
19156 ids: excerpt_ids.clone(),
19157 })
19158 }
19159 multi_buffer::Event::ExcerptsExpanded { ids } => {
19160 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19161 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19162 }
19163 multi_buffer::Event::Reparsed(buffer_id) => {
19164 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19165 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19166
19167 cx.emit(EditorEvent::Reparsed(*buffer_id));
19168 }
19169 multi_buffer::Event::DiffHunksToggled => {
19170 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19171 }
19172 multi_buffer::Event::LanguageChanged(buffer_id) => {
19173 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19174 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19175 cx.emit(EditorEvent::Reparsed(*buffer_id));
19176 cx.notify();
19177 }
19178 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19179 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19180 multi_buffer::Event::FileHandleChanged
19181 | multi_buffer::Event::Reloaded
19182 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19183 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19184 multi_buffer::Event::DiagnosticsUpdated => {
19185 self.update_diagnostics_state(window, cx);
19186 }
19187 _ => {}
19188 };
19189 }
19190
19191 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19192 self.refresh_active_diagnostics(cx);
19193 self.refresh_inline_diagnostics(true, window, cx);
19194 self.scrollbar_marker_state.dirty = true;
19195 cx.notify();
19196 }
19197
19198 pub fn start_temporary_diff_override(&mut self) {
19199 self.load_diff_task.take();
19200 self.temporary_diff_override = true;
19201 }
19202
19203 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19204 self.temporary_diff_override = false;
19205 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19206 self.buffer.update(cx, |buffer, cx| {
19207 buffer.set_all_diff_hunks_collapsed(cx);
19208 });
19209
19210 if let Some(project) = self.project.clone() {
19211 self.load_diff_task = Some(
19212 update_uncommitted_diff_for_buffer(
19213 cx.entity(),
19214 &project,
19215 self.buffer.read(cx).all_buffers(),
19216 self.buffer.clone(),
19217 cx,
19218 )
19219 .shared(),
19220 );
19221 }
19222 }
19223
19224 fn on_display_map_changed(
19225 &mut self,
19226 _: Entity<DisplayMap>,
19227 _: &mut Window,
19228 cx: &mut Context<Self>,
19229 ) {
19230 cx.notify();
19231 }
19232
19233 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19234 let new_severity = if self.diagnostics_enabled() {
19235 EditorSettings::get_global(cx)
19236 .diagnostics_max_severity
19237 .unwrap_or(DiagnosticSeverity::Hint)
19238 } else {
19239 DiagnosticSeverity::Off
19240 };
19241 self.set_max_diagnostics_severity(new_severity, cx);
19242 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19243 self.update_edit_prediction_settings(cx);
19244 self.refresh_inline_completion(true, false, window, cx);
19245 self.refresh_inlay_hints(
19246 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19247 self.selections.newest_anchor().head(),
19248 &self.buffer.read(cx).snapshot(cx),
19249 cx,
19250 )),
19251 cx,
19252 );
19253
19254 let old_cursor_shape = self.cursor_shape;
19255
19256 {
19257 let editor_settings = EditorSettings::get_global(cx);
19258 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19259 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19260 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19261 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19262 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19263 }
19264
19265 if old_cursor_shape != self.cursor_shape {
19266 cx.emit(EditorEvent::CursorShapeChanged);
19267 }
19268
19269 let project_settings = ProjectSettings::get_global(cx);
19270 self.serialize_dirty_buffers =
19271 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19272
19273 if self.mode.is_full() {
19274 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19275 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19276 if self.show_inline_diagnostics != show_inline_diagnostics {
19277 self.show_inline_diagnostics = show_inline_diagnostics;
19278 self.refresh_inline_diagnostics(false, window, cx);
19279 }
19280
19281 if self.git_blame_inline_enabled != inline_blame_enabled {
19282 self.toggle_git_blame_inline_internal(false, window, cx);
19283 }
19284
19285 let minimap_settings = EditorSettings::get_global(cx).minimap;
19286 if self.minimap_visibility != MinimapVisibility::Disabled {
19287 if self.minimap_visibility.settings_visibility()
19288 != minimap_settings.minimap_enabled()
19289 {
19290 self.set_minimap_visibility(
19291 MinimapVisibility::for_mode(self.mode(), cx),
19292 window,
19293 cx,
19294 );
19295 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19296 minimap_entity.update(cx, |minimap_editor, cx| {
19297 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19298 })
19299 }
19300 }
19301 }
19302
19303 cx.notify();
19304 }
19305
19306 pub fn set_searchable(&mut self, searchable: bool) {
19307 self.searchable = searchable;
19308 }
19309
19310 pub fn searchable(&self) -> bool {
19311 self.searchable
19312 }
19313
19314 fn open_proposed_changes_editor(
19315 &mut self,
19316 _: &OpenProposedChangesEditor,
19317 window: &mut Window,
19318 cx: &mut Context<Self>,
19319 ) {
19320 let Some(workspace) = self.workspace() else {
19321 cx.propagate();
19322 return;
19323 };
19324
19325 let selections = self.selections.all::<usize>(cx);
19326 let multi_buffer = self.buffer.read(cx);
19327 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19328 let mut new_selections_by_buffer = HashMap::default();
19329 for selection in selections {
19330 for (buffer, range, _) in
19331 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19332 {
19333 let mut range = range.to_point(buffer);
19334 range.start.column = 0;
19335 range.end.column = buffer.line_len(range.end.row);
19336 new_selections_by_buffer
19337 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19338 .or_insert(Vec::new())
19339 .push(range)
19340 }
19341 }
19342
19343 let proposed_changes_buffers = new_selections_by_buffer
19344 .into_iter()
19345 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19346 .collect::<Vec<_>>();
19347 let proposed_changes_editor = cx.new(|cx| {
19348 ProposedChangesEditor::new(
19349 "Proposed changes",
19350 proposed_changes_buffers,
19351 self.project.clone(),
19352 window,
19353 cx,
19354 )
19355 });
19356
19357 window.defer(cx, move |window, cx| {
19358 workspace.update(cx, |workspace, cx| {
19359 workspace.active_pane().update(cx, |pane, cx| {
19360 pane.add_item(
19361 Box::new(proposed_changes_editor),
19362 true,
19363 true,
19364 None,
19365 window,
19366 cx,
19367 );
19368 });
19369 });
19370 });
19371 }
19372
19373 pub fn open_excerpts_in_split(
19374 &mut self,
19375 _: &OpenExcerptsSplit,
19376 window: &mut Window,
19377 cx: &mut Context<Self>,
19378 ) {
19379 self.open_excerpts_common(None, true, window, cx)
19380 }
19381
19382 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19383 self.open_excerpts_common(None, false, window, cx)
19384 }
19385
19386 fn open_excerpts_common(
19387 &mut self,
19388 jump_data: Option<JumpData>,
19389 split: bool,
19390 window: &mut Window,
19391 cx: &mut Context<Self>,
19392 ) {
19393 let Some(workspace) = self.workspace() else {
19394 cx.propagate();
19395 return;
19396 };
19397
19398 if self.buffer.read(cx).is_singleton() {
19399 cx.propagate();
19400 return;
19401 }
19402
19403 let mut new_selections_by_buffer = HashMap::default();
19404 match &jump_data {
19405 Some(JumpData::MultiBufferPoint {
19406 excerpt_id,
19407 position,
19408 anchor,
19409 line_offset_from_top,
19410 }) => {
19411 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19412 if let Some(buffer) = multi_buffer_snapshot
19413 .buffer_id_for_excerpt(*excerpt_id)
19414 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19415 {
19416 let buffer_snapshot = buffer.read(cx).snapshot();
19417 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19418 language::ToPoint::to_point(anchor, &buffer_snapshot)
19419 } else {
19420 buffer_snapshot.clip_point(*position, Bias::Left)
19421 };
19422 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19423 new_selections_by_buffer.insert(
19424 buffer,
19425 (
19426 vec![jump_to_offset..jump_to_offset],
19427 Some(*line_offset_from_top),
19428 ),
19429 );
19430 }
19431 }
19432 Some(JumpData::MultiBufferRow {
19433 row,
19434 line_offset_from_top,
19435 }) => {
19436 let point = MultiBufferPoint::new(row.0, 0);
19437 if let Some((buffer, buffer_point, _)) =
19438 self.buffer.read(cx).point_to_buffer_point(point, cx)
19439 {
19440 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19441 new_selections_by_buffer
19442 .entry(buffer)
19443 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19444 .0
19445 .push(buffer_offset..buffer_offset)
19446 }
19447 }
19448 None => {
19449 let selections = self.selections.all::<usize>(cx);
19450 let multi_buffer = self.buffer.read(cx);
19451 for selection in selections {
19452 for (snapshot, range, _, anchor) in multi_buffer
19453 .snapshot(cx)
19454 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19455 {
19456 if let Some(anchor) = anchor {
19457 // selection is in a deleted hunk
19458 let Some(buffer_id) = anchor.buffer_id else {
19459 continue;
19460 };
19461 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19462 continue;
19463 };
19464 let offset = text::ToOffset::to_offset(
19465 &anchor.text_anchor,
19466 &buffer_handle.read(cx).snapshot(),
19467 );
19468 let range = offset..offset;
19469 new_selections_by_buffer
19470 .entry(buffer_handle)
19471 .or_insert((Vec::new(), None))
19472 .0
19473 .push(range)
19474 } else {
19475 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19476 else {
19477 continue;
19478 };
19479 new_selections_by_buffer
19480 .entry(buffer_handle)
19481 .or_insert((Vec::new(), None))
19482 .0
19483 .push(range)
19484 }
19485 }
19486 }
19487 }
19488 }
19489
19490 new_selections_by_buffer
19491 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19492
19493 if new_selections_by_buffer.is_empty() {
19494 return;
19495 }
19496
19497 // We defer the pane interaction because we ourselves are a workspace item
19498 // and activating a new item causes the pane to call a method on us reentrantly,
19499 // which panics if we're on the stack.
19500 window.defer(cx, move |window, cx| {
19501 workspace.update(cx, |workspace, cx| {
19502 let pane = if split {
19503 workspace.adjacent_pane(window, cx)
19504 } else {
19505 workspace.active_pane().clone()
19506 };
19507
19508 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19509 let editor = buffer
19510 .read(cx)
19511 .file()
19512 .is_none()
19513 .then(|| {
19514 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19515 // so `workspace.open_project_item` will never find them, always opening a new editor.
19516 // Instead, we try to activate the existing editor in the pane first.
19517 let (editor, pane_item_index) =
19518 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19519 let editor = item.downcast::<Editor>()?;
19520 let singleton_buffer =
19521 editor.read(cx).buffer().read(cx).as_singleton()?;
19522 if singleton_buffer == buffer {
19523 Some((editor, i))
19524 } else {
19525 None
19526 }
19527 })?;
19528 pane.update(cx, |pane, cx| {
19529 pane.activate_item(pane_item_index, true, true, window, cx)
19530 });
19531 Some(editor)
19532 })
19533 .flatten()
19534 .unwrap_or_else(|| {
19535 workspace.open_project_item::<Self>(
19536 pane.clone(),
19537 buffer,
19538 true,
19539 true,
19540 window,
19541 cx,
19542 )
19543 });
19544
19545 editor.update(cx, |editor, cx| {
19546 let autoscroll = match scroll_offset {
19547 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19548 None => Autoscroll::newest(),
19549 };
19550 let nav_history = editor.nav_history.take();
19551 editor.change_selections(Some(autoscroll), window, cx, |s| {
19552 s.select_ranges(ranges);
19553 });
19554 editor.nav_history = nav_history;
19555 });
19556 }
19557 })
19558 });
19559 }
19560
19561 // For now, don't allow opening excerpts in buffers that aren't backed by
19562 // regular project files.
19563 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19564 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19565 }
19566
19567 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19568 let snapshot = self.buffer.read(cx).read(cx);
19569 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19570 Some(
19571 ranges
19572 .iter()
19573 .map(move |range| {
19574 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19575 })
19576 .collect(),
19577 )
19578 }
19579
19580 fn selection_replacement_ranges(
19581 &self,
19582 range: Range<OffsetUtf16>,
19583 cx: &mut App,
19584 ) -> Vec<Range<OffsetUtf16>> {
19585 let selections = self.selections.all::<OffsetUtf16>(cx);
19586 let newest_selection = selections
19587 .iter()
19588 .max_by_key(|selection| selection.id)
19589 .unwrap();
19590 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19591 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19592 let snapshot = self.buffer.read(cx).read(cx);
19593 selections
19594 .into_iter()
19595 .map(|mut selection| {
19596 selection.start.0 =
19597 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19598 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19599 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19600 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19601 })
19602 .collect()
19603 }
19604
19605 fn report_editor_event(
19606 &self,
19607 event_type: &'static str,
19608 file_extension: Option<String>,
19609 cx: &App,
19610 ) {
19611 if cfg!(any(test, feature = "test-support")) {
19612 return;
19613 }
19614
19615 let Some(project) = &self.project else { return };
19616
19617 // If None, we are in a file without an extension
19618 let file = self
19619 .buffer
19620 .read(cx)
19621 .as_singleton()
19622 .and_then(|b| b.read(cx).file());
19623 let file_extension = file_extension.or(file
19624 .as_ref()
19625 .and_then(|file| Path::new(file.file_name(cx)).extension())
19626 .and_then(|e| e.to_str())
19627 .map(|a| a.to_string()));
19628
19629 let vim_mode = vim_enabled(cx);
19630
19631 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19632 let copilot_enabled = edit_predictions_provider
19633 == language::language_settings::EditPredictionProvider::Copilot;
19634 let copilot_enabled_for_language = self
19635 .buffer
19636 .read(cx)
19637 .language_settings(cx)
19638 .show_edit_predictions;
19639
19640 let project = project.read(cx);
19641 telemetry::event!(
19642 event_type,
19643 file_extension,
19644 vim_mode,
19645 copilot_enabled,
19646 copilot_enabled_for_language,
19647 edit_predictions_provider,
19648 is_via_ssh = project.is_via_ssh(),
19649 );
19650 }
19651
19652 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19653 /// with each line being an array of {text, highlight} objects.
19654 fn copy_highlight_json(
19655 &mut self,
19656 _: &CopyHighlightJson,
19657 window: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 #[derive(Serialize)]
19661 struct Chunk<'a> {
19662 text: String,
19663 highlight: Option<&'a str>,
19664 }
19665
19666 let snapshot = self.buffer.read(cx).snapshot(cx);
19667 let range = self
19668 .selected_text_range(false, window, cx)
19669 .and_then(|selection| {
19670 if selection.range.is_empty() {
19671 None
19672 } else {
19673 Some(selection.range)
19674 }
19675 })
19676 .unwrap_or_else(|| 0..snapshot.len());
19677
19678 let chunks = snapshot.chunks(range, true);
19679 let mut lines = Vec::new();
19680 let mut line: VecDeque<Chunk> = VecDeque::new();
19681
19682 let Some(style) = self.style.as_ref() else {
19683 return;
19684 };
19685
19686 for chunk in chunks {
19687 let highlight = chunk
19688 .syntax_highlight_id
19689 .and_then(|id| id.name(&style.syntax));
19690 let mut chunk_lines = chunk.text.split('\n').peekable();
19691 while let Some(text) = chunk_lines.next() {
19692 let mut merged_with_last_token = false;
19693 if let Some(last_token) = line.back_mut() {
19694 if last_token.highlight == highlight {
19695 last_token.text.push_str(text);
19696 merged_with_last_token = true;
19697 }
19698 }
19699
19700 if !merged_with_last_token {
19701 line.push_back(Chunk {
19702 text: text.into(),
19703 highlight,
19704 });
19705 }
19706
19707 if chunk_lines.peek().is_some() {
19708 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19709 line.pop_front();
19710 }
19711 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19712 line.pop_back();
19713 }
19714
19715 lines.push(mem::take(&mut line));
19716 }
19717 }
19718 }
19719
19720 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19721 return;
19722 };
19723 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19724 }
19725
19726 pub fn open_context_menu(
19727 &mut self,
19728 _: &OpenContextMenu,
19729 window: &mut Window,
19730 cx: &mut Context<Self>,
19731 ) {
19732 self.request_autoscroll(Autoscroll::newest(), cx);
19733 let position = self.selections.newest_display(cx).start;
19734 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19735 }
19736
19737 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19738 &self.inlay_hint_cache
19739 }
19740
19741 pub fn replay_insert_event(
19742 &mut self,
19743 text: &str,
19744 relative_utf16_range: Option<Range<isize>>,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 if !self.input_enabled {
19749 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19750 return;
19751 }
19752 if let Some(relative_utf16_range) = relative_utf16_range {
19753 let selections = self.selections.all::<OffsetUtf16>(cx);
19754 self.change_selections(None, window, cx, |s| {
19755 let new_ranges = selections.into_iter().map(|range| {
19756 let start = OffsetUtf16(
19757 range
19758 .head()
19759 .0
19760 .saturating_add_signed(relative_utf16_range.start),
19761 );
19762 let end = OffsetUtf16(
19763 range
19764 .head()
19765 .0
19766 .saturating_add_signed(relative_utf16_range.end),
19767 );
19768 start..end
19769 });
19770 s.select_ranges(new_ranges);
19771 });
19772 }
19773
19774 self.handle_input(text, window, cx);
19775 }
19776
19777 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19778 let Some(provider) = self.semantics_provider.as_ref() else {
19779 return false;
19780 };
19781
19782 let mut supports = false;
19783 self.buffer().update(cx, |this, cx| {
19784 this.for_each_buffer(|buffer| {
19785 supports |= provider.supports_inlay_hints(buffer, cx);
19786 });
19787 });
19788
19789 supports
19790 }
19791
19792 pub fn is_focused(&self, window: &Window) -> bool {
19793 self.focus_handle.is_focused(window)
19794 }
19795
19796 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19797 cx.emit(EditorEvent::Focused);
19798
19799 if let Some(descendant) = self
19800 .last_focused_descendant
19801 .take()
19802 .and_then(|descendant| descendant.upgrade())
19803 {
19804 window.focus(&descendant);
19805 } else {
19806 if let Some(blame) = self.blame.as_ref() {
19807 blame.update(cx, GitBlame::focus)
19808 }
19809
19810 self.blink_manager.update(cx, BlinkManager::enable);
19811 self.show_cursor_names(window, cx);
19812 self.buffer.update(cx, |buffer, cx| {
19813 buffer.finalize_last_transaction(cx);
19814 if self.leader_id.is_none() {
19815 buffer.set_active_selections(
19816 &self.selections.disjoint_anchors(),
19817 self.selections.line_mode,
19818 self.cursor_shape,
19819 cx,
19820 );
19821 }
19822 });
19823 }
19824 }
19825
19826 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19827 cx.emit(EditorEvent::FocusedIn)
19828 }
19829
19830 fn handle_focus_out(
19831 &mut self,
19832 event: FocusOutEvent,
19833 _window: &mut Window,
19834 cx: &mut Context<Self>,
19835 ) {
19836 if event.blurred != self.focus_handle {
19837 self.last_focused_descendant = Some(event.blurred);
19838 }
19839 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19840 }
19841
19842 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19843 self.blink_manager.update(cx, BlinkManager::disable);
19844 self.buffer
19845 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19846
19847 if let Some(blame) = self.blame.as_ref() {
19848 blame.update(cx, GitBlame::blur)
19849 }
19850 if !self.hover_state.focused(window, cx) {
19851 hide_hover(self, cx);
19852 }
19853 if !self
19854 .context_menu
19855 .borrow()
19856 .as_ref()
19857 .is_some_and(|context_menu| context_menu.focused(window, cx))
19858 {
19859 self.hide_context_menu(window, cx);
19860 }
19861 self.discard_inline_completion(false, cx);
19862 cx.emit(EditorEvent::Blurred);
19863 cx.notify();
19864 }
19865
19866 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19867 let mut pending: String = window
19868 .pending_input_keystrokes()
19869 .into_iter()
19870 .flatten()
19871 .filter_map(|keystroke| {
19872 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19873 keystroke.key_char.clone()
19874 } else {
19875 None
19876 }
19877 })
19878 .collect();
19879
19880 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19881 pending = "".to_string();
19882 }
19883
19884 let existing_pending = self
19885 .text_highlights::<PendingInput>(cx)
19886 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19887 if existing_pending.is_none() && pending.is_empty() {
19888 return;
19889 }
19890 let transaction =
19891 self.transact(window, cx, |this, window, cx| {
19892 let selections = this.selections.all::<usize>(cx);
19893 let edits = selections
19894 .iter()
19895 .map(|selection| (selection.end..selection.end, pending.clone()));
19896 this.edit(edits, cx);
19897 this.change_selections(None, window, cx, |s| {
19898 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19899 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19900 }));
19901 });
19902 if let Some(existing_ranges) = existing_pending {
19903 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19904 this.edit(edits, cx);
19905 }
19906 });
19907
19908 let snapshot = self.snapshot(window, cx);
19909 let ranges = self
19910 .selections
19911 .all::<usize>(cx)
19912 .into_iter()
19913 .map(|selection| {
19914 snapshot.buffer_snapshot.anchor_after(selection.end)
19915 ..snapshot
19916 .buffer_snapshot
19917 .anchor_before(selection.end + pending.len())
19918 })
19919 .collect();
19920
19921 if pending.is_empty() {
19922 self.clear_highlights::<PendingInput>(cx);
19923 } else {
19924 self.highlight_text::<PendingInput>(
19925 ranges,
19926 HighlightStyle {
19927 underline: Some(UnderlineStyle {
19928 thickness: px(1.),
19929 color: None,
19930 wavy: false,
19931 }),
19932 ..Default::default()
19933 },
19934 cx,
19935 );
19936 }
19937
19938 self.ime_transaction = self.ime_transaction.or(transaction);
19939 if let Some(transaction) = self.ime_transaction {
19940 self.buffer.update(cx, |buffer, cx| {
19941 buffer.group_until_transaction(transaction, cx);
19942 });
19943 }
19944
19945 if self.text_highlights::<PendingInput>(cx).is_none() {
19946 self.ime_transaction.take();
19947 }
19948 }
19949
19950 pub fn register_action_renderer(
19951 &mut self,
19952 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19953 ) -> Subscription {
19954 let id = self.next_editor_action_id.post_inc();
19955 self.editor_actions
19956 .borrow_mut()
19957 .insert(id, Box::new(listener));
19958
19959 let editor_actions = self.editor_actions.clone();
19960 Subscription::new(move || {
19961 editor_actions.borrow_mut().remove(&id);
19962 })
19963 }
19964
19965 pub fn register_action<A: Action>(
19966 &mut self,
19967 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19968 ) -> Subscription {
19969 let id = self.next_editor_action_id.post_inc();
19970 let listener = Arc::new(listener);
19971 self.editor_actions.borrow_mut().insert(
19972 id,
19973 Box::new(move |_, window, _| {
19974 let listener = listener.clone();
19975 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19976 let action = action.downcast_ref().unwrap();
19977 if phase == DispatchPhase::Bubble {
19978 listener(action, window, cx)
19979 }
19980 })
19981 }),
19982 );
19983
19984 let editor_actions = self.editor_actions.clone();
19985 Subscription::new(move || {
19986 editor_actions.borrow_mut().remove(&id);
19987 })
19988 }
19989
19990 pub fn file_header_size(&self) -> u32 {
19991 FILE_HEADER_HEIGHT
19992 }
19993
19994 pub fn restore(
19995 &mut self,
19996 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19997 window: &mut Window,
19998 cx: &mut Context<Self>,
19999 ) {
20000 let workspace = self.workspace();
20001 let project = self.project.as_ref();
20002 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20003 let mut tasks = Vec::new();
20004 for (buffer_id, changes) in revert_changes {
20005 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20006 buffer.update(cx, |buffer, cx| {
20007 buffer.edit(
20008 changes
20009 .into_iter()
20010 .map(|(range, text)| (range, text.to_string())),
20011 None,
20012 cx,
20013 );
20014 });
20015
20016 if let Some(project) =
20017 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20018 {
20019 project.update(cx, |project, cx| {
20020 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20021 })
20022 }
20023 }
20024 }
20025 tasks
20026 });
20027 cx.spawn_in(window, async move |_, cx| {
20028 for (buffer, task) in save_tasks {
20029 let result = task.await;
20030 if result.is_err() {
20031 let Some(path) = buffer
20032 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20033 .ok()
20034 else {
20035 continue;
20036 };
20037 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20038 let Some(task) = cx
20039 .update_window_entity(&workspace, |workspace, window, cx| {
20040 workspace
20041 .open_path_preview(path, None, false, false, false, window, cx)
20042 })
20043 .ok()
20044 else {
20045 continue;
20046 };
20047 task.await.log_err();
20048 }
20049 }
20050 }
20051 })
20052 .detach();
20053 self.change_selections(None, window, cx, |selections| selections.refresh());
20054 }
20055
20056 pub fn to_pixel_point(
20057 &self,
20058 source: multi_buffer::Anchor,
20059 editor_snapshot: &EditorSnapshot,
20060 window: &mut Window,
20061 ) -> Option<gpui::Point<Pixels>> {
20062 let source_point = source.to_display_point(editor_snapshot);
20063 self.display_to_pixel_point(source_point, editor_snapshot, window)
20064 }
20065
20066 pub fn display_to_pixel_point(
20067 &self,
20068 source: DisplayPoint,
20069 editor_snapshot: &EditorSnapshot,
20070 window: &mut Window,
20071 ) -> Option<gpui::Point<Pixels>> {
20072 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20073 let text_layout_details = self.text_layout_details(window);
20074 let scroll_top = text_layout_details
20075 .scroll_anchor
20076 .scroll_position(editor_snapshot)
20077 .y;
20078
20079 if source.row().as_f32() < scroll_top.floor() {
20080 return None;
20081 }
20082 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20083 let source_y = line_height * (source.row().as_f32() - scroll_top);
20084 Some(gpui::Point::new(source_x, source_y))
20085 }
20086
20087 pub fn has_visible_completions_menu(&self) -> bool {
20088 !self.edit_prediction_preview_is_active()
20089 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20090 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20091 })
20092 }
20093
20094 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20095 if self.mode.is_minimap() {
20096 return;
20097 }
20098 self.addons
20099 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20100 }
20101
20102 pub fn unregister_addon<T: Addon>(&mut self) {
20103 self.addons.remove(&std::any::TypeId::of::<T>());
20104 }
20105
20106 pub fn addon<T: Addon>(&self) -> Option<&T> {
20107 let type_id = std::any::TypeId::of::<T>();
20108 self.addons
20109 .get(&type_id)
20110 .and_then(|item| item.to_any().downcast_ref::<T>())
20111 }
20112
20113 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20114 let type_id = std::any::TypeId::of::<T>();
20115 self.addons
20116 .get_mut(&type_id)
20117 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20118 }
20119
20120 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20121 let text_layout_details = self.text_layout_details(window);
20122 let style = &text_layout_details.editor_style;
20123 let font_id = window.text_system().resolve_font(&style.text.font());
20124 let font_size = style.text.font_size.to_pixels(window.rem_size());
20125 let line_height = style.text.line_height_in_pixels(window.rem_size());
20126 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20127
20128 gpui::Size::new(em_width, line_height)
20129 }
20130
20131 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20132 self.load_diff_task.clone()
20133 }
20134
20135 fn read_metadata_from_db(
20136 &mut self,
20137 item_id: u64,
20138 workspace_id: WorkspaceId,
20139 window: &mut Window,
20140 cx: &mut Context<Editor>,
20141 ) {
20142 if self.is_singleton(cx)
20143 && !self.mode.is_minimap()
20144 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20145 {
20146 let buffer_snapshot = OnceCell::new();
20147
20148 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20149 if !folds.is_empty() {
20150 let snapshot =
20151 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20152 self.fold_ranges(
20153 folds
20154 .into_iter()
20155 .map(|(start, end)| {
20156 snapshot.clip_offset(start, Bias::Left)
20157 ..snapshot.clip_offset(end, Bias::Right)
20158 })
20159 .collect(),
20160 false,
20161 window,
20162 cx,
20163 );
20164 }
20165 }
20166
20167 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20168 if !selections.is_empty() {
20169 let snapshot =
20170 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20171 // skip adding the initial selection to selection history
20172 self.selection_history.mode = SelectionHistoryMode::Skipping;
20173 self.change_selections(None, window, cx, |s| {
20174 s.select_ranges(selections.into_iter().map(|(start, end)| {
20175 snapshot.clip_offset(start, Bias::Left)
20176 ..snapshot.clip_offset(end, Bias::Right)
20177 }));
20178 });
20179 self.selection_history.mode = SelectionHistoryMode::Normal;
20180 }
20181 };
20182 }
20183
20184 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20185 }
20186}
20187
20188fn vim_enabled(cx: &App) -> bool {
20189 cx.global::<SettingsStore>()
20190 .raw_user_settings()
20191 .get("vim_mode")
20192 == Some(&serde_json::Value::Bool(true))
20193}
20194
20195fn process_completion_for_edit(
20196 completion: &Completion,
20197 intent: CompletionIntent,
20198 buffer: &Entity<Buffer>,
20199 cursor_position: &text::Anchor,
20200 cx: &mut Context<Editor>,
20201) -> CompletionEdit {
20202 let buffer = buffer.read(cx);
20203 let buffer_snapshot = buffer.snapshot();
20204 let (snippet, new_text) = if completion.is_snippet() {
20205 // Workaround for typescript language server issues so that methods don't expand within
20206 // strings and functions with type expressions. The previous point is used because the query
20207 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20208 let mut snippet_source = completion.new_text.clone();
20209 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20210 previous_point.column = previous_point.column.saturating_sub(1);
20211 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20212 if scope.prefers_label_for_snippet_in_completion() {
20213 if let Some(label) = completion.label() {
20214 if matches!(
20215 completion.kind(),
20216 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20217 ) {
20218 snippet_source = label;
20219 }
20220 }
20221 }
20222 }
20223 match Snippet::parse(&snippet_source).log_err() {
20224 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20225 None => (None, completion.new_text.clone()),
20226 }
20227 } else {
20228 (None, completion.new_text.clone())
20229 };
20230
20231 let mut range_to_replace = {
20232 let replace_range = &completion.replace_range;
20233 if let CompletionSource::Lsp {
20234 insert_range: Some(insert_range),
20235 ..
20236 } = &completion.source
20237 {
20238 debug_assert_eq!(
20239 insert_range.start, replace_range.start,
20240 "insert_range and replace_range should start at the same position"
20241 );
20242 debug_assert!(
20243 insert_range
20244 .start
20245 .cmp(&cursor_position, &buffer_snapshot)
20246 .is_le(),
20247 "insert_range should start before or at cursor position"
20248 );
20249 debug_assert!(
20250 replace_range
20251 .start
20252 .cmp(&cursor_position, &buffer_snapshot)
20253 .is_le(),
20254 "replace_range should start before or at cursor position"
20255 );
20256 debug_assert!(
20257 insert_range
20258 .end
20259 .cmp(&cursor_position, &buffer_snapshot)
20260 .is_le(),
20261 "insert_range should end before or at cursor position"
20262 );
20263
20264 let should_replace = match intent {
20265 CompletionIntent::CompleteWithInsert => false,
20266 CompletionIntent::CompleteWithReplace => true,
20267 CompletionIntent::Complete | CompletionIntent::Compose => {
20268 let insert_mode =
20269 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20270 .completions
20271 .lsp_insert_mode;
20272 match insert_mode {
20273 LspInsertMode::Insert => false,
20274 LspInsertMode::Replace => true,
20275 LspInsertMode::ReplaceSubsequence => {
20276 let mut text_to_replace = buffer.chars_for_range(
20277 buffer.anchor_before(replace_range.start)
20278 ..buffer.anchor_after(replace_range.end),
20279 );
20280 let mut current_needle = text_to_replace.next();
20281 for haystack_ch in completion.label.text.chars() {
20282 if let Some(needle_ch) = current_needle {
20283 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20284 current_needle = text_to_replace.next();
20285 }
20286 }
20287 }
20288 current_needle.is_none()
20289 }
20290 LspInsertMode::ReplaceSuffix => {
20291 if replace_range
20292 .end
20293 .cmp(&cursor_position, &buffer_snapshot)
20294 .is_gt()
20295 {
20296 let range_after_cursor = *cursor_position..replace_range.end;
20297 let text_after_cursor = buffer
20298 .text_for_range(
20299 buffer.anchor_before(range_after_cursor.start)
20300 ..buffer.anchor_after(range_after_cursor.end),
20301 )
20302 .collect::<String>()
20303 .to_ascii_lowercase();
20304 completion
20305 .label
20306 .text
20307 .to_ascii_lowercase()
20308 .ends_with(&text_after_cursor)
20309 } else {
20310 true
20311 }
20312 }
20313 }
20314 }
20315 };
20316
20317 if should_replace {
20318 replace_range.clone()
20319 } else {
20320 insert_range.clone()
20321 }
20322 } else {
20323 replace_range.clone()
20324 }
20325 };
20326
20327 if range_to_replace
20328 .end
20329 .cmp(&cursor_position, &buffer_snapshot)
20330 .is_lt()
20331 {
20332 range_to_replace.end = *cursor_position;
20333 }
20334
20335 CompletionEdit {
20336 new_text,
20337 replace_range: range_to_replace.to_offset(&buffer),
20338 snippet,
20339 }
20340}
20341
20342struct CompletionEdit {
20343 new_text: String,
20344 replace_range: Range<usize>,
20345 snippet: Option<Snippet>,
20346}
20347
20348fn insert_extra_newline_brackets(
20349 buffer: &MultiBufferSnapshot,
20350 range: Range<usize>,
20351 language: &language::LanguageScope,
20352) -> bool {
20353 let leading_whitespace_len = buffer
20354 .reversed_chars_at(range.start)
20355 .take_while(|c| c.is_whitespace() && *c != '\n')
20356 .map(|c| c.len_utf8())
20357 .sum::<usize>();
20358 let trailing_whitespace_len = buffer
20359 .chars_at(range.end)
20360 .take_while(|c| c.is_whitespace() && *c != '\n')
20361 .map(|c| c.len_utf8())
20362 .sum::<usize>();
20363 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20364
20365 language.brackets().any(|(pair, enabled)| {
20366 let pair_start = pair.start.trim_end();
20367 let pair_end = pair.end.trim_start();
20368
20369 enabled
20370 && pair.newline
20371 && buffer.contains_str_at(range.end, pair_end)
20372 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20373 })
20374}
20375
20376fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20377 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20378 [(buffer, range, _)] => (*buffer, range.clone()),
20379 _ => return false,
20380 };
20381 let pair = {
20382 let mut result: Option<BracketMatch> = None;
20383
20384 for pair in buffer
20385 .all_bracket_ranges(range.clone())
20386 .filter(move |pair| {
20387 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20388 })
20389 {
20390 let len = pair.close_range.end - pair.open_range.start;
20391
20392 if let Some(existing) = &result {
20393 let existing_len = existing.close_range.end - existing.open_range.start;
20394 if len > existing_len {
20395 continue;
20396 }
20397 }
20398
20399 result = Some(pair);
20400 }
20401
20402 result
20403 };
20404 let Some(pair) = pair else {
20405 return false;
20406 };
20407 pair.newline_only
20408 && buffer
20409 .chars_for_range(pair.open_range.end..range.start)
20410 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20411 .all(|c| c.is_whitespace() && c != '\n')
20412}
20413
20414fn update_uncommitted_diff_for_buffer(
20415 editor: Entity<Editor>,
20416 project: &Entity<Project>,
20417 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20418 buffer: Entity<MultiBuffer>,
20419 cx: &mut App,
20420) -> Task<()> {
20421 let mut tasks = Vec::new();
20422 project.update(cx, |project, cx| {
20423 for buffer in buffers {
20424 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20425 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20426 }
20427 }
20428 });
20429 cx.spawn(async move |cx| {
20430 let diffs = future::join_all(tasks).await;
20431 if editor
20432 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20433 .unwrap_or(false)
20434 {
20435 return;
20436 }
20437
20438 buffer
20439 .update(cx, |buffer, cx| {
20440 for diff in diffs.into_iter().flatten() {
20441 buffer.add_diff(diff, cx);
20442 }
20443 })
20444 .ok();
20445 })
20446}
20447
20448fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20449 let tab_size = tab_size.get() as usize;
20450 let mut width = offset;
20451
20452 for ch in text.chars() {
20453 width += if ch == '\t' {
20454 tab_size - (width % tab_size)
20455 } else {
20456 1
20457 };
20458 }
20459
20460 width - offset
20461}
20462
20463#[cfg(test)]
20464mod tests {
20465 use super::*;
20466
20467 #[test]
20468 fn test_string_size_with_expanded_tabs() {
20469 let nz = |val| NonZeroU32::new(val).unwrap();
20470 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20471 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20472 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20473 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20474 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20475 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20476 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20477 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20478 }
20479}
20480
20481/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20482struct WordBreakingTokenizer<'a> {
20483 input: &'a str,
20484}
20485
20486impl<'a> WordBreakingTokenizer<'a> {
20487 fn new(input: &'a str) -> Self {
20488 Self { input }
20489 }
20490}
20491
20492fn is_char_ideographic(ch: char) -> bool {
20493 use unicode_script::Script::*;
20494 use unicode_script::UnicodeScript;
20495 matches!(ch.script(), Han | Tangut | Yi)
20496}
20497
20498fn is_grapheme_ideographic(text: &str) -> bool {
20499 text.chars().any(is_char_ideographic)
20500}
20501
20502fn is_grapheme_whitespace(text: &str) -> bool {
20503 text.chars().any(|x| x.is_whitespace())
20504}
20505
20506fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20507 text.chars().next().map_or(false, |ch| {
20508 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20509 })
20510}
20511
20512#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20513enum WordBreakToken<'a> {
20514 Word { token: &'a str, grapheme_len: usize },
20515 InlineWhitespace { token: &'a str, grapheme_len: usize },
20516 Newline,
20517}
20518
20519impl<'a> Iterator for WordBreakingTokenizer<'a> {
20520 /// Yields a span, the count of graphemes in the token, and whether it was
20521 /// whitespace. Note that it also breaks at word boundaries.
20522 type Item = WordBreakToken<'a>;
20523
20524 fn next(&mut self) -> Option<Self::Item> {
20525 use unicode_segmentation::UnicodeSegmentation;
20526 if self.input.is_empty() {
20527 return None;
20528 }
20529
20530 let mut iter = self.input.graphemes(true).peekable();
20531 let mut offset = 0;
20532 let mut grapheme_len = 0;
20533 if let Some(first_grapheme) = iter.next() {
20534 let is_newline = first_grapheme == "\n";
20535 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20536 offset += first_grapheme.len();
20537 grapheme_len += 1;
20538 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20539 if let Some(grapheme) = iter.peek().copied() {
20540 if should_stay_with_preceding_ideograph(grapheme) {
20541 offset += grapheme.len();
20542 grapheme_len += 1;
20543 }
20544 }
20545 } else {
20546 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20547 let mut next_word_bound = words.peek().copied();
20548 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20549 next_word_bound = words.next();
20550 }
20551 while let Some(grapheme) = iter.peek().copied() {
20552 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20553 break;
20554 };
20555 if is_grapheme_whitespace(grapheme) != is_whitespace
20556 || (grapheme == "\n") != is_newline
20557 {
20558 break;
20559 };
20560 offset += grapheme.len();
20561 grapheme_len += 1;
20562 iter.next();
20563 }
20564 }
20565 let token = &self.input[..offset];
20566 self.input = &self.input[offset..];
20567 if token == "\n" {
20568 Some(WordBreakToken::Newline)
20569 } else if is_whitespace {
20570 Some(WordBreakToken::InlineWhitespace {
20571 token,
20572 grapheme_len,
20573 })
20574 } else {
20575 Some(WordBreakToken::Word {
20576 token,
20577 grapheme_len,
20578 })
20579 }
20580 } else {
20581 None
20582 }
20583 }
20584}
20585
20586#[test]
20587fn test_word_breaking_tokenizer() {
20588 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20589 ("", &[]),
20590 (" ", &[whitespace(" ", 2)]),
20591 ("Ʒ", &[word("Ʒ", 1)]),
20592 ("Ǽ", &[word("Ǽ", 1)]),
20593 ("⋑", &[word("⋑", 1)]),
20594 ("⋑⋑", &[word("⋑⋑", 2)]),
20595 (
20596 "原理,进而",
20597 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20598 ),
20599 (
20600 "hello world",
20601 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20602 ),
20603 (
20604 "hello, world",
20605 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20606 ),
20607 (
20608 " hello world",
20609 &[
20610 whitespace(" ", 2),
20611 word("hello", 5),
20612 whitespace(" ", 1),
20613 word("world", 5),
20614 ],
20615 ),
20616 (
20617 "这是什么 \n 钢笔",
20618 &[
20619 word("这", 1),
20620 word("是", 1),
20621 word("什", 1),
20622 word("么", 1),
20623 whitespace(" ", 1),
20624 newline(),
20625 whitespace(" ", 1),
20626 word("钢", 1),
20627 word("笔", 1),
20628 ],
20629 ),
20630 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20631 ];
20632
20633 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20634 WordBreakToken::Word {
20635 token,
20636 grapheme_len,
20637 }
20638 }
20639
20640 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20641 WordBreakToken::InlineWhitespace {
20642 token,
20643 grapheme_len,
20644 }
20645 }
20646
20647 fn newline() -> WordBreakToken<'static> {
20648 WordBreakToken::Newline
20649 }
20650
20651 for (input, result) in tests {
20652 assert_eq!(
20653 WordBreakingTokenizer::new(input)
20654 .collect::<Vec<_>>()
20655 .as_slice(),
20656 *result,
20657 );
20658 }
20659}
20660
20661fn wrap_with_prefix(
20662 line_prefix: String,
20663 unwrapped_text: String,
20664 wrap_column: usize,
20665 tab_size: NonZeroU32,
20666 preserve_existing_whitespace: bool,
20667) -> String {
20668 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20669 let mut wrapped_text = String::new();
20670 let mut current_line = line_prefix.clone();
20671
20672 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20673 let mut current_line_len = line_prefix_len;
20674 let mut in_whitespace = false;
20675 for token in tokenizer {
20676 let have_preceding_whitespace = in_whitespace;
20677 match token {
20678 WordBreakToken::Word {
20679 token,
20680 grapheme_len,
20681 } => {
20682 in_whitespace = false;
20683 if current_line_len + grapheme_len > wrap_column
20684 && current_line_len != line_prefix_len
20685 {
20686 wrapped_text.push_str(current_line.trim_end());
20687 wrapped_text.push('\n');
20688 current_line.truncate(line_prefix.len());
20689 current_line_len = line_prefix_len;
20690 }
20691 current_line.push_str(token);
20692 current_line_len += grapheme_len;
20693 }
20694 WordBreakToken::InlineWhitespace {
20695 mut token,
20696 mut grapheme_len,
20697 } => {
20698 in_whitespace = true;
20699 if have_preceding_whitespace && !preserve_existing_whitespace {
20700 continue;
20701 }
20702 if !preserve_existing_whitespace {
20703 token = " ";
20704 grapheme_len = 1;
20705 }
20706 if current_line_len + grapheme_len > wrap_column {
20707 wrapped_text.push_str(current_line.trim_end());
20708 wrapped_text.push('\n');
20709 current_line.truncate(line_prefix.len());
20710 current_line_len = line_prefix_len;
20711 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20712 current_line.push_str(token);
20713 current_line_len += grapheme_len;
20714 }
20715 }
20716 WordBreakToken::Newline => {
20717 in_whitespace = true;
20718 if preserve_existing_whitespace {
20719 wrapped_text.push_str(current_line.trim_end());
20720 wrapped_text.push('\n');
20721 current_line.truncate(line_prefix.len());
20722 current_line_len = line_prefix_len;
20723 } else if have_preceding_whitespace {
20724 continue;
20725 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20726 {
20727 wrapped_text.push_str(current_line.trim_end());
20728 wrapped_text.push('\n');
20729 current_line.truncate(line_prefix.len());
20730 current_line_len = line_prefix_len;
20731 } else if current_line_len != line_prefix_len {
20732 current_line.push(' ');
20733 current_line_len += 1;
20734 }
20735 }
20736 }
20737 }
20738
20739 if !current_line.is_empty() {
20740 wrapped_text.push_str(¤t_line);
20741 }
20742 wrapped_text
20743}
20744
20745#[test]
20746fn test_wrap_with_prefix() {
20747 assert_eq!(
20748 wrap_with_prefix(
20749 "# ".to_string(),
20750 "abcdefg".to_string(),
20751 4,
20752 NonZeroU32::new(4).unwrap(),
20753 false,
20754 ),
20755 "# abcdefg"
20756 );
20757 assert_eq!(
20758 wrap_with_prefix(
20759 "".to_string(),
20760 "\thello world".to_string(),
20761 8,
20762 NonZeroU32::new(4).unwrap(),
20763 false,
20764 ),
20765 "hello\nworld"
20766 );
20767 assert_eq!(
20768 wrap_with_prefix(
20769 "// ".to_string(),
20770 "xx \nyy zz aa bb cc".to_string(),
20771 12,
20772 NonZeroU32::new(4).unwrap(),
20773 false,
20774 ),
20775 "// xx yy zz\n// aa bb cc"
20776 );
20777 assert_eq!(
20778 wrap_with_prefix(
20779 String::new(),
20780 "这是什么 \n 钢笔".to_string(),
20781 3,
20782 NonZeroU32::new(4).unwrap(),
20783 false,
20784 ),
20785 "这是什\n么 钢\n笔"
20786 );
20787}
20788
20789pub trait CollaborationHub {
20790 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20791 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20792 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20793}
20794
20795impl CollaborationHub for Entity<Project> {
20796 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20797 self.read(cx).collaborators()
20798 }
20799
20800 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20801 self.read(cx).user_store().read(cx).participant_indices()
20802 }
20803
20804 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20805 let this = self.read(cx);
20806 let user_ids = this.collaborators().values().map(|c| c.user_id);
20807 this.user_store().read(cx).participant_names(user_ids, cx)
20808 }
20809}
20810
20811pub trait SemanticsProvider {
20812 fn hover(
20813 &self,
20814 buffer: &Entity<Buffer>,
20815 position: text::Anchor,
20816 cx: &mut App,
20817 ) -> Option<Task<Vec<project::Hover>>>;
20818
20819 fn inline_values(
20820 &self,
20821 buffer_handle: Entity<Buffer>,
20822 range: Range<text::Anchor>,
20823 cx: &mut App,
20824 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20825
20826 fn inlay_hints(
20827 &self,
20828 buffer_handle: Entity<Buffer>,
20829 range: Range<text::Anchor>,
20830 cx: &mut App,
20831 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20832
20833 fn resolve_inlay_hint(
20834 &self,
20835 hint: InlayHint,
20836 buffer_handle: Entity<Buffer>,
20837 server_id: LanguageServerId,
20838 cx: &mut App,
20839 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20840
20841 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20842
20843 fn document_highlights(
20844 &self,
20845 buffer: &Entity<Buffer>,
20846 position: text::Anchor,
20847 cx: &mut App,
20848 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20849
20850 fn definitions(
20851 &self,
20852 buffer: &Entity<Buffer>,
20853 position: text::Anchor,
20854 kind: GotoDefinitionKind,
20855 cx: &mut App,
20856 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20857
20858 fn range_for_rename(
20859 &self,
20860 buffer: &Entity<Buffer>,
20861 position: text::Anchor,
20862 cx: &mut App,
20863 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20864
20865 fn perform_rename(
20866 &self,
20867 buffer: &Entity<Buffer>,
20868 position: text::Anchor,
20869 new_name: String,
20870 cx: &mut App,
20871 ) -> Option<Task<Result<ProjectTransaction>>>;
20872
20873 fn pull_diagnostics_for_buffer(
20874 &self,
20875 buffer: Entity<Buffer>,
20876 cx: &mut App,
20877 ) -> Task<anyhow::Result<()>>;
20878}
20879
20880pub trait CompletionProvider {
20881 fn completions(
20882 &self,
20883 excerpt_id: ExcerptId,
20884 buffer: &Entity<Buffer>,
20885 buffer_position: text::Anchor,
20886 trigger: CompletionContext,
20887 window: &mut Window,
20888 cx: &mut Context<Editor>,
20889 ) -> Task<Result<Vec<CompletionResponse>>>;
20890
20891 fn resolve_completions(
20892 &self,
20893 _buffer: Entity<Buffer>,
20894 _completion_indices: Vec<usize>,
20895 _completions: Rc<RefCell<Box<[Completion]>>>,
20896 _cx: &mut Context<Editor>,
20897 ) -> Task<Result<bool>> {
20898 Task::ready(Ok(false))
20899 }
20900
20901 fn apply_additional_edits_for_completion(
20902 &self,
20903 _buffer: Entity<Buffer>,
20904 _completions: Rc<RefCell<Box<[Completion]>>>,
20905 _completion_index: usize,
20906 _push_to_history: bool,
20907 _cx: &mut Context<Editor>,
20908 ) -> Task<Result<Option<language::Transaction>>> {
20909 Task::ready(Ok(None))
20910 }
20911
20912 fn is_completion_trigger(
20913 &self,
20914 buffer: &Entity<Buffer>,
20915 position: language::Anchor,
20916 text: &str,
20917 trigger_in_words: bool,
20918 menu_is_open: bool,
20919 cx: &mut Context<Editor>,
20920 ) -> bool;
20921
20922 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20923
20924 fn sort_completions(&self) -> bool {
20925 true
20926 }
20927
20928 fn filter_completions(&self) -> bool {
20929 true
20930 }
20931}
20932
20933pub trait CodeActionProvider {
20934 fn id(&self) -> Arc<str>;
20935
20936 fn code_actions(
20937 &self,
20938 buffer: &Entity<Buffer>,
20939 range: Range<text::Anchor>,
20940 window: &mut Window,
20941 cx: &mut App,
20942 ) -> Task<Result<Vec<CodeAction>>>;
20943
20944 fn apply_code_action(
20945 &self,
20946 buffer_handle: Entity<Buffer>,
20947 action: CodeAction,
20948 excerpt_id: ExcerptId,
20949 push_to_history: bool,
20950 window: &mut Window,
20951 cx: &mut App,
20952 ) -> Task<Result<ProjectTransaction>>;
20953}
20954
20955impl CodeActionProvider for Entity<Project> {
20956 fn id(&self) -> Arc<str> {
20957 "project".into()
20958 }
20959
20960 fn code_actions(
20961 &self,
20962 buffer: &Entity<Buffer>,
20963 range: Range<text::Anchor>,
20964 _window: &mut Window,
20965 cx: &mut App,
20966 ) -> Task<Result<Vec<CodeAction>>> {
20967 self.update(cx, |project, cx| {
20968 let code_lens = project.code_lens(buffer, range.clone(), cx);
20969 let code_actions = project.code_actions(buffer, range, None, cx);
20970 cx.background_spawn(async move {
20971 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20972 Ok(code_lens
20973 .context("code lens fetch")?
20974 .into_iter()
20975 .chain(code_actions.context("code action fetch")?)
20976 .collect())
20977 })
20978 })
20979 }
20980
20981 fn apply_code_action(
20982 &self,
20983 buffer_handle: Entity<Buffer>,
20984 action: CodeAction,
20985 _excerpt_id: ExcerptId,
20986 push_to_history: bool,
20987 _window: &mut Window,
20988 cx: &mut App,
20989 ) -> Task<Result<ProjectTransaction>> {
20990 self.update(cx, |project, cx| {
20991 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20992 })
20993 }
20994}
20995
20996fn snippet_completions(
20997 project: &Project,
20998 buffer: &Entity<Buffer>,
20999 buffer_position: text::Anchor,
21000 cx: &mut App,
21001) -> Task<Result<CompletionResponse>> {
21002 let languages = buffer.read(cx).languages_at(buffer_position);
21003 let snippet_store = project.snippets().read(cx);
21004
21005 let scopes: Vec<_> = languages
21006 .iter()
21007 .filter_map(|language| {
21008 let language_name = language.lsp_id();
21009 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21010
21011 if snippets.is_empty() {
21012 None
21013 } else {
21014 Some((language.default_scope(), snippets))
21015 }
21016 })
21017 .collect();
21018
21019 if scopes.is_empty() {
21020 return Task::ready(Ok(CompletionResponse {
21021 completions: vec![],
21022 is_incomplete: false,
21023 }));
21024 }
21025
21026 let snapshot = buffer.read(cx).text_snapshot();
21027 let chars: String = snapshot
21028 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21029 .collect();
21030 let executor = cx.background_executor().clone();
21031
21032 cx.background_spawn(async move {
21033 let mut is_incomplete = false;
21034 let mut completions: Vec<Completion> = Vec::new();
21035 for (scope, snippets) in scopes.into_iter() {
21036 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21037 let mut last_word = chars
21038 .chars()
21039 .take_while(|c| classifier.is_word(*c))
21040 .collect::<String>();
21041 last_word = last_word.chars().rev().collect();
21042
21043 if last_word.is_empty() {
21044 return Ok(CompletionResponse {
21045 completions: vec![],
21046 is_incomplete: true,
21047 });
21048 }
21049
21050 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21051 let to_lsp = |point: &text::Anchor| {
21052 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21053 point_to_lsp(end)
21054 };
21055 let lsp_end = to_lsp(&buffer_position);
21056
21057 let candidates = snippets
21058 .iter()
21059 .enumerate()
21060 .flat_map(|(ix, snippet)| {
21061 snippet
21062 .prefix
21063 .iter()
21064 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21065 })
21066 .collect::<Vec<StringMatchCandidate>>();
21067
21068 const MAX_RESULTS: usize = 100;
21069 let mut matches = fuzzy::match_strings(
21070 &candidates,
21071 &last_word,
21072 last_word.chars().any(|c| c.is_uppercase()),
21073 MAX_RESULTS,
21074 &Default::default(),
21075 executor.clone(),
21076 )
21077 .await;
21078
21079 if matches.len() >= MAX_RESULTS {
21080 is_incomplete = true;
21081 }
21082
21083 // Remove all candidates where the query's start does not match the start of any word in the candidate
21084 if let Some(query_start) = last_word.chars().next() {
21085 matches.retain(|string_match| {
21086 split_words(&string_match.string).any(|word| {
21087 // Check that the first codepoint of the word as lowercase matches the first
21088 // codepoint of the query as lowercase
21089 word.chars()
21090 .flat_map(|codepoint| codepoint.to_lowercase())
21091 .zip(query_start.to_lowercase())
21092 .all(|(word_cp, query_cp)| word_cp == query_cp)
21093 })
21094 });
21095 }
21096
21097 let matched_strings = matches
21098 .into_iter()
21099 .map(|m| m.string)
21100 .collect::<HashSet<_>>();
21101
21102 completions.extend(snippets.iter().filter_map(|snippet| {
21103 let matching_prefix = snippet
21104 .prefix
21105 .iter()
21106 .find(|prefix| matched_strings.contains(*prefix))?;
21107 let start = as_offset - last_word.len();
21108 let start = snapshot.anchor_before(start);
21109 let range = start..buffer_position;
21110 let lsp_start = to_lsp(&start);
21111 let lsp_range = lsp::Range {
21112 start: lsp_start,
21113 end: lsp_end,
21114 };
21115 Some(Completion {
21116 replace_range: range,
21117 new_text: snippet.body.clone(),
21118 source: CompletionSource::Lsp {
21119 insert_range: None,
21120 server_id: LanguageServerId(usize::MAX),
21121 resolved: true,
21122 lsp_completion: Box::new(lsp::CompletionItem {
21123 label: snippet.prefix.first().unwrap().clone(),
21124 kind: Some(CompletionItemKind::SNIPPET),
21125 label_details: snippet.description.as_ref().map(|description| {
21126 lsp::CompletionItemLabelDetails {
21127 detail: Some(description.clone()),
21128 description: None,
21129 }
21130 }),
21131 insert_text_format: Some(InsertTextFormat::SNIPPET),
21132 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21133 lsp::InsertReplaceEdit {
21134 new_text: snippet.body.clone(),
21135 insert: lsp_range,
21136 replace: lsp_range,
21137 },
21138 )),
21139 filter_text: Some(snippet.body.clone()),
21140 sort_text: Some(char::MAX.to_string()),
21141 ..lsp::CompletionItem::default()
21142 }),
21143 lsp_defaults: None,
21144 },
21145 label: CodeLabel {
21146 text: matching_prefix.clone(),
21147 runs: Vec::new(),
21148 filter_range: 0..matching_prefix.len(),
21149 },
21150 icon_path: None,
21151 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21152 single_line: snippet.name.clone().into(),
21153 plain_text: snippet
21154 .description
21155 .clone()
21156 .map(|description| description.into()),
21157 }),
21158 insert_text_mode: None,
21159 confirm: None,
21160 })
21161 }))
21162 }
21163
21164 Ok(CompletionResponse {
21165 completions,
21166 is_incomplete,
21167 })
21168 })
21169}
21170
21171impl CompletionProvider for Entity<Project> {
21172 fn completions(
21173 &self,
21174 _excerpt_id: ExcerptId,
21175 buffer: &Entity<Buffer>,
21176 buffer_position: text::Anchor,
21177 options: CompletionContext,
21178 _window: &mut Window,
21179 cx: &mut Context<Editor>,
21180 ) -> Task<Result<Vec<CompletionResponse>>> {
21181 self.update(cx, |project, cx| {
21182 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21183 let project_completions = project.completions(buffer, buffer_position, options, cx);
21184 cx.background_spawn(async move {
21185 let mut responses = project_completions.await?;
21186 let snippets = snippets.await?;
21187 if !snippets.completions.is_empty() {
21188 responses.push(snippets);
21189 }
21190 Ok(responses)
21191 })
21192 })
21193 }
21194
21195 fn resolve_completions(
21196 &self,
21197 buffer: Entity<Buffer>,
21198 completion_indices: Vec<usize>,
21199 completions: Rc<RefCell<Box<[Completion]>>>,
21200 cx: &mut Context<Editor>,
21201 ) -> Task<Result<bool>> {
21202 self.update(cx, |project, cx| {
21203 project.lsp_store().update(cx, |lsp_store, cx| {
21204 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21205 })
21206 })
21207 }
21208
21209 fn apply_additional_edits_for_completion(
21210 &self,
21211 buffer: Entity<Buffer>,
21212 completions: Rc<RefCell<Box<[Completion]>>>,
21213 completion_index: usize,
21214 push_to_history: bool,
21215 cx: &mut Context<Editor>,
21216 ) -> Task<Result<Option<language::Transaction>>> {
21217 self.update(cx, |project, cx| {
21218 project.lsp_store().update(cx, |lsp_store, cx| {
21219 lsp_store.apply_additional_edits_for_completion(
21220 buffer,
21221 completions,
21222 completion_index,
21223 push_to_history,
21224 cx,
21225 )
21226 })
21227 })
21228 }
21229
21230 fn is_completion_trigger(
21231 &self,
21232 buffer: &Entity<Buffer>,
21233 position: language::Anchor,
21234 text: &str,
21235 trigger_in_words: bool,
21236 menu_is_open: bool,
21237 cx: &mut Context<Editor>,
21238 ) -> bool {
21239 let mut chars = text.chars();
21240 let char = if let Some(char) = chars.next() {
21241 char
21242 } else {
21243 return false;
21244 };
21245 if chars.next().is_some() {
21246 return false;
21247 }
21248
21249 let buffer = buffer.read(cx);
21250 let snapshot = buffer.snapshot();
21251 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21252 return false;
21253 }
21254 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21255 if trigger_in_words && classifier.is_word(char) {
21256 return true;
21257 }
21258
21259 buffer.completion_triggers().contains(text)
21260 }
21261}
21262
21263impl SemanticsProvider for Entity<Project> {
21264 fn hover(
21265 &self,
21266 buffer: &Entity<Buffer>,
21267 position: text::Anchor,
21268 cx: &mut App,
21269 ) -> Option<Task<Vec<project::Hover>>> {
21270 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21271 }
21272
21273 fn document_highlights(
21274 &self,
21275 buffer: &Entity<Buffer>,
21276 position: text::Anchor,
21277 cx: &mut App,
21278 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21279 Some(self.update(cx, |project, cx| {
21280 project.document_highlights(buffer, position, cx)
21281 }))
21282 }
21283
21284 fn definitions(
21285 &self,
21286 buffer: &Entity<Buffer>,
21287 position: text::Anchor,
21288 kind: GotoDefinitionKind,
21289 cx: &mut App,
21290 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21291 Some(self.update(cx, |project, cx| match kind {
21292 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21293 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21294 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21295 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21296 }))
21297 }
21298
21299 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21300 // TODO: make this work for remote projects
21301 self.update(cx, |project, cx| {
21302 if project
21303 .active_debug_session(cx)
21304 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21305 {
21306 return true;
21307 }
21308
21309 buffer.update(cx, |buffer, cx| {
21310 project.any_language_server_supports_inlay_hints(buffer, cx)
21311 })
21312 })
21313 }
21314
21315 fn inline_values(
21316 &self,
21317 buffer_handle: Entity<Buffer>,
21318
21319 range: Range<text::Anchor>,
21320 cx: &mut App,
21321 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21322 self.update(cx, |project, cx| {
21323 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21324
21325 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21326 })
21327 }
21328
21329 fn inlay_hints(
21330 &self,
21331 buffer_handle: Entity<Buffer>,
21332 range: Range<text::Anchor>,
21333 cx: &mut App,
21334 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21335 Some(self.update(cx, |project, cx| {
21336 project.inlay_hints(buffer_handle, range, cx)
21337 }))
21338 }
21339
21340 fn resolve_inlay_hint(
21341 &self,
21342 hint: InlayHint,
21343 buffer_handle: Entity<Buffer>,
21344 server_id: LanguageServerId,
21345 cx: &mut App,
21346 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21347 Some(self.update(cx, |project, cx| {
21348 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21349 }))
21350 }
21351
21352 fn range_for_rename(
21353 &self,
21354 buffer: &Entity<Buffer>,
21355 position: text::Anchor,
21356 cx: &mut App,
21357 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21358 Some(self.update(cx, |project, cx| {
21359 let buffer = buffer.clone();
21360 let task = project.prepare_rename(buffer.clone(), position, cx);
21361 cx.spawn(async move |_, cx| {
21362 Ok(match task.await? {
21363 PrepareRenameResponse::Success(range) => Some(range),
21364 PrepareRenameResponse::InvalidPosition => None,
21365 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21366 // Fallback on using TreeSitter info to determine identifier range
21367 buffer.read_with(cx, |buffer, _| {
21368 let snapshot = buffer.snapshot();
21369 let (range, kind) = snapshot.surrounding_word(position);
21370 if kind != Some(CharKind::Word) {
21371 return None;
21372 }
21373 Some(
21374 snapshot.anchor_before(range.start)
21375 ..snapshot.anchor_after(range.end),
21376 )
21377 })?
21378 }
21379 })
21380 })
21381 }))
21382 }
21383
21384 fn perform_rename(
21385 &self,
21386 buffer: &Entity<Buffer>,
21387 position: text::Anchor,
21388 new_name: String,
21389 cx: &mut App,
21390 ) -> Option<Task<Result<ProjectTransaction>>> {
21391 Some(self.update(cx, |project, cx| {
21392 project.perform_rename(buffer.clone(), position, new_name, cx)
21393 }))
21394 }
21395
21396 fn pull_diagnostics_for_buffer(
21397 &self,
21398 buffer: Entity<Buffer>,
21399 cx: &mut App,
21400 ) -> Task<anyhow::Result<()>> {
21401 let diagnostics = self.update(cx, |project, cx| {
21402 project
21403 .lsp_store()
21404 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21405 });
21406 let project = self.clone();
21407 cx.spawn(async move |cx| {
21408 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21409 project.update(cx, |project, cx| {
21410 project.lsp_store().update(cx, |lsp_store, cx| {
21411 for diagnostics_set in diagnostics {
21412 let LspPullDiagnostics::Response {
21413 server_id,
21414 uri,
21415 diagnostics,
21416 } = diagnostics_set
21417 else {
21418 continue;
21419 };
21420
21421 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21422 let disk_based_sources = adapter
21423 .as_ref()
21424 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21425 .unwrap_or(&[]);
21426 match diagnostics {
21427 PulledDiagnostics::Unchanged { result_id } => {
21428 lsp_store
21429 .merge_diagnostics(
21430 server_id,
21431 lsp::PublishDiagnosticsParams {
21432 uri: uri.clone(),
21433 diagnostics: Vec::new(),
21434 version: None,
21435 },
21436 Some(result_id),
21437 DiagnosticSourceKind::Pulled,
21438 disk_based_sources,
21439 |_, _| true,
21440 cx,
21441 )
21442 .log_err();
21443 }
21444 PulledDiagnostics::Changed {
21445 diagnostics,
21446 result_id,
21447 } => {
21448 lsp_store
21449 .merge_diagnostics(
21450 server_id,
21451 lsp::PublishDiagnosticsParams {
21452 uri: uri.clone(),
21453 diagnostics,
21454 version: None,
21455 },
21456 result_id,
21457 DiagnosticSourceKind::Pulled,
21458 disk_based_sources,
21459 |old_diagnostic, _| match old_diagnostic.source_kind {
21460 DiagnosticSourceKind::Pulled => false,
21461 DiagnosticSourceKind::Other
21462 | DiagnosticSourceKind::Pushed => true,
21463 },
21464 cx,
21465 )
21466 .log_err();
21467 }
21468 }
21469 }
21470 })
21471 })
21472 })
21473 }
21474}
21475
21476fn inlay_hint_settings(
21477 location: Anchor,
21478 snapshot: &MultiBufferSnapshot,
21479 cx: &mut Context<Editor>,
21480) -> InlayHintSettings {
21481 let file = snapshot.file_at(location);
21482 let language = snapshot.language_at(location).map(|l| l.name());
21483 language_settings(language, file, cx).inlay_hints
21484}
21485
21486fn consume_contiguous_rows(
21487 contiguous_row_selections: &mut Vec<Selection<Point>>,
21488 selection: &Selection<Point>,
21489 display_map: &DisplaySnapshot,
21490 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21491) -> (MultiBufferRow, MultiBufferRow) {
21492 contiguous_row_selections.push(selection.clone());
21493 let start_row = MultiBufferRow(selection.start.row);
21494 let mut end_row = ending_row(selection, display_map);
21495
21496 while let Some(next_selection) = selections.peek() {
21497 if next_selection.start.row <= end_row.0 {
21498 end_row = ending_row(next_selection, display_map);
21499 contiguous_row_selections.push(selections.next().unwrap().clone());
21500 } else {
21501 break;
21502 }
21503 }
21504 (start_row, end_row)
21505}
21506
21507fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21508 if next_selection.end.column > 0 || next_selection.is_empty() {
21509 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21510 } else {
21511 MultiBufferRow(next_selection.end.row)
21512 }
21513}
21514
21515impl EditorSnapshot {
21516 pub fn remote_selections_in_range<'a>(
21517 &'a self,
21518 range: &'a Range<Anchor>,
21519 collaboration_hub: &dyn CollaborationHub,
21520 cx: &'a App,
21521 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21522 let participant_names = collaboration_hub.user_names(cx);
21523 let participant_indices = collaboration_hub.user_participant_indices(cx);
21524 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21525 let collaborators_by_replica_id = collaborators_by_peer_id
21526 .values()
21527 .map(|collaborator| (collaborator.replica_id, collaborator))
21528 .collect::<HashMap<_, _>>();
21529 self.buffer_snapshot
21530 .selections_in_range(range, false)
21531 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21532 if replica_id == AGENT_REPLICA_ID {
21533 Some(RemoteSelection {
21534 replica_id,
21535 selection,
21536 cursor_shape,
21537 line_mode,
21538 collaborator_id: CollaboratorId::Agent,
21539 user_name: Some("Agent".into()),
21540 color: cx.theme().players().agent(),
21541 })
21542 } else {
21543 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21544 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21545 let user_name = participant_names.get(&collaborator.user_id).cloned();
21546 Some(RemoteSelection {
21547 replica_id,
21548 selection,
21549 cursor_shape,
21550 line_mode,
21551 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21552 user_name,
21553 color: if let Some(index) = participant_index {
21554 cx.theme().players().color_for_participant(index.0)
21555 } else {
21556 cx.theme().players().absent()
21557 },
21558 })
21559 }
21560 })
21561 }
21562
21563 pub fn hunks_for_ranges(
21564 &self,
21565 ranges: impl IntoIterator<Item = Range<Point>>,
21566 ) -> Vec<MultiBufferDiffHunk> {
21567 let mut hunks = Vec::new();
21568 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21569 HashMap::default();
21570 for query_range in ranges {
21571 let query_rows =
21572 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21573 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21574 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21575 ) {
21576 // Include deleted hunks that are adjacent to the query range, because
21577 // otherwise they would be missed.
21578 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21579 if hunk.status().is_deleted() {
21580 intersects_range |= hunk.row_range.start == query_rows.end;
21581 intersects_range |= hunk.row_range.end == query_rows.start;
21582 }
21583 if intersects_range {
21584 if !processed_buffer_rows
21585 .entry(hunk.buffer_id)
21586 .or_default()
21587 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21588 {
21589 continue;
21590 }
21591 hunks.push(hunk);
21592 }
21593 }
21594 }
21595
21596 hunks
21597 }
21598
21599 fn display_diff_hunks_for_rows<'a>(
21600 &'a self,
21601 display_rows: Range<DisplayRow>,
21602 folded_buffers: &'a HashSet<BufferId>,
21603 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21604 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21605 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21606
21607 self.buffer_snapshot
21608 .diff_hunks_in_range(buffer_start..buffer_end)
21609 .filter_map(|hunk| {
21610 if folded_buffers.contains(&hunk.buffer_id) {
21611 return None;
21612 }
21613
21614 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21615 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21616
21617 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21618 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21619
21620 let display_hunk = if hunk_display_start.column() != 0 {
21621 DisplayDiffHunk::Folded {
21622 display_row: hunk_display_start.row(),
21623 }
21624 } else {
21625 let mut end_row = hunk_display_end.row();
21626 if hunk_display_end.column() > 0 {
21627 end_row.0 += 1;
21628 }
21629 let is_created_file = hunk.is_created_file();
21630 DisplayDiffHunk::Unfolded {
21631 status: hunk.status(),
21632 diff_base_byte_range: hunk.diff_base_byte_range,
21633 display_row_range: hunk_display_start.row()..end_row,
21634 multi_buffer_range: Anchor::range_in_buffer(
21635 hunk.excerpt_id,
21636 hunk.buffer_id,
21637 hunk.buffer_range,
21638 ),
21639 is_created_file,
21640 }
21641 };
21642
21643 Some(display_hunk)
21644 })
21645 }
21646
21647 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21648 self.display_snapshot.buffer_snapshot.language_at(position)
21649 }
21650
21651 pub fn is_focused(&self) -> bool {
21652 self.is_focused
21653 }
21654
21655 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21656 self.placeholder_text.as_ref()
21657 }
21658
21659 pub fn scroll_position(&self) -> gpui::Point<f32> {
21660 self.scroll_anchor.scroll_position(&self.display_snapshot)
21661 }
21662
21663 fn gutter_dimensions(
21664 &self,
21665 font_id: FontId,
21666 font_size: Pixels,
21667 max_line_number_width: Pixels,
21668 cx: &App,
21669 ) -> Option<GutterDimensions> {
21670 if !self.show_gutter {
21671 return None;
21672 }
21673
21674 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21675 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21676
21677 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21678 matches!(
21679 ProjectSettings::get_global(cx).git.git_gutter,
21680 Some(GitGutterSetting::TrackedFiles)
21681 )
21682 });
21683 let gutter_settings = EditorSettings::get_global(cx).gutter;
21684 let show_line_numbers = self
21685 .show_line_numbers
21686 .unwrap_or(gutter_settings.line_numbers);
21687 let line_gutter_width = if show_line_numbers {
21688 // Avoid flicker-like gutter resizes when the line number gains another digit by
21689 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21690 let min_width_for_number_on_gutter =
21691 ch_advance * gutter_settings.min_line_number_digits as f32;
21692 max_line_number_width.max(min_width_for_number_on_gutter)
21693 } else {
21694 0.0.into()
21695 };
21696
21697 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21698 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21699
21700 let git_blame_entries_width =
21701 self.git_blame_gutter_max_author_length
21702 .map(|max_author_length| {
21703 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21704 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21705
21706 /// The number of characters to dedicate to gaps and margins.
21707 const SPACING_WIDTH: usize = 4;
21708
21709 let max_char_count = max_author_length.min(renderer.max_author_length())
21710 + ::git::SHORT_SHA_LENGTH
21711 + MAX_RELATIVE_TIMESTAMP.len()
21712 + SPACING_WIDTH;
21713
21714 ch_advance * max_char_count
21715 });
21716
21717 let is_singleton = self.buffer_snapshot.is_singleton();
21718
21719 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21720 left_padding += if !is_singleton {
21721 ch_width * 4.0
21722 } else if show_runnables || show_breakpoints {
21723 ch_width * 3.0
21724 } else if show_git_gutter && show_line_numbers {
21725 ch_width * 2.0
21726 } else if show_git_gutter || show_line_numbers {
21727 ch_width
21728 } else {
21729 px(0.)
21730 };
21731
21732 let shows_folds = is_singleton && gutter_settings.folds;
21733
21734 let right_padding = if shows_folds && show_line_numbers {
21735 ch_width * 4.0
21736 } else if shows_folds || (!is_singleton && show_line_numbers) {
21737 ch_width * 3.0
21738 } else if show_line_numbers {
21739 ch_width
21740 } else {
21741 px(0.)
21742 };
21743
21744 Some(GutterDimensions {
21745 left_padding,
21746 right_padding,
21747 width: line_gutter_width + left_padding + right_padding,
21748 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21749 git_blame_entries_width,
21750 })
21751 }
21752
21753 pub fn render_crease_toggle(
21754 &self,
21755 buffer_row: MultiBufferRow,
21756 row_contains_cursor: bool,
21757 editor: Entity<Editor>,
21758 window: &mut Window,
21759 cx: &mut App,
21760 ) -> Option<AnyElement> {
21761 let folded = self.is_line_folded(buffer_row);
21762 let mut is_foldable = false;
21763
21764 if let Some(crease) = self
21765 .crease_snapshot
21766 .query_row(buffer_row, &self.buffer_snapshot)
21767 {
21768 is_foldable = true;
21769 match crease {
21770 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21771 if let Some(render_toggle) = render_toggle {
21772 let toggle_callback =
21773 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21774 if folded {
21775 editor.update(cx, |editor, cx| {
21776 editor.fold_at(buffer_row, window, cx)
21777 });
21778 } else {
21779 editor.update(cx, |editor, cx| {
21780 editor.unfold_at(buffer_row, window, cx)
21781 });
21782 }
21783 });
21784 return Some((render_toggle)(
21785 buffer_row,
21786 folded,
21787 toggle_callback,
21788 window,
21789 cx,
21790 ));
21791 }
21792 }
21793 }
21794 }
21795
21796 is_foldable |= self.starts_indent(buffer_row);
21797
21798 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21799 Some(
21800 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21801 .toggle_state(folded)
21802 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21803 if folded {
21804 this.unfold_at(buffer_row, window, cx);
21805 } else {
21806 this.fold_at(buffer_row, window, cx);
21807 }
21808 }))
21809 .into_any_element(),
21810 )
21811 } else {
21812 None
21813 }
21814 }
21815
21816 pub fn render_crease_trailer(
21817 &self,
21818 buffer_row: MultiBufferRow,
21819 window: &mut Window,
21820 cx: &mut App,
21821 ) -> Option<AnyElement> {
21822 let folded = self.is_line_folded(buffer_row);
21823 if let Crease::Inline { render_trailer, .. } = self
21824 .crease_snapshot
21825 .query_row(buffer_row, &self.buffer_snapshot)?
21826 {
21827 let render_trailer = render_trailer.as_ref()?;
21828 Some(render_trailer(buffer_row, folded, window, cx))
21829 } else {
21830 None
21831 }
21832 }
21833}
21834
21835impl Deref for EditorSnapshot {
21836 type Target = DisplaySnapshot;
21837
21838 fn deref(&self) -> &Self::Target {
21839 &self.display_snapshot
21840 }
21841}
21842
21843#[derive(Clone, Debug, PartialEq, Eq)]
21844pub enum EditorEvent {
21845 InputIgnored {
21846 text: Arc<str>,
21847 },
21848 InputHandled {
21849 utf16_range_to_replace: Option<Range<isize>>,
21850 text: Arc<str>,
21851 },
21852 ExcerptsAdded {
21853 buffer: Entity<Buffer>,
21854 predecessor: ExcerptId,
21855 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21856 },
21857 ExcerptsRemoved {
21858 ids: Vec<ExcerptId>,
21859 removed_buffer_ids: Vec<BufferId>,
21860 },
21861 BufferFoldToggled {
21862 ids: Vec<ExcerptId>,
21863 folded: bool,
21864 },
21865 ExcerptsEdited {
21866 ids: Vec<ExcerptId>,
21867 },
21868 ExcerptsExpanded {
21869 ids: Vec<ExcerptId>,
21870 },
21871 BufferEdited,
21872 Edited {
21873 transaction_id: clock::Lamport,
21874 },
21875 Reparsed(BufferId),
21876 Focused,
21877 FocusedIn,
21878 Blurred,
21879 DirtyChanged,
21880 Saved,
21881 TitleChanged,
21882 DiffBaseChanged,
21883 SelectionsChanged {
21884 local: bool,
21885 },
21886 ScrollPositionChanged {
21887 local: bool,
21888 autoscroll: bool,
21889 },
21890 Closed,
21891 TransactionUndone {
21892 transaction_id: clock::Lamport,
21893 },
21894 TransactionBegun {
21895 transaction_id: clock::Lamport,
21896 },
21897 Reloaded,
21898 CursorShapeChanged,
21899 PushedToNavHistory {
21900 anchor: Anchor,
21901 is_deactivate: bool,
21902 },
21903}
21904
21905impl EventEmitter<EditorEvent> for Editor {}
21906
21907impl Focusable for Editor {
21908 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21909 self.focus_handle.clone()
21910 }
21911}
21912
21913impl Render for Editor {
21914 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21915 let settings = ThemeSettings::get_global(cx);
21916
21917 let mut text_style = match self.mode {
21918 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21919 color: cx.theme().colors().editor_foreground,
21920 font_family: settings.ui_font.family.clone(),
21921 font_features: settings.ui_font.features.clone(),
21922 font_fallbacks: settings.ui_font.fallbacks.clone(),
21923 font_size: rems(0.875).into(),
21924 font_weight: settings.ui_font.weight,
21925 line_height: relative(settings.buffer_line_height.value()),
21926 ..Default::default()
21927 },
21928 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21929 color: cx.theme().colors().editor_foreground,
21930 font_family: settings.buffer_font.family.clone(),
21931 font_features: settings.buffer_font.features.clone(),
21932 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21933 font_size: settings.buffer_font_size(cx).into(),
21934 font_weight: settings.buffer_font.weight,
21935 line_height: relative(settings.buffer_line_height.value()),
21936 ..Default::default()
21937 },
21938 };
21939 if let Some(text_style_refinement) = &self.text_style_refinement {
21940 text_style.refine(text_style_refinement)
21941 }
21942
21943 let background = match self.mode {
21944 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21945 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
21946 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21947 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21948 };
21949
21950 EditorElement::new(
21951 &cx.entity(),
21952 EditorStyle {
21953 background,
21954 local_player: cx.theme().players().local(),
21955 text: text_style,
21956 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21957 syntax: cx.theme().syntax().clone(),
21958 status: cx.theme().status().clone(),
21959 inlay_hints_style: make_inlay_hints_style(cx),
21960 inline_completion_styles: make_suggestion_styles(cx),
21961 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21962 show_underlines: !self.mode.is_minimap(),
21963 },
21964 )
21965 }
21966}
21967
21968impl EntityInputHandler for Editor {
21969 fn text_for_range(
21970 &mut self,
21971 range_utf16: Range<usize>,
21972 adjusted_range: &mut Option<Range<usize>>,
21973 _: &mut Window,
21974 cx: &mut Context<Self>,
21975 ) -> Option<String> {
21976 let snapshot = self.buffer.read(cx).read(cx);
21977 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21978 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21979 if (start.0..end.0) != range_utf16 {
21980 adjusted_range.replace(start.0..end.0);
21981 }
21982 Some(snapshot.text_for_range(start..end).collect())
21983 }
21984
21985 fn selected_text_range(
21986 &mut self,
21987 ignore_disabled_input: bool,
21988 _: &mut Window,
21989 cx: &mut Context<Self>,
21990 ) -> Option<UTF16Selection> {
21991 // Prevent the IME menu from appearing when holding down an alphabetic key
21992 // while input is disabled.
21993 if !ignore_disabled_input && !self.input_enabled {
21994 return None;
21995 }
21996
21997 let selection = self.selections.newest::<OffsetUtf16>(cx);
21998 let range = selection.range();
21999
22000 Some(UTF16Selection {
22001 range: range.start.0..range.end.0,
22002 reversed: selection.reversed,
22003 })
22004 }
22005
22006 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22007 let snapshot = self.buffer.read(cx).read(cx);
22008 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22009 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22010 }
22011
22012 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22013 self.clear_highlights::<InputComposition>(cx);
22014 self.ime_transaction.take();
22015 }
22016
22017 fn replace_text_in_range(
22018 &mut self,
22019 range_utf16: Option<Range<usize>>,
22020 text: &str,
22021 window: &mut Window,
22022 cx: &mut Context<Self>,
22023 ) {
22024 if !self.input_enabled {
22025 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22026 return;
22027 }
22028
22029 self.transact(window, cx, |this, window, cx| {
22030 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22031 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22032 Some(this.selection_replacement_ranges(range_utf16, cx))
22033 } else {
22034 this.marked_text_ranges(cx)
22035 };
22036
22037 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22038 let newest_selection_id = this.selections.newest_anchor().id;
22039 this.selections
22040 .all::<OffsetUtf16>(cx)
22041 .iter()
22042 .zip(ranges_to_replace.iter())
22043 .find_map(|(selection, range)| {
22044 if selection.id == newest_selection_id {
22045 Some(
22046 (range.start.0 as isize - selection.head().0 as isize)
22047 ..(range.end.0 as isize - selection.head().0 as isize),
22048 )
22049 } else {
22050 None
22051 }
22052 })
22053 });
22054
22055 cx.emit(EditorEvent::InputHandled {
22056 utf16_range_to_replace: range_to_replace,
22057 text: text.into(),
22058 });
22059
22060 if let Some(new_selected_ranges) = new_selected_ranges {
22061 this.change_selections(None, window, cx, |selections| {
22062 selections.select_ranges(new_selected_ranges)
22063 });
22064 this.backspace(&Default::default(), window, cx);
22065 }
22066
22067 this.handle_input(text, window, cx);
22068 });
22069
22070 if let Some(transaction) = self.ime_transaction {
22071 self.buffer.update(cx, |buffer, cx| {
22072 buffer.group_until_transaction(transaction, cx);
22073 });
22074 }
22075
22076 self.unmark_text(window, cx);
22077 }
22078
22079 fn replace_and_mark_text_in_range(
22080 &mut self,
22081 range_utf16: Option<Range<usize>>,
22082 text: &str,
22083 new_selected_range_utf16: Option<Range<usize>>,
22084 window: &mut Window,
22085 cx: &mut Context<Self>,
22086 ) {
22087 if !self.input_enabled {
22088 return;
22089 }
22090
22091 let transaction = self.transact(window, cx, |this, window, cx| {
22092 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22093 let snapshot = this.buffer.read(cx).read(cx);
22094 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22095 for marked_range in &mut marked_ranges {
22096 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22097 marked_range.start.0 += relative_range_utf16.start;
22098 marked_range.start =
22099 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22100 marked_range.end =
22101 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22102 }
22103 }
22104 Some(marked_ranges)
22105 } else if let Some(range_utf16) = range_utf16 {
22106 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22107 Some(this.selection_replacement_ranges(range_utf16, cx))
22108 } else {
22109 None
22110 };
22111
22112 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22113 let newest_selection_id = this.selections.newest_anchor().id;
22114 this.selections
22115 .all::<OffsetUtf16>(cx)
22116 .iter()
22117 .zip(ranges_to_replace.iter())
22118 .find_map(|(selection, range)| {
22119 if selection.id == newest_selection_id {
22120 Some(
22121 (range.start.0 as isize - selection.head().0 as isize)
22122 ..(range.end.0 as isize - selection.head().0 as isize),
22123 )
22124 } else {
22125 None
22126 }
22127 })
22128 });
22129
22130 cx.emit(EditorEvent::InputHandled {
22131 utf16_range_to_replace: range_to_replace,
22132 text: text.into(),
22133 });
22134
22135 if let Some(ranges) = ranges_to_replace {
22136 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22137 }
22138
22139 let marked_ranges = {
22140 let snapshot = this.buffer.read(cx).read(cx);
22141 this.selections
22142 .disjoint_anchors()
22143 .iter()
22144 .map(|selection| {
22145 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22146 })
22147 .collect::<Vec<_>>()
22148 };
22149
22150 if text.is_empty() {
22151 this.unmark_text(window, cx);
22152 } else {
22153 this.highlight_text::<InputComposition>(
22154 marked_ranges.clone(),
22155 HighlightStyle {
22156 underline: Some(UnderlineStyle {
22157 thickness: px(1.),
22158 color: None,
22159 wavy: false,
22160 }),
22161 ..Default::default()
22162 },
22163 cx,
22164 );
22165 }
22166
22167 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22168 let use_autoclose = this.use_autoclose;
22169 let use_auto_surround = this.use_auto_surround;
22170 this.set_use_autoclose(false);
22171 this.set_use_auto_surround(false);
22172 this.handle_input(text, window, cx);
22173 this.set_use_autoclose(use_autoclose);
22174 this.set_use_auto_surround(use_auto_surround);
22175
22176 if let Some(new_selected_range) = new_selected_range_utf16 {
22177 let snapshot = this.buffer.read(cx).read(cx);
22178 let new_selected_ranges = marked_ranges
22179 .into_iter()
22180 .map(|marked_range| {
22181 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22182 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22183 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22184 snapshot.clip_offset_utf16(new_start, Bias::Left)
22185 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22186 })
22187 .collect::<Vec<_>>();
22188
22189 drop(snapshot);
22190 this.change_selections(None, window, cx, |selections| {
22191 selections.select_ranges(new_selected_ranges)
22192 });
22193 }
22194 });
22195
22196 self.ime_transaction = self.ime_transaction.or(transaction);
22197 if let Some(transaction) = self.ime_transaction {
22198 self.buffer.update(cx, |buffer, cx| {
22199 buffer.group_until_transaction(transaction, cx);
22200 });
22201 }
22202
22203 if self.text_highlights::<InputComposition>(cx).is_none() {
22204 self.ime_transaction.take();
22205 }
22206 }
22207
22208 fn bounds_for_range(
22209 &mut self,
22210 range_utf16: Range<usize>,
22211 element_bounds: gpui::Bounds<Pixels>,
22212 window: &mut Window,
22213 cx: &mut Context<Self>,
22214 ) -> Option<gpui::Bounds<Pixels>> {
22215 let text_layout_details = self.text_layout_details(window);
22216 let gpui::Size {
22217 width: em_width,
22218 height: line_height,
22219 } = self.character_size(window);
22220
22221 let snapshot = self.snapshot(window, cx);
22222 let scroll_position = snapshot.scroll_position();
22223 let scroll_left = scroll_position.x * em_width;
22224
22225 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22226 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22227 + self.gutter_dimensions.width
22228 + self.gutter_dimensions.margin;
22229 let y = line_height * (start.row().as_f32() - scroll_position.y);
22230
22231 Some(Bounds {
22232 origin: element_bounds.origin + point(x, y),
22233 size: size(em_width, line_height),
22234 })
22235 }
22236
22237 fn character_index_for_point(
22238 &mut self,
22239 point: gpui::Point<Pixels>,
22240 _window: &mut Window,
22241 _cx: &mut Context<Self>,
22242 ) -> Option<usize> {
22243 let position_map = self.last_position_map.as_ref()?;
22244 if !position_map.text_hitbox.contains(&point) {
22245 return None;
22246 }
22247 let display_point = position_map.point_for_position(point).previous_valid;
22248 let anchor = position_map
22249 .snapshot
22250 .display_point_to_anchor(display_point, Bias::Left);
22251 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22252 Some(utf16_offset.0)
22253 }
22254}
22255
22256trait SelectionExt {
22257 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22258 fn spanned_rows(
22259 &self,
22260 include_end_if_at_line_start: bool,
22261 map: &DisplaySnapshot,
22262 ) -> Range<MultiBufferRow>;
22263}
22264
22265impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22266 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22267 let start = self
22268 .start
22269 .to_point(&map.buffer_snapshot)
22270 .to_display_point(map);
22271 let end = self
22272 .end
22273 .to_point(&map.buffer_snapshot)
22274 .to_display_point(map);
22275 if self.reversed {
22276 end..start
22277 } else {
22278 start..end
22279 }
22280 }
22281
22282 fn spanned_rows(
22283 &self,
22284 include_end_if_at_line_start: bool,
22285 map: &DisplaySnapshot,
22286 ) -> Range<MultiBufferRow> {
22287 let start = self.start.to_point(&map.buffer_snapshot);
22288 let mut end = self.end.to_point(&map.buffer_snapshot);
22289 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22290 end.row -= 1;
22291 }
22292
22293 let buffer_start = map.prev_line_boundary(start).0;
22294 let buffer_end = map.next_line_boundary(end).0;
22295 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22296 }
22297}
22298
22299impl<T: InvalidationRegion> InvalidationStack<T> {
22300 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22301 where
22302 S: Clone + ToOffset,
22303 {
22304 while let Some(region) = self.last() {
22305 let all_selections_inside_invalidation_ranges =
22306 if selections.len() == region.ranges().len() {
22307 selections
22308 .iter()
22309 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22310 .all(|(selection, invalidation_range)| {
22311 let head = selection.head().to_offset(buffer);
22312 invalidation_range.start <= head && invalidation_range.end >= head
22313 })
22314 } else {
22315 false
22316 };
22317
22318 if all_selections_inside_invalidation_ranges {
22319 break;
22320 } else {
22321 self.pop();
22322 }
22323 }
22324 }
22325}
22326
22327impl<T> Default for InvalidationStack<T> {
22328 fn default() -> Self {
22329 Self(Default::default())
22330 }
22331}
22332
22333impl<T> Deref for InvalidationStack<T> {
22334 type Target = Vec<T>;
22335
22336 fn deref(&self) -> &Self::Target {
22337 &self.0
22338 }
22339}
22340
22341impl<T> DerefMut for InvalidationStack<T> {
22342 fn deref_mut(&mut self) -> &mut Self::Target {
22343 &mut self.0
22344 }
22345}
22346
22347impl InvalidationRegion for SnippetState {
22348 fn ranges(&self) -> &[Range<Anchor>] {
22349 &self.ranges[self.active_index]
22350 }
22351}
22352
22353fn inline_completion_edit_text(
22354 current_snapshot: &BufferSnapshot,
22355 edits: &[(Range<Anchor>, String)],
22356 edit_preview: &EditPreview,
22357 include_deletions: bool,
22358 cx: &App,
22359) -> HighlightedText {
22360 let edits = edits
22361 .iter()
22362 .map(|(anchor, text)| {
22363 (
22364 anchor.start.text_anchor..anchor.end.text_anchor,
22365 text.clone(),
22366 )
22367 })
22368 .collect::<Vec<_>>();
22369
22370 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22371}
22372
22373pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22374 match severity {
22375 lsp::DiagnosticSeverity::ERROR => colors.error,
22376 lsp::DiagnosticSeverity::WARNING => colors.warning,
22377 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22378 lsp::DiagnosticSeverity::HINT => colors.info,
22379 _ => colors.ignored,
22380 }
22381}
22382
22383pub fn styled_runs_for_code_label<'a>(
22384 label: &'a CodeLabel,
22385 syntax_theme: &'a theme::SyntaxTheme,
22386) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22387 let fade_out = HighlightStyle {
22388 fade_out: Some(0.35),
22389 ..Default::default()
22390 };
22391
22392 let mut prev_end = label.filter_range.end;
22393 label
22394 .runs
22395 .iter()
22396 .enumerate()
22397 .flat_map(move |(ix, (range, highlight_id))| {
22398 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22399 style
22400 } else {
22401 return Default::default();
22402 };
22403 let mut muted_style = style;
22404 muted_style.highlight(fade_out);
22405
22406 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22407 if range.start >= label.filter_range.end {
22408 if range.start > prev_end {
22409 runs.push((prev_end..range.start, fade_out));
22410 }
22411 runs.push((range.clone(), muted_style));
22412 } else if range.end <= label.filter_range.end {
22413 runs.push((range.clone(), style));
22414 } else {
22415 runs.push((range.start..label.filter_range.end, style));
22416 runs.push((label.filter_range.end..range.end, muted_style));
22417 }
22418 prev_end = cmp::max(prev_end, range.end);
22419
22420 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22421 runs.push((prev_end..label.text.len(), fade_out));
22422 }
22423
22424 runs
22425 })
22426}
22427
22428pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22429 let mut prev_index = 0;
22430 let mut prev_codepoint: Option<char> = None;
22431 text.char_indices()
22432 .chain([(text.len(), '\0')])
22433 .filter_map(move |(index, codepoint)| {
22434 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22435 let is_boundary = index == text.len()
22436 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22437 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22438 if is_boundary {
22439 let chunk = &text[prev_index..index];
22440 prev_index = index;
22441 Some(chunk)
22442 } else {
22443 None
22444 }
22445 })
22446}
22447
22448pub trait RangeToAnchorExt: Sized {
22449 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22450
22451 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22452 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22453 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22454 }
22455}
22456
22457impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22458 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22459 let start_offset = self.start.to_offset(snapshot);
22460 let end_offset = self.end.to_offset(snapshot);
22461 if start_offset == end_offset {
22462 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22463 } else {
22464 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22465 }
22466 }
22467}
22468
22469pub trait RowExt {
22470 fn as_f32(&self) -> f32;
22471
22472 fn next_row(&self) -> Self;
22473
22474 fn previous_row(&self) -> Self;
22475
22476 fn minus(&self, other: Self) -> u32;
22477}
22478
22479impl RowExt for DisplayRow {
22480 fn as_f32(&self) -> f32 {
22481 self.0 as f32
22482 }
22483
22484 fn next_row(&self) -> Self {
22485 Self(self.0 + 1)
22486 }
22487
22488 fn previous_row(&self) -> Self {
22489 Self(self.0.saturating_sub(1))
22490 }
22491
22492 fn minus(&self, other: Self) -> u32 {
22493 self.0 - other.0
22494 }
22495}
22496
22497impl RowExt for MultiBufferRow {
22498 fn as_f32(&self) -> f32 {
22499 self.0 as f32
22500 }
22501
22502 fn next_row(&self) -> Self {
22503 Self(self.0 + 1)
22504 }
22505
22506 fn previous_row(&self) -> Self {
22507 Self(self.0.saturating_sub(1))
22508 }
22509
22510 fn minus(&self, other: Self) -> u32 {
22511 self.0 - other.0
22512 }
22513}
22514
22515trait RowRangeExt {
22516 type Row;
22517
22518 fn len(&self) -> usize;
22519
22520 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22521}
22522
22523impl RowRangeExt for Range<MultiBufferRow> {
22524 type Row = MultiBufferRow;
22525
22526 fn len(&self) -> usize {
22527 (self.end.0 - self.start.0) as usize
22528 }
22529
22530 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22531 (self.start.0..self.end.0).map(MultiBufferRow)
22532 }
22533}
22534
22535impl RowRangeExt for Range<DisplayRow> {
22536 type Row = DisplayRow;
22537
22538 fn len(&self) -> usize {
22539 (self.end.0 - self.start.0) as usize
22540 }
22541
22542 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22543 (self.start.0..self.end.0).map(DisplayRow)
22544 }
22545}
22546
22547/// If select range has more than one line, we
22548/// just point the cursor to range.start.
22549fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22550 if range.start.row == range.end.row {
22551 range
22552 } else {
22553 range.start..range.start
22554 }
22555}
22556pub struct KillRing(ClipboardItem);
22557impl Global for KillRing {}
22558
22559const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22560
22561enum BreakpointPromptEditAction {
22562 Log,
22563 Condition,
22564 HitCondition,
22565}
22566
22567struct BreakpointPromptEditor {
22568 pub(crate) prompt: Entity<Editor>,
22569 editor: WeakEntity<Editor>,
22570 breakpoint_anchor: Anchor,
22571 breakpoint: Breakpoint,
22572 edit_action: BreakpointPromptEditAction,
22573 block_ids: HashSet<CustomBlockId>,
22574 editor_margins: Arc<Mutex<EditorMargins>>,
22575 _subscriptions: Vec<Subscription>,
22576}
22577
22578impl BreakpointPromptEditor {
22579 const MAX_LINES: u8 = 4;
22580
22581 fn new(
22582 editor: WeakEntity<Editor>,
22583 breakpoint_anchor: Anchor,
22584 breakpoint: Breakpoint,
22585 edit_action: BreakpointPromptEditAction,
22586 window: &mut Window,
22587 cx: &mut Context<Self>,
22588 ) -> Self {
22589 let base_text = match edit_action {
22590 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22591 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22592 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22593 }
22594 .map(|msg| msg.to_string())
22595 .unwrap_or_default();
22596
22597 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22598 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22599
22600 let prompt = cx.new(|cx| {
22601 let mut prompt = Editor::new(
22602 EditorMode::AutoHeight {
22603 min_lines: 1,
22604 max_lines: Self::MAX_LINES as usize,
22605 },
22606 buffer,
22607 None,
22608 window,
22609 cx,
22610 );
22611 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22612 prompt.set_show_cursor_when_unfocused(false, cx);
22613 prompt.set_placeholder_text(
22614 match edit_action {
22615 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22616 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22617 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22618 },
22619 cx,
22620 );
22621
22622 prompt
22623 });
22624
22625 Self {
22626 prompt,
22627 editor,
22628 breakpoint_anchor,
22629 breakpoint,
22630 edit_action,
22631 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22632 block_ids: Default::default(),
22633 _subscriptions: vec![],
22634 }
22635 }
22636
22637 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22638 self.block_ids.extend(block_ids)
22639 }
22640
22641 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22642 if let Some(editor) = self.editor.upgrade() {
22643 let message = self
22644 .prompt
22645 .read(cx)
22646 .buffer
22647 .read(cx)
22648 .as_singleton()
22649 .expect("A multi buffer in breakpoint prompt isn't possible")
22650 .read(cx)
22651 .as_rope()
22652 .to_string();
22653
22654 editor.update(cx, |editor, cx| {
22655 editor.edit_breakpoint_at_anchor(
22656 self.breakpoint_anchor,
22657 self.breakpoint.clone(),
22658 match self.edit_action {
22659 BreakpointPromptEditAction::Log => {
22660 BreakpointEditAction::EditLogMessage(message.into())
22661 }
22662 BreakpointPromptEditAction::Condition => {
22663 BreakpointEditAction::EditCondition(message.into())
22664 }
22665 BreakpointPromptEditAction::HitCondition => {
22666 BreakpointEditAction::EditHitCondition(message.into())
22667 }
22668 },
22669 cx,
22670 );
22671
22672 editor.remove_blocks(self.block_ids.clone(), None, cx);
22673 cx.focus_self(window);
22674 });
22675 }
22676 }
22677
22678 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22679 self.editor
22680 .update(cx, |editor, cx| {
22681 editor.remove_blocks(self.block_ids.clone(), None, cx);
22682 window.focus(&editor.focus_handle);
22683 })
22684 .log_err();
22685 }
22686
22687 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22688 let settings = ThemeSettings::get_global(cx);
22689 let text_style = TextStyle {
22690 color: if self.prompt.read(cx).read_only(cx) {
22691 cx.theme().colors().text_disabled
22692 } else {
22693 cx.theme().colors().text
22694 },
22695 font_family: settings.buffer_font.family.clone(),
22696 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22697 font_size: settings.buffer_font_size(cx).into(),
22698 font_weight: settings.buffer_font.weight,
22699 line_height: relative(settings.buffer_line_height.value()),
22700 ..Default::default()
22701 };
22702 EditorElement::new(
22703 &self.prompt,
22704 EditorStyle {
22705 background: cx.theme().colors().editor_background,
22706 local_player: cx.theme().players().local(),
22707 text: text_style,
22708 ..Default::default()
22709 },
22710 )
22711 }
22712}
22713
22714impl Render for BreakpointPromptEditor {
22715 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22716 let editor_margins = *self.editor_margins.lock();
22717 let gutter_dimensions = editor_margins.gutter;
22718 h_flex()
22719 .key_context("Editor")
22720 .bg(cx.theme().colors().editor_background)
22721 .border_y_1()
22722 .border_color(cx.theme().status().info_border)
22723 .size_full()
22724 .py(window.line_height() / 2.5)
22725 .on_action(cx.listener(Self::confirm))
22726 .on_action(cx.listener(Self::cancel))
22727 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22728 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22729 }
22730}
22731
22732impl Focusable for BreakpointPromptEditor {
22733 fn focus_handle(&self, cx: &App) -> FocusHandle {
22734 self.prompt.focus_handle(cx)
22735 }
22736}
22737
22738fn all_edits_insertions_or_deletions(
22739 edits: &Vec<(Range<Anchor>, String)>,
22740 snapshot: &MultiBufferSnapshot,
22741) -> bool {
22742 let mut all_insertions = true;
22743 let mut all_deletions = true;
22744
22745 for (range, new_text) in edits.iter() {
22746 let range_is_empty = range.to_offset(&snapshot).is_empty();
22747 let text_is_empty = new_text.is_empty();
22748
22749 if range_is_empty != text_is_empty {
22750 if range_is_empty {
22751 all_deletions = false;
22752 } else {
22753 all_insertions = false;
22754 }
22755 } else {
22756 return false;
22757 }
22758
22759 if !all_insertions && !all_deletions {
22760 return false;
22761 }
22762 }
22763 all_insertions || all_deletions
22764}
22765
22766struct MissingEditPredictionKeybindingTooltip;
22767
22768impl Render for MissingEditPredictionKeybindingTooltip {
22769 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22770 ui::tooltip_container(window, cx, |container, _, cx| {
22771 container
22772 .flex_shrink_0()
22773 .max_w_80()
22774 .min_h(rems_from_px(124.))
22775 .justify_between()
22776 .child(
22777 v_flex()
22778 .flex_1()
22779 .text_ui_sm(cx)
22780 .child(Label::new("Conflict with Accept Keybinding"))
22781 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22782 )
22783 .child(
22784 h_flex()
22785 .pb_1()
22786 .gap_1()
22787 .items_end()
22788 .w_full()
22789 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22790 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22791 }))
22792 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22793 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22794 })),
22795 )
22796 })
22797 }
22798}
22799
22800#[derive(Debug, Clone, Copy, PartialEq)]
22801pub struct LineHighlight {
22802 pub background: Background,
22803 pub border: Option<gpui::Hsla>,
22804 pub include_gutter: bool,
22805 pub type_id: Option<TypeId>,
22806}
22807
22808fn render_diff_hunk_controls(
22809 row: u32,
22810 status: &DiffHunkStatus,
22811 hunk_range: Range<Anchor>,
22812 is_created_file: bool,
22813 line_height: Pixels,
22814 editor: &Entity<Editor>,
22815 _window: &mut Window,
22816 cx: &mut App,
22817) -> AnyElement {
22818 h_flex()
22819 .h(line_height)
22820 .mr_1()
22821 .gap_1()
22822 .px_0p5()
22823 .pb_1()
22824 .border_x_1()
22825 .border_b_1()
22826 .border_color(cx.theme().colors().border_variant)
22827 .rounded_b_lg()
22828 .bg(cx.theme().colors().editor_background)
22829 .gap_1()
22830 .block_mouse_except_scroll()
22831 .shadow_md()
22832 .child(if status.has_secondary_hunk() {
22833 Button::new(("stage", row as u64), "Stage")
22834 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22835 .tooltip({
22836 let focus_handle = editor.focus_handle(cx);
22837 move |window, cx| {
22838 Tooltip::for_action_in(
22839 "Stage Hunk",
22840 &::git::ToggleStaged,
22841 &focus_handle,
22842 window,
22843 cx,
22844 )
22845 }
22846 })
22847 .on_click({
22848 let editor = editor.clone();
22849 move |_event, _window, cx| {
22850 editor.update(cx, |editor, cx| {
22851 editor.stage_or_unstage_diff_hunks(
22852 true,
22853 vec![hunk_range.start..hunk_range.start],
22854 cx,
22855 );
22856 });
22857 }
22858 })
22859 } else {
22860 Button::new(("unstage", row as u64), "Unstage")
22861 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22862 .tooltip({
22863 let focus_handle = editor.focus_handle(cx);
22864 move |window, cx| {
22865 Tooltip::for_action_in(
22866 "Unstage Hunk",
22867 &::git::ToggleStaged,
22868 &focus_handle,
22869 window,
22870 cx,
22871 )
22872 }
22873 })
22874 .on_click({
22875 let editor = editor.clone();
22876 move |_event, _window, cx| {
22877 editor.update(cx, |editor, cx| {
22878 editor.stage_or_unstage_diff_hunks(
22879 false,
22880 vec![hunk_range.start..hunk_range.start],
22881 cx,
22882 );
22883 });
22884 }
22885 })
22886 })
22887 .child(
22888 Button::new(("restore", row as u64), "Restore")
22889 .tooltip({
22890 let focus_handle = editor.focus_handle(cx);
22891 move |window, cx| {
22892 Tooltip::for_action_in(
22893 "Restore Hunk",
22894 &::git::Restore,
22895 &focus_handle,
22896 window,
22897 cx,
22898 )
22899 }
22900 })
22901 .on_click({
22902 let editor = editor.clone();
22903 move |_event, window, cx| {
22904 editor.update(cx, |editor, cx| {
22905 let snapshot = editor.snapshot(window, cx);
22906 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22907 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22908 });
22909 }
22910 })
22911 .disabled(is_created_file),
22912 )
22913 .when(
22914 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22915 |el| {
22916 el.child(
22917 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22918 .shape(IconButtonShape::Square)
22919 .icon_size(IconSize::Small)
22920 // .disabled(!has_multiple_hunks)
22921 .tooltip({
22922 let focus_handle = editor.focus_handle(cx);
22923 move |window, cx| {
22924 Tooltip::for_action_in(
22925 "Next Hunk",
22926 &GoToHunk,
22927 &focus_handle,
22928 window,
22929 cx,
22930 )
22931 }
22932 })
22933 .on_click({
22934 let editor = editor.clone();
22935 move |_event, window, cx| {
22936 editor.update(cx, |editor, cx| {
22937 let snapshot = editor.snapshot(window, cx);
22938 let position =
22939 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22940 editor.go_to_hunk_before_or_after_position(
22941 &snapshot,
22942 position,
22943 Direction::Next,
22944 window,
22945 cx,
22946 );
22947 editor.expand_selected_diff_hunks(cx);
22948 });
22949 }
22950 }),
22951 )
22952 .child(
22953 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22954 .shape(IconButtonShape::Square)
22955 .icon_size(IconSize::Small)
22956 // .disabled(!has_multiple_hunks)
22957 .tooltip({
22958 let focus_handle = editor.focus_handle(cx);
22959 move |window, cx| {
22960 Tooltip::for_action_in(
22961 "Previous Hunk",
22962 &GoToPreviousHunk,
22963 &focus_handle,
22964 window,
22965 cx,
22966 )
22967 }
22968 })
22969 .on_click({
22970 let editor = editor.clone();
22971 move |_event, window, cx| {
22972 editor.update(cx, |editor, cx| {
22973 let snapshot = editor.snapshot(window, cx);
22974 let point =
22975 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22976 editor.go_to_hunk_before_or_after_position(
22977 &snapshot,
22978 point,
22979 Direction::Prev,
22980 window,
22981 cx,
22982 );
22983 editor.expand_selected_diff_hunks(cx);
22984 });
22985 }
22986 }),
22987 )
22988 },
22989 )
22990 .into_any_element()
22991}