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(cx);
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, cx: &mut Context<Self>) {
2316 if self.mouse_cursor_hidden {
2317 self.mouse_cursor_hidden = false;
2318 cx.notify();
2319 }
2320 }
2321
2322 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2323 let hide_mouse_cursor = match origin {
2324 HideMouseCursorOrigin::TypingAction => {
2325 matches!(
2326 self.hide_mouse_mode,
2327 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2328 )
2329 }
2330 HideMouseCursorOrigin::MovementAction => {
2331 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2332 }
2333 };
2334 if self.mouse_cursor_hidden != hide_mouse_cursor {
2335 self.mouse_cursor_hidden = hide_mouse_cursor;
2336 cx.notify();
2337 }
2338 }
2339
2340 pub fn edit_prediction_in_conflict(&self) -> bool {
2341 if !self.show_edit_predictions_in_menu() {
2342 return false;
2343 }
2344
2345 let showing_completions = self
2346 .context_menu
2347 .borrow()
2348 .as_ref()
2349 .map_or(false, |context| {
2350 matches!(context, CodeContextMenu::Completions(_))
2351 });
2352
2353 showing_completions
2354 || self.edit_prediction_requires_modifier()
2355 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2356 // bindings to insert tab characters.
2357 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2358 }
2359
2360 pub fn accept_edit_prediction_keybind(
2361 &self,
2362 accept_partial: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> AcceptEditPredictionBinding {
2366 let key_context = self.key_context_internal(true, window, cx);
2367 let in_conflict = self.edit_prediction_in_conflict();
2368
2369 let bindings = if accept_partial {
2370 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2371 } else {
2372 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2373 };
2374
2375 // TODO: if the binding contains multiple keystrokes, display all of them, not
2376 // just the first one.
2377 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2378 !in_conflict
2379 || binding
2380 .keystrokes()
2381 .first()
2382 .map_or(false, |keystroke| keystroke.modifiers.modified())
2383 }))
2384 }
2385
2386 pub fn new_file(
2387 workspace: &mut Workspace,
2388 _: &workspace::NewFile,
2389 window: &mut Window,
2390 cx: &mut Context<Workspace>,
2391 ) {
2392 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2393 "Failed to create buffer",
2394 window,
2395 cx,
2396 |e, _, _| match e.error_code() {
2397 ErrorCode::RemoteUpgradeRequired => Some(format!(
2398 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2399 e.error_tag("required").unwrap_or("the latest version")
2400 )),
2401 _ => None,
2402 },
2403 );
2404 }
2405
2406 pub fn new_in_workspace(
2407 workspace: &mut Workspace,
2408 window: &mut Window,
2409 cx: &mut Context<Workspace>,
2410 ) -> Task<Result<Entity<Editor>>> {
2411 let project = workspace.project().clone();
2412 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2413
2414 cx.spawn_in(window, async move |workspace, cx| {
2415 let buffer = create.await?;
2416 workspace.update_in(cx, |workspace, window, cx| {
2417 let editor =
2418 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2419 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2420 editor
2421 })
2422 })
2423 }
2424
2425 fn new_file_vertical(
2426 workspace: &mut Workspace,
2427 _: &workspace::NewFileSplitVertical,
2428 window: &mut Window,
2429 cx: &mut Context<Workspace>,
2430 ) {
2431 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2432 }
2433
2434 fn new_file_horizontal(
2435 workspace: &mut Workspace,
2436 _: &workspace::NewFileSplitHorizontal,
2437 window: &mut Window,
2438 cx: &mut Context<Workspace>,
2439 ) {
2440 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2441 }
2442
2443 fn new_file_in_direction(
2444 workspace: &mut Workspace,
2445 direction: SplitDirection,
2446 window: &mut Window,
2447 cx: &mut Context<Workspace>,
2448 ) {
2449 let project = workspace.project().clone();
2450 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2451
2452 cx.spawn_in(window, async move |workspace, cx| {
2453 let buffer = create.await?;
2454 workspace.update_in(cx, move |workspace, window, cx| {
2455 workspace.split_item(
2456 direction,
2457 Box::new(
2458 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2459 ),
2460 window,
2461 cx,
2462 )
2463 })?;
2464 anyhow::Ok(())
2465 })
2466 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2467 match e.error_code() {
2468 ErrorCode::RemoteUpgradeRequired => Some(format!(
2469 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2470 e.error_tag("required").unwrap_or("the latest version")
2471 )),
2472 _ => None,
2473 }
2474 });
2475 }
2476
2477 pub fn leader_id(&self) -> Option<CollaboratorId> {
2478 self.leader_id
2479 }
2480
2481 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2482 &self.buffer
2483 }
2484
2485 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2486 self.workspace.as_ref()?.0.upgrade()
2487 }
2488
2489 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2490 self.buffer().read(cx).title(cx)
2491 }
2492
2493 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2494 let git_blame_gutter_max_author_length = self
2495 .render_git_blame_gutter(cx)
2496 .then(|| {
2497 if let Some(blame) = self.blame.as_ref() {
2498 let max_author_length =
2499 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2500 Some(max_author_length)
2501 } else {
2502 None
2503 }
2504 })
2505 .flatten();
2506
2507 EditorSnapshot {
2508 mode: self.mode.clone(),
2509 show_gutter: self.show_gutter,
2510 show_line_numbers: self.show_line_numbers,
2511 show_git_diff_gutter: self.show_git_diff_gutter,
2512 show_code_actions: self.show_code_actions,
2513 show_runnables: self.show_runnables,
2514 show_breakpoints: self.show_breakpoints,
2515 git_blame_gutter_max_author_length,
2516 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2517 scroll_anchor: self.scroll_manager.anchor(),
2518 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2519 placeholder_text: self.placeholder_text.clone(),
2520 is_focused: self.focus_handle.is_focused(window),
2521 current_line_highlight: self
2522 .current_line_highlight
2523 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2524 gutter_hovered: self.gutter_hovered,
2525 }
2526 }
2527
2528 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2529 self.buffer.read(cx).language_at(point, cx)
2530 }
2531
2532 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2533 self.buffer.read(cx).read(cx).file_at(point).cloned()
2534 }
2535
2536 pub fn active_excerpt(
2537 &self,
2538 cx: &App,
2539 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2540 self.buffer
2541 .read(cx)
2542 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2543 }
2544
2545 pub fn mode(&self) -> &EditorMode {
2546 &self.mode
2547 }
2548
2549 pub fn set_mode(&mut self, mode: EditorMode) {
2550 self.mode = mode;
2551 }
2552
2553 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2554 self.collaboration_hub.as_deref()
2555 }
2556
2557 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2558 self.collaboration_hub = Some(hub);
2559 }
2560
2561 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2562 self.in_project_search = in_project_search;
2563 }
2564
2565 pub fn set_custom_context_menu(
2566 &mut self,
2567 f: impl 'static
2568 + Fn(
2569 &mut Self,
2570 DisplayPoint,
2571 &mut Window,
2572 &mut Context<Self>,
2573 ) -> Option<Entity<ui::ContextMenu>>,
2574 ) {
2575 self.custom_context_menu = Some(Box::new(f))
2576 }
2577
2578 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2579 self.completion_provider = provider;
2580 }
2581
2582 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2583 self.semantics_provider.clone()
2584 }
2585
2586 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2587 self.semantics_provider = provider;
2588 }
2589
2590 pub fn set_edit_prediction_provider<T>(
2591 &mut self,
2592 provider: Option<Entity<T>>,
2593 window: &mut Window,
2594 cx: &mut Context<Self>,
2595 ) where
2596 T: EditPredictionProvider,
2597 {
2598 self.edit_prediction_provider =
2599 provider.map(|provider| RegisteredInlineCompletionProvider {
2600 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2601 if this.focus_handle.is_focused(window) {
2602 this.update_visible_inline_completion(window, cx);
2603 }
2604 }),
2605 provider: Arc::new(provider),
2606 });
2607 self.update_edit_prediction_settings(cx);
2608 self.refresh_inline_completion(false, false, window, cx);
2609 }
2610
2611 pub fn placeholder_text(&self) -> Option<&str> {
2612 self.placeholder_text.as_deref()
2613 }
2614
2615 pub fn set_placeholder_text(
2616 &mut self,
2617 placeholder_text: impl Into<Arc<str>>,
2618 cx: &mut Context<Self>,
2619 ) {
2620 let placeholder_text = Some(placeholder_text.into());
2621 if self.placeholder_text != placeholder_text {
2622 self.placeholder_text = placeholder_text;
2623 cx.notify();
2624 }
2625 }
2626
2627 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2628 self.cursor_shape = cursor_shape;
2629
2630 // Disrupt blink for immediate user feedback that the cursor shape has changed
2631 self.blink_manager.update(cx, BlinkManager::show_cursor);
2632
2633 cx.notify();
2634 }
2635
2636 pub fn set_current_line_highlight(
2637 &mut self,
2638 current_line_highlight: Option<CurrentLineHighlight>,
2639 ) {
2640 self.current_line_highlight = current_line_highlight;
2641 }
2642
2643 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2644 self.collapse_matches = collapse_matches;
2645 }
2646
2647 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2648 let buffers = self.buffer.read(cx).all_buffers();
2649 let Some(project) = self.project.as_ref() else {
2650 return;
2651 };
2652 project.update(cx, |project, cx| {
2653 for buffer in buffers {
2654 self.registered_buffers
2655 .entry(buffer.read(cx).remote_id())
2656 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2657 }
2658 })
2659 }
2660
2661 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2662 if self.collapse_matches {
2663 return range.start..range.start;
2664 }
2665 range.clone()
2666 }
2667
2668 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2669 if self.display_map.read(cx).clip_at_line_ends != clip {
2670 self.display_map
2671 .update(cx, |map, _| map.clip_at_line_ends = clip);
2672 }
2673 }
2674
2675 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2676 self.input_enabled = input_enabled;
2677 }
2678
2679 pub fn set_inline_completions_hidden_for_vim_mode(
2680 &mut self,
2681 hidden: bool,
2682 window: &mut Window,
2683 cx: &mut Context<Self>,
2684 ) {
2685 if hidden != self.inline_completions_hidden_for_vim_mode {
2686 self.inline_completions_hidden_for_vim_mode = hidden;
2687 if hidden {
2688 self.update_visible_inline_completion(window, cx);
2689 } else {
2690 self.refresh_inline_completion(true, false, window, cx);
2691 }
2692 }
2693 }
2694
2695 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2696 self.menu_inline_completions_policy = value;
2697 }
2698
2699 pub fn set_autoindent(&mut self, autoindent: bool) {
2700 if autoindent {
2701 self.autoindent_mode = Some(AutoindentMode::EachLine);
2702 } else {
2703 self.autoindent_mode = None;
2704 }
2705 }
2706
2707 pub fn read_only(&self, cx: &App) -> bool {
2708 self.read_only || self.buffer.read(cx).read_only()
2709 }
2710
2711 pub fn set_read_only(&mut self, read_only: bool) {
2712 self.read_only = read_only;
2713 }
2714
2715 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2716 self.use_autoclose = autoclose;
2717 }
2718
2719 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2720 self.use_auto_surround = auto_surround;
2721 }
2722
2723 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2724 self.auto_replace_emoji_shortcode = auto_replace;
2725 }
2726
2727 pub fn toggle_edit_predictions(
2728 &mut self,
2729 _: &ToggleEditPrediction,
2730 window: &mut Window,
2731 cx: &mut Context<Self>,
2732 ) {
2733 if self.show_inline_completions_override.is_some() {
2734 self.set_show_edit_predictions(None, window, cx);
2735 } else {
2736 let show_edit_predictions = !self.edit_predictions_enabled();
2737 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2738 }
2739 }
2740
2741 pub fn set_show_edit_predictions(
2742 &mut self,
2743 show_edit_predictions: Option<bool>,
2744 window: &mut Window,
2745 cx: &mut Context<Self>,
2746 ) {
2747 self.show_inline_completions_override = show_edit_predictions;
2748 self.update_edit_prediction_settings(cx);
2749
2750 if let Some(false) = show_edit_predictions {
2751 self.discard_inline_completion(false, cx);
2752 } else {
2753 self.refresh_inline_completion(false, true, window, cx);
2754 }
2755 }
2756
2757 fn inline_completions_disabled_in_scope(
2758 &self,
2759 buffer: &Entity<Buffer>,
2760 buffer_position: language::Anchor,
2761 cx: &App,
2762 ) -> bool {
2763 let snapshot = buffer.read(cx).snapshot();
2764 let settings = snapshot.settings_at(buffer_position, cx);
2765
2766 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2767 return false;
2768 };
2769
2770 scope.override_name().map_or(false, |scope_name| {
2771 settings
2772 .edit_predictions_disabled_in
2773 .iter()
2774 .any(|s| s == scope_name)
2775 })
2776 }
2777
2778 pub fn set_use_modal_editing(&mut self, to: bool) {
2779 self.use_modal_editing = to;
2780 }
2781
2782 pub fn use_modal_editing(&self) -> bool {
2783 self.use_modal_editing
2784 }
2785
2786 fn selections_did_change(
2787 &mut self,
2788 local: bool,
2789 old_cursor_position: &Anchor,
2790 effects: SelectionEffects,
2791 window: &mut Window,
2792 cx: &mut Context<Self>,
2793 ) {
2794 window.invalidate_character_coordinates();
2795
2796 // Copy selections to primary selection buffer
2797 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2798 if local {
2799 let selections = self.selections.all::<usize>(cx);
2800 let buffer_handle = self.buffer.read(cx).read(cx);
2801
2802 let mut text = String::new();
2803 for (index, selection) in selections.iter().enumerate() {
2804 let text_for_selection = buffer_handle
2805 .text_for_range(selection.start..selection.end)
2806 .collect::<String>();
2807
2808 text.push_str(&text_for_selection);
2809 if index != selections.len() - 1 {
2810 text.push('\n');
2811 }
2812 }
2813
2814 if !text.is_empty() {
2815 cx.write_to_primary(ClipboardItem::new_string(text));
2816 }
2817 }
2818
2819 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2820 self.buffer.update(cx, |buffer, cx| {
2821 buffer.set_active_selections(
2822 &self.selections.disjoint_anchors(),
2823 self.selections.line_mode,
2824 self.cursor_shape,
2825 cx,
2826 )
2827 });
2828 }
2829 let display_map = self
2830 .display_map
2831 .update(cx, |display_map, cx| display_map.snapshot(cx));
2832 let buffer = &display_map.buffer_snapshot;
2833 if self.selections.count() == 1 {
2834 self.add_selections_state = None;
2835 }
2836 self.select_next_state = None;
2837 self.select_prev_state = None;
2838 self.select_syntax_node_history.try_clear();
2839 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2840 self.snippet_stack
2841 .invalidate(&self.selections.disjoint_anchors(), buffer);
2842 self.take_rename(false, window, cx);
2843
2844 let newest_selection = self.selections.newest_anchor();
2845 let new_cursor_position = newest_selection.head();
2846 let selection_start = newest_selection.start;
2847
2848 if effects.nav_history {
2849 self.push_to_nav_history(
2850 *old_cursor_position,
2851 Some(new_cursor_position.to_point(buffer)),
2852 false,
2853 cx,
2854 );
2855 }
2856
2857 if local {
2858 if let Some(buffer_id) = new_cursor_position.buffer_id {
2859 if !self.registered_buffers.contains_key(&buffer_id) {
2860 if let Some(project) = self.project.as_ref() {
2861 project.update(cx, |project, cx| {
2862 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2863 return;
2864 };
2865 self.registered_buffers.insert(
2866 buffer_id,
2867 project.register_buffer_with_language_servers(&buffer, cx),
2868 );
2869 })
2870 }
2871 }
2872 }
2873
2874 let mut context_menu = self.context_menu.borrow_mut();
2875 let completion_menu = match context_menu.as_ref() {
2876 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2877 Some(CodeContextMenu::CodeActions(_)) => {
2878 *context_menu = None;
2879 None
2880 }
2881 None => None,
2882 };
2883 let completion_position = completion_menu.map(|menu| menu.initial_position);
2884 drop(context_menu);
2885
2886 if effects.completions {
2887 if let Some(completion_position) = completion_position {
2888 let start_offset = selection_start.to_offset(buffer);
2889 let position_matches = start_offset == completion_position.to_offset(buffer);
2890 let continue_showing = if position_matches {
2891 if self.snippet_stack.is_empty() {
2892 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2893 } else {
2894 // Snippet choices can be shown even when the cursor is in whitespace.
2895 // Dismissing the menu with actions like backspace is handled by
2896 // invalidation regions.
2897 true
2898 }
2899 } else {
2900 false
2901 };
2902
2903 if continue_showing {
2904 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2905 } else {
2906 self.hide_context_menu(window, cx);
2907 }
2908 }
2909 }
2910
2911 hide_hover(self, cx);
2912
2913 if old_cursor_position.to_display_point(&display_map).row()
2914 != new_cursor_position.to_display_point(&display_map).row()
2915 {
2916 self.available_code_actions.take();
2917 }
2918 self.refresh_code_actions(window, cx);
2919 self.refresh_document_highlights(cx);
2920 self.refresh_selected_text_highlights(false, window, cx);
2921 refresh_matching_bracket_highlights(self, window, cx);
2922 self.update_visible_inline_completion(window, cx);
2923 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2924 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2925 self.inline_blame_popover.take();
2926 if self.git_blame_inline_enabled {
2927 self.start_inline_blame_timer(window, cx);
2928 }
2929 }
2930
2931 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2932 cx.emit(EditorEvent::SelectionsChanged { local });
2933
2934 let selections = &self.selections.disjoint;
2935 if selections.len() == 1 {
2936 cx.emit(SearchEvent::ActiveMatchChanged)
2937 }
2938 if local {
2939 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2940 let inmemory_selections = selections
2941 .iter()
2942 .map(|s| {
2943 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2944 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2945 })
2946 .collect();
2947 self.update_restoration_data(cx, |data| {
2948 data.selections = inmemory_selections;
2949 });
2950
2951 if WorkspaceSettings::get(None, cx).restore_on_startup
2952 != RestoreOnStartupBehavior::None
2953 {
2954 if let Some(workspace_id) =
2955 self.workspace.as_ref().and_then(|workspace| workspace.1)
2956 {
2957 let snapshot = self.buffer().read(cx).snapshot(cx);
2958 let selections = selections.clone();
2959 let background_executor = cx.background_executor().clone();
2960 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2961 self.serialize_selections = cx.background_spawn(async move {
2962 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2963 let db_selections = selections
2964 .iter()
2965 .map(|selection| {
2966 (
2967 selection.start.to_offset(&snapshot),
2968 selection.end.to_offset(&snapshot),
2969 )
2970 })
2971 .collect();
2972
2973 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2974 .await
2975 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2976 .log_err();
2977 });
2978 }
2979 }
2980 }
2981 }
2982
2983 cx.notify();
2984 }
2985
2986 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2987 use text::ToOffset as _;
2988 use text::ToPoint as _;
2989
2990 if self.mode.is_minimap()
2991 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2992 {
2993 return;
2994 }
2995
2996 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2997 return;
2998 };
2999
3000 let snapshot = singleton.read(cx).snapshot();
3001 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3002 let display_snapshot = display_map.snapshot(cx);
3003
3004 display_snapshot
3005 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3006 .map(|fold| {
3007 fold.range.start.text_anchor.to_point(&snapshot)
3008 ..fold.range.end.text_anchor.to_point(&snapshot)
3009 })
3010 .collect()
3011 });
3012 self.update_restoration_data(cx, |data| {
3013 data.folds = inmemory_folds;
3014 });
3015
3016 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3017 return;
3018 };
3019 let background_executor = cx.background_executor().clone();
3020 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3021 let db_folds = self.display_map.update(cx, |display_map, cx| {
3022 display_map
3023 .snapshot(cx)
3024 .folds_in_range(0..snapshot.len())
3025 .map(|fold| {
3026 (
3027 fold.range.start.text_anchor.to_offset(&snapshot),
3028 fold.range.end.text_anchor.to_offset(&snapshot),
3029 )
3030 })
3031 .collect()
3032 });
3033 self.serialize_folds = cx.background_spawn(async move {
3034 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3035 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3036 .await
3037 .with_context(|| {
3038 format!(
3039 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3040 )
3041 })
3042 .log_err();
3043 });
3044 }
3045
3046 pub fn sync_selections(
3047 &mut self,
3048 other: Entity<Editor>,
3049 cx: &mut Context<Self>,
3050 ) -> gpui::Subscription {
3051 let other_selections = other.read(cx).selections.disjoint.to_vec();
3052 self.selections.change_with(cx, |selections| {
3053 selections.select_anchors(other_selections);
3054 });
3055
3056 let other_subscription =
3057 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3058 EditorEvent::SelectionsChanged { local: true } => {
3059 let other_selections = other.read(cx).selections.disjoint.to_vec();
3060 if other_selections.is_empty() {
3061 return;
3062 }
3063 this.selections.change_with(cx, |selections| {
3064 selections.select_anchors(other_selections);
3065 });
3066 }
3067 _ => {}
3068 });
3069
3070 let this_subscription =
3071 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3072 EditorEvent::SelectionsChanged { local: true } => {
3073 let these_selections = this.selections.disjoint.to_vec();
3074 if these_selections.is_empty() {
3075 return;
3076 }
3077 other.update(cx, |other_editor, cx| {
3078 other_editor.selections.change_with(cx, |selections| {
3079 selections.select_anchors(these_selections);
3080 })
3081 });
3082 }
3083 _ => {}
3084 });
3085
3086 Subscription::join(other_subscription, this_subscription)
3087 }
3088
3089 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3090 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3091 /// effects of selection change occur at the end of the transaction.
3092 pub fn change_selections<R>(
3093 &mut self,
3094 effects: impl Into<SelectionEffects>,
3095 window: &mut Window,
3096 cx: &mut Context<Self>,
3097 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3098 ) -> R {
3099 let effects = effects.into();
3100 if let Some(state) = &mut self.deferred_selection_effects_state {
3101 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3102 state.effects.completions = effects.completions;
3103 state.effects.nav_history |= effects.nav_history;
3104 let (changed, result) = self.selections.change_with(cx, change);
3105 state.changed |= changed;
3106 return result;
3107 }
3108 let mut state = DeferredSelectionEffectsState {
3109 changed: false,
3110 effects,
3111 old_cursor_position: self.selections.newest_anchor().head(),
3112 history_entry: SelectionHistoryEntry {
3113 selections: self.selections.disjoint_anchors(),
3114 select_next_state: self.select_next_state.clone(),
3115 select_prev_state: self.select_prev_state.clone(),
3116 add_selections_state: self.add_selections_state.clone(),
3117 },
3118 };
3119 let (changed, result) = self.selections.change_with(cx, change);
3120 state.changed = state.changed || changed;
3121 if self.defer_selection_effects {
3122 self.deferred_selection_effects_state = Some(state);
3123 } else {
3124 self.apply_selection_effects(state, window, cx);
3125 }
3126 result
3127 }
3128
3129 /// Defers the effects of selection change, so that the effects of multiple calls to
3130 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3131 /// to selection history and the state of popovers based on selection position aren't
3132 /// erroneously updated.
3133 pub fn with_selection_effects_deferred<R>(
3134 &mut self,
3135 window: &mut Window,
3136 cx: &mut Context<Self>,
3137 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3138 ) -> R {
3139 let already_deferred = self.defer_selection_effects;
3140 self.defer_selection_effects = true;
3141 let result = update(self, window, cx);
3142 if !already_deferred {
3143 self.defer_selection_effects = false;
3144 if let Some(state) = self.deferred_selection_effects_state.take() {
3145 self.apply_selection_effects(state, window, cx);
3146 }
3147 }
3148 result
3149 }
3150
3151 fn apply_selection_effects(
3152 &mut self,
3153 state: DeferredSelectionEffectsState,
3154 window: &mut Window,
3155 cx: &mut Context<Self>,
3156 ) {
3157 if state.changed {
3158 self.selection_history.push(state.history_entry);
3159
3160 if let Some(autoscroll) = state.effects.scroll {
3161 self.request_autoscroll(autoscroll, cx);
3162 }
3163
3164 let old_cursor_position = &state.old_cursor_position;
3165
3166 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3167
3168 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3169 self.show_signature_help(&ShowSignatureHelp, window, cx);
3170 }
3171 }
3172 }
3173
3174 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3175 where
3176 I: IntoIterator<Item = (Range<S>, T)>,
3177 S: ToOffset,
3178 T: Into<Arc<str>>,
3179 {
3180 if self.read_only(cx) {
3181 return;
3182 }
3183
3184 self.buffer
3185 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3186 }
3187
3188 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3189 where
3190 I: IntoIterator<Item = (Range<S>, T)>,
3191 S: ToOffset,
3192 T: Into<Arc<str>>,
3193 {
3194 if self.read_only(cx) {
3195 return;
3196 }
3197
3198 self.buffer.update(cx, |buffer, cx| {
3199 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3200 });
3201 }
3202
3203 pub fn edit_with_block_indent<I, S, T>(
3204 &mut self,
3205 edits: I,
3206 original_indent_columns: Vec<Option<u32>>,
3207 cx: &mut Context<Self>,
3208 ) where
3209 I: IntoIterator<Item = (Range<S>, T)>,
3210 S: ToOffset,
3211 T: Into<Arc<str>>,
3212 {
3213 if self.read_only(cx) {
3214 return;
3215 }
3216
3217 self.buffer.update(cx, |buffer, cx| {
3218 buffer.edit(
3219 edits,
3220 Some(AutoindentMode::Block {
3221 original_indent_columns,
3222 }),
3223 cx,
3224 )
3225 });
3226 }
3227
3228 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3229 self.hide_context_menu(window, cx);
3230
3231 match phase {
3232 SelectPhase::Begin {
3233 position,
3234 add,
3235 click_count,
3236 } => self.begin_selection(position, add, click_count, window, cx),
3237 SelectPhase::BeginColumnar {
3238 position,
3239 goal_column,
3240 reset,
3241 mode,
3242 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3243 SelectPhase::Extend {
3244 position,
3245 click_count,
3246 } => self.extend_selection(position, click_count, window, cx),
3247 SelectPhase::Update {
3248 position,
3249 goal_column,
3250 scroll_delta,
3251 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3252 SelectPhase::End => self.end_selection(window, cx),
3253 }
3254 }
3255
3256 fn extend_selection(
3257 &mut self,
3258 position: DisplayPoint,
3259 click_count: usize,
3260 window: &mut Window,
3261 cx: &mut Context<Self>,
3262 ) {
3263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3264 let tail = self.selections.newest::<usize>(cx).tail();
3265 self.begin_selection(position, false, click_count, window, cx);
3266
3267 let position = position.to_offset(&display_map, Bias::Left);
3268 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3269
3270 let mut pending_selection = self
3271 .selections
3272 .pending_anchor()
3273 .expect("extend_selection not called with pending selection");
3274 if position >= tail {
3275 pending_selection.start = tail_anchor;
3276 } else {
3277 pending_selection.end = tail_anchor;
3278 pending_selection.reversed = true;
3279 }
3280
3281 let mut pending_mode = self.selections.pending_mode().unwrap();
3282 match &mut pending_mode {
3283 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3284 _ => {}
3285 }
3286
3287 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3288 SelectionEffects::scroll(Autoscroll::fit())
3289 } else {
3290 SelectionEffects::no_scroll()
3291 };
3292
3293 self.change_selections(effects, window, cx, |s| {
3294 s.set_pending(pending_selection, pending_mode)
3295 });
3296 }
3297
3298 fn begin_selection(
3299 &mut self,
3300 position: DisplayPoint,
3301 add: bool,
3302 click_count: usize,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 ) {
3306 if !self.focus_handle.is_focused(window) {
3307 self.last_focused_descendant = None;
3308 window.focus(&self.focus_handle);
3309 }
3310
3311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3312 let buffer = &display_map.buffer_snapshot;
3313 let position = display_map.clip_point(position, Bias::Left);
3314
3315 let start;
3316 let end;
3317 let mode;
3318 let mut auto_scroll;
3319 match click_count {
3320 1 => {
3321 start = buffer.anchor_before(position.to_point(&display_map));
3322 end = start;
3323 mode = SelectMode::Character;
3324 auto_scroll = true;
3325 }
3326 2 => {
3327 let range = movement::surrounding_word(&display_map, position);
3328 start = buffer.anchor_before(range.start.to_point(&display_map));
3329 end = buffer.anchor_before(range.end.to_point(&display_map));
3330 mode = SelectMode::Word(start..end);
3331 auto_scroll = true;
3332 }
3333 3 => {
3334 let position = display_map
3335 .clip_point(position, Bias::Left)
3336 .to_point(&display_map);
3337 let line_start = display_map.prev_line_boundary(position).0;
3338 let next_line_start = buffer.clip_point(
3339 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3340 Bias::Left,
3341 );
3342 start = buffer.anchor_before(line_start);
3343 end = buffer.anchor_before(next_line_start);
3344 mode = SelectMode::Line(start..end);
3345 auto_scroll = true;
3346 }
3347 _ => {
3348 start = buffer.anchor_before(0);
3349 end = buffer.anchor_before(buffer.len());
3350 mode = SelectMode::All;
3351 auto_scroll = false;
3352 }
3353 }
3354 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3355
3356 let point_to_delete: Option<usize> = {
3357 let selected_points: Vec<Selection<Point>> =
3358 self.selections.disjoint_in_range(start..end, cx);
3359
3360 if !add || click_count > 1 {
3361 None
3362 } else if !selected_points.is_empty() {
3363 Some(selected_points[0].id)
3364 } else {
3365 let clicked_point_already_selected =
3366 self.selections.disjoint.iter().find(|selection| {
3367 selection.start.to_point(buffer) == start.to_point(buffer)
3368 || selection.end.to_point(buffer) == end.to_point(buffer)
3369 });
3370
3371 clicked_point_already_selected.map(|selection| selection.id)
3372 }
3373 };
3374
3375 let selections_count = self.selections.count();
3376
3377 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3378 if let Some(point_to_delete) = point_to_delete {
3379 s.delete(point_to_delete);
3380
3381 if selections_count == 1 {
3382 s.set_pending_anchor_range(start..end, mode);
3383 }
3384 } else {
3385 if !add {
3386 s.clear_disjoint();
3387 }
3388
3389 s.set_pending_anchor_range(start..end, mode);
3390 }
3391 });
3392 }
3393
3394 fn begin_columnar_selection(
3395 &mut self,
3396 position: DisplayPoint,
3397 goal_column: u32,
3398 reset: bool,
3399 mode: ColumnarMode,
3400 window: &mut Window,
3401 cx: &mut Context<Self>,
3402 ) {
3403 if !self.focus_handle.is_focused(window) {
3404 self.last_focused_descendant = None;
3405 window.focus(&self.focus_handle);
3406 }
3407
3408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3409
3410 if reset {
3411 let pointer_position = display_map
3412 .buffer_snapshot
3413 .anchor_before(position.to_point(&display_map));
3414
3415 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3416 s.clear_disjoint();
3417 s.set_pending_anchor_range(
3418 pointer_position..pointer_position,
3419 SelectMode::Character,
3420 );
3421 });
3422 };
3423
3424 let tail = self.selections.newest::<Point>(cx).tail();
3425 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3426 self.columnar_selection_state = match mode {
3427 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3428 selection_tail: selection_anchor,
3429 display_point: if reset {
3430 if position.column() != goal_column {
3431 Some(DisplayPoint::new(position.row(), goal_column))
3432 } else {
3433 None
3434 }
3435 } else {
3436 None
3437 },
3438 }),
3439 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3440 selection_tail: selection_anchor,
3441 }),
3442 };
3443
3444 if !reset {
3445 self.select_columns(position, goal_column, &display_map, window, cx);
3446 }
3447 }
3448
3449 fn update_selection(
3450 &mut self,
3451 position: DisplayPoint,
3452 goal_column: u32,
3453 scroll_delta: gpui::Point<f32>,
3454 window: &mut Window,
3455 cx: &mut Context<Self>,
3456 ) {
3457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3458
3459 if self.columnar_selection_state.is_some() {
3460 self.select_columns(position, goal_column, &display_map, window, cx);
3461 } else if let Some(mut pending) = self.selections.pending_anchor() {
3462 let buffer = self.buffer.read(cx).snapshot(cx);
3463 let head;
3464 let tail;
3465 let mode = self.selections.pending_mode().unwrap();
3466 match &mode {
3467 SelectMode::Character => {
3468 head = position.to_point(&display_map);
3469 tail = pending.tail().to_point(&buffer);
3470 }
3471 SelectMode::Word(original_range) => {
3472 let original_display_range = original_range.start.to_display_point(&display_map)
3473 ..original_range.end.to_display_point(&display_map);
3474 let original_buffer_range = original_display_range.start.to_point(&display_map)
3475 ..original_display_range.end.to_point(&display_map);
3476 if movement::is_inside_word(&display_map, position)
3477 || original_display_range.contains(&position)
3478 {
3479 let word_range = movement::surrounding_word(&display_map, position);
3480 if word_range.start < original_display_range.start {
3481 head = word_range.start.to_point(&display_map);
3482 } else {
3483 head = word_range.end.to_point(&display_map);
3484 }
3485 } else {
3486 head = position.to_point(&display_map);
3487 }
3488
3489 if head <= original_buffer_range.start {
3490 tail = original_buffer_range.end;
3491 } else {
3492 tail = original_buffer_range.start;
3493 }
3494 }
3495 SelectMode::Line(original_range) => {
3496 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3497
3498 let position = display_map
3499 .clip_point(position, Bias::Left)
3500 .to_point(&display_map);
3501 let line_start = display_map.prev_line_boundary(position).0;
3502 let next_line_start = buffer.clip_point(
3503 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3504 Bias::Left,
3505 );
3506
3507 if line_start < original_range.start {
3508 head = line_start
3509 } else {
3510 head = next_line_start
3511 }
3512
3513 if head <= original_range.start {
3514 tail = original_range.end;
3515 } else {
3516 tail = original_range.start;
3517 }
3518 }
3519 SelectMode::All => {
3520 return;
3521 }
3522 };
3523
3524 if head < tail {
3525 pending.start = buffer.anchor_before(head);
3526 pending.end = buffer.anchor_before(tail);
3527 pending.reversed = true;
3528 } else {
3529 pending.start = buffer.anchor_before(tail);
3530 pending.end = buffer.anchor_before(head);
3531 pending.reversed = false;
3532 }
3533
3534 self.change_selections(None, window, cx, |s| {
3535 s.set_pending(pending, mode);
3536 });
3537 } else {
3538 log::error!("update_selection dispatched with no pending selection");
3539 return;
3540 }
3541
3542 self.apply_scroll_delta(scroll_delta, window, cx);
3543 cx.notify();
3544 }
3545
3546 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3547 self.columnar_selection_state.take();
3548 if self.selections.pending_anchor().is_some() {
3549 let selections = self.selections.all::<usize>(cx);
3550 self.change_selections(None, window, cx, |s| {
3551 s.select(selections);
3552 s.clear_pending();
3553 });
3554 }
3555 }
3556
3557 fn select_columns(
3558 &mut self,
3559 head: DisplayPoint,
3560 goal_column: u32,
3561 display_map: &DisplaySnapshot,
3562 window: &mut Window,
3563 cx: &mut Context<Self>,
3564 ) {
3565 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3566 return;
3567 };
3568
3569 let tail = match columnar_state {
3570 ColumnarSelectionState::FromMouse {
3571 selection_tail,
3572 display_point,
3573 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3574 ColumnarSelectionState::FromSelection { selection_tail } => {
3575 selection_tail.to_display_point(&display_map)
3576 }
3577 };
3578
3579 let start_row = cmp::min(tail.row(), head.row());
3580 let end_row = cmp::max(tail.row(), head.row());
3581 let start_column = cmp::min(tail.column(), goal_column);
3582 let end_column = cmp::max(tail.column(), goal_column);
3583 let reversed = start_column < tail.column();
3584
3585 let selection_ranges = (start_row.0..=end_row.0)
3586 .map(DisplayRow)
3587 .filter_map(|row| {
3588 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3589 || start_column <= display_map.line_len(row))
3590 && !display_map.is_block_line(row)
3591 {
3592 let start = display_map
3593 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3594 .to_point(display_map);
3595 let end = display_map
3596 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3597 .to_point(display_map);
3598 if reversed {
3599 Some(end..start)
3600 } else {
3601 Some(start..end)
3602 }
3603 } else {
3604 None
3605 }
3606 })
3607 .collect::<Vec<_>>();
3608
3609 let ranges = match columnar_state {
3610 ColumnarSelectionState::FromMouse { .. } => {
3611 let mut non_empty_ranges = selection_ranges
3612 .iter()
3613 .filter(|selection_range| selection_range.start != selection_range.end)
3614 .peekable();
3615 if non_empty_ranges.peek().is_some() {
3616 non_empty_ranges.cloned().collect()
3617 } else {
3618 selection_ranges
3619 }
3620 }
3621 _ => selection_ranges,
3622 };
3623
3624 self.change_selections(None, window, cx, |s| {
3625 s.select_ranges(ranges);
3626 });
3627 cx.notify();
3628 }
3629
3630 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3631 self.selections
3632 .all_adjusted(cx)
3633 .iter()
3634 .any(|selection| !selection.is_empty())
3635 }
3636
3637 pub fn has_pending_nonempty_selection(&self) -> bool {
3638 let pending_nonempty_selection = match self.selections.pending_anchor() {
3639 Some(Selection { start, end, .. }) => start != end,
3640 None => false,
3641 };
3642
3643 pending_nonempty_selection
3644 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3645 }
3646
3647 pub fn has_pending_selection(&self) -> bool {
3648 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3649 }
3650
3651 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3652 self.selection_mark_mode = false;
3653 self.selection_drag_state = SelectionDragState::None;
3654
3655 if self.clear_expanded_diff_hunks(cx) {
3656 cx.notify();
3657 return;
3658 }
3659 if self.dismiss_menus_and_popups(true, window, cx) {
3660 return;
3661 }
3662
3663 if self.mode.is_full()
3664 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3665 {
3666 return;
3667 }
3668
3669 cx.propagate();
3670 }
3671
3672 pub fn dismiss_menus_and_popups(
3673 &mut self,
3674 is_user_requested: bool,
3675 window: &mut Window,
3676 cx: &mut Context<Self>,
3677 ) -> bool {
3678 if self.take_rename(false, window, cx).is_some() {
3679 return true;
3680 }
3681
3682 if hide_hover(self, cx) {
3683 return true;
3684 }
3685
3686 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3687 return true;
3688 }
3689
3690 if self.hide_context_menu(window, cx).is_some() {
3691 return true;
3692 }
3693
3694 if self.mouse_context_menu.take().is_some() {
3695 return true;
3696 }
3697
3698 if is_user_requested && self.discard_inline_completion(true, cx) {
3699 return true;
3700 }
3701
3702 if self.snippet_stack.pop().is_some() {
3703 return true;
3704 }
3705
3706 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3707 self.dismiss_diagnostics(cx);
3708 return true;
3709 }
3710
3711 false
3712 }
3713
3714 fn linked_editing_ranges_for(
3715 &self,
3716 selection: Range<text::Anchor>,
3717 cx: &App,
3718 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3719 if self.linked_edit_ranges.is_empty() {
3720 return None;
3721 }
3722 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3723 selection.end.buffer_id.and_then(|end_buffer_id| {
3724 if selection.start.buffer_id != Some(end_buffer_id) {
3725 return None;
3726 }
3727 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3728 let snapshot = buffer.read(cx).snapshot();
3729 self.linked_edit_ranges
3730 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3731 .map(|ranges| (ranges, snapshot, buffer))
3732 })?;
3733 use text::ToOffset as TO;
3734 // find offset from the start of current range to current cursor position
3735 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3736
3737 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3738 let start_difference = start_offset - start_byte_offset;
3739 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3740 let end_difference = end_offset - start_byte_offset;
3741 // Current range has associated linked ranges.
3742 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3743 for range in linked_ranges.iter() {
3744 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3745 let end_offset = start_offset + end_difference;
3746 let start_offset = start_offset + start_difference;
3747 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3748 continue;
3749 }
3750 if self.selections.disjoint_anchor_ranges().any(|s| {
3751 if s.start.buffer_id != selection.start.buffer_id
3752 || s.end.buffer_id != selection.end.buffer_id
3753 {
3754 return false;
3755 }
3756 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3757 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3758 }) {
3759 continue;
3760 }
3761 let start = buffer_snapshot.anchor_after(start_offset);
3762 let end = buffer_snapshot.anchor_after(end_offset);
3763 linked_edits
3764 .entry(buffer.clone())
3765 .or_default()
3766 .push(start..end);
3767 }
3768 Some(linked_edits)
3769 }
3770
3771 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3772 let text: Arc<str> = text.into();
3773
3774 if self.read_only(cx) {
3775 return;
3776 }
3777
3778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3779
3780 let selections = self.selections.all_adjusted(cx);
3781 let mut bracket_inserted = false;
3782 let mut edits = Vec::new();
3783 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3784 let mut new_selections = Vec::with_capacity(selections.len());
3785 let mut new_autoclose_regions = Vec::new();
3786 let snapshot = self.buffer.read(cx).read(cx);
3787 let mut clear_linked_edit_ranges = false;
3788
3789 for (selection, autoclose_region) in
3790 self.selections_with_autoclose_regions(selections, &snapshot)
3791 {
3792 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3793 // Determine if the inserted text matches the opening or closing
3794 // bracket of any of this language's bracket pairs.
3795 let mut bracket_pair = None;
3796 let mut is_bracket_pair_start = false;
3797 let mut is_bracket_pair_end = false;
3798 if !text.is_empty() {
3799 let mut bracket_pair_matching_end = None;
3800 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3801 // and they are removing the character that triggered IME popup.
3802 for (pair, enabled) in scope.brackets() {
3803 if !pair.close && !pair.surround {
3804 continue;
3805 }
3806
3807 if enabled && pair.start.ends_with(text.as_ref()) {
3808 let prefix_len = pair.start.len() - text.len();
3809 let preceding_text_matches_prefix = prefix_len == 0
3810 || (selection.start.column >= (prefix_len as u32)
3811 && snapshot.contains_str_at(
3812 Point::new(
3813 selection.start.row,
3814 selection.start.column - (prefix_len as u32),
3815 ),
3816 &pair.start[..prefix_len],
3817 ));
3818 if preceding_text_matches_prefix {
3819 bracket_pair = Some(pair.clone());
3820 is_bracket_pair_start = true;
3821 break;
3822 }
3823 }
3824 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3825 {
3826 // take first bracket pair matching end, but don't break in case a later bracket
3827 // pair matches start
3828 bracket_pair_matching_end = Some(pair.clone());
3829 }
3830 }
3831 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3832 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3833 is_bracket_pair_end = true;
3834 }
3835 }
3836
3837 if let Some(bracket_pair) = bracket_pair {
3838 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3839 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3840 let auto_surround =
3841 self.use_auto_surround && snapshot_settings.use_auto_surround;
3842 if selection.is_empty() {
3843 if is_bracket_pair_start {
3844 // If the inserted text is a suffix of an opening bracket and the
3845 // selection is preceded by the rest of the opening bracket, then
3846 // insert the closing bracket.
3847 let following_text_allows_autoclose = snapshot
3848 .chars_at(selection.start)
3849 .next()
3850 .map_or(true, |c| scope.should_autoclose_before(c));
3851
3852 let preceding_text_allows_autoclose = selection.start.column == 0
3853 || snapshot.reversed_chars_at(selection.start).next().map_or(
3854 true,
3855 |c| {
3856 bracket_pair.start != bracket_pair.end
3857 || !snapshot
3858 .char_classifier_at(selection.start)
3859 .is_word(c)
3860 },
3861 );
3862
3863 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3864 && bracket_pair.start.len() == 1
3865 {
3866 let target = bracket_pair.start.chars().next().unwrap();
3867 let current_line_count = snapshot
3868 .reversed_chars_at(selection.start)
3869 .take_while(|&c| c != '\n')
3870 .filter(|&c| c == target)
3871 .count();
3872 current_line_count % 2 == 1
3873 } else {
3874 false
3875 };
3876
3877 if autoclose
3878 && bracket_pair.close
3879 && following_text_allows_autoclose
3880 && preceding_text_allows_autoclose
3881 && !is_closing_quote
3882 {
3883 let anchor = snapshot.anchor_before(selection.end);
3884 new_selections.push((selection.map(|_| anchor), text.len()));
3885 new_autoclose_regions.push((
3886 anchor,
3887 text.len(),
3888 selection.id,
3889 bracket_pair.clone(),
3890 ));
3891 edits.push((
3892 selection.range(),
3893 format!("{}{}", text, bracket_pair.end).into(),
3894 ));
3895 bracket_inserted = true;
3896 continue;
3897 }
3898 }
3899
3900 if let Some(region) = autoclose_region {
3901 // If the selection is followed by an auto-inserted closing bracket,
3902 // then don't insert that closing bracket again; just move the selection
3903 // past the closing bracket.
3904 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3905 && text.as_ref() == region.pair.end.as_str();
3906 if should_skip {
3907 let anchor = snapshot.anchor_after(selection.end);
3908 new_selections
3909 .push((selection.map(|_| anchor), region.pair.end.len()));
3910 continue;
3911 }
3912 }
3913
3914 let always_treat_brackets_as_autoclosed = snapshot
3915 .language_settings_at(selection.start, cx)
3916 .always_treat_brackets_as_autoclosed;
3917 if always_treat_brackets_as_autoclosed
3918 && is_bracket_pair_end
3919 && snapshot.contains_str_at(selection.end, text.as_ref())
3920 {
3921 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3922 // and the inserted text is a closing bracket and the selection is followed
3923 // by the closing bracket then move the selection past the closing bracket.
3924 let anchor = snapshot.anchor_after(selection.end);
3925 new_selections.push((selection.map(|_| anchor), text.len()));
3926 continue;
3927 }
3928 }
3929 // If an opening bracket is 1 character long and is typed while
3930 // text is selected, then surround that text with the bracket pair.
3931 else if auto_surround
3932 && bracket_pair.surround
3933 && is_bracket_pair_start
3934 && bracket_pair.start.chars().count() == 1
3935 {
3936 edits.push((selection.start..selection.start, text.clone()));
3937 edits.push((
3938 selection.end..selection.end,
3939 bracket_pair.end.as_str().into(),
3940 ));
3941 bracket_inserted = true;
3942 new_selections.push((
3943 Selection {
3944 id: selection.id,
3945 start: snapshot.anchor_after(selection.start),
3946 end: snapshot.anchor_before(selection.end),
3947 reversed: selection.reversed,
3948 goal: selection.goal,
3949 },
3950 0,
3951 ));
3952 continue;
3953 }
3954 }
3955 }
3956
3957 if self.auto_replace_emoji_shortcode
3958 && selection.is_empty()
3959 && text.as_ref().ends_with(':')
3960 {
3961 if let Some(possible_emoji_short_code) =
3962 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3963 {
3964 if !possible_emoji_short_code.is_empty() {
3965 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3966 let emoji_shortcode_start = Point::new(
3967 selection.start.row,
3968 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3969 );
3970
3971 // Remove shortcode from buffer
3972 edits.push((
3973 emoji_shortcode_start..selection.start,
3974 "".to_string().into(),
3975 ));
3976 new_selections.push((
3977 Selection {
3978 id: selection.id,
3979 start: snapshot.anchor_after(emoji_shortcode_start),
3980 end: snapshot.anchor_before(selection.start),
3981 reversed: selection.reversed,
3982 goal: selection.goal,
3983 },
3984 0,
3985 ));
3986
3987 // Insert emoji
3988 let selection_start_anchor = snapshot.anchor_after(selection.start);
3989 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3990 edits.push((selection.start..selection.end, emoji.to_string().into()));
3991
3992 continue;
3993 }
3994 }
3995 }
3996 }
3997
3998 // If not handling any auto-close operation, then just replace the selected
3999 // text with the given input and move the selection to the end of the
4000 // newly inserted text.
4001 let anchor = snapshot.anchor_after(selection.end);
4002 if !self.linked_edit_ranges.is_empty() {
4003 let start_anchor = snapshot.anchor_before(selection.start);
4004
4005 let is_word_char = text.chars().next().map_or(true, |char| {
4006 let classifier = snapshot
4007 .char_classifier_at(start_anchor.to_offset(&snapshot))
4008 .ignore_punctuation(true);
4009 classifier.is_word(char)
4010 });
4011
4012 if is_word_char {
4013 if let Some(ranges) = self
4014 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4015 {
4016 for (buffer, edits) in ranges {
4017 linked_edits
4018 .entry(buffer.clone())
4019 .or_default()
4020 .extend(edits.into_iter().map(|range| (range, text.clone())));
4021 }
4022 }
4023 } else {
4024 clear_linked_edit_ranges = true;
4025 }
4026 }
4027
4028 new_selections.push((selection.map(|_| anchor), 0));
4029 edits.push((selection.start..selection.end, text.clone()));
4030 }
4031
4032 drop(snapshot);
4033
4034 self.transact(window, cx, |this, window, cx| {
4035 if clear_linked_edit_ranges {
4036 this.linked_edit_ranges.clear();
4037 }
4038 let initial_buffer_versions =
4039 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4040
4041 this.buffer.update(cx, |buffer, cx| {
4042 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4043 });
4044 for (buffer, edits) in linked_edits {
4045 buffer.update(cx, |buffer, cx| {
4046 let snapshot = buffer.snapshot();
4047 let edits = edits
4048 .into_iter()
4049 .map(|(range, text)| {
4050 use text::ToPoint as TP;
4051 let end_point = TP::to_point(&range.end, &snapshot);
4052 let start_point = TP::to_point(&range.start, &snapshot);
4053 (start_point..end_point, text)
4054 })
4055 .sorted_by_key(|(range, _)| range.start);
4056 buffer.edit(edits, None, cx);
4057 })
4058 }
4059 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4060 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4061 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4062 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4063 .zip(new_selection_deltas)
4064 .map(|(selection, delta)| Selection {
4065 id: selection.id,
4066 start: selection.start + delta,
4067 end: selection.end + delta,
4068 reversed: selection.reversed,
4069 goal: SelectionGoal::None,
4070 })
4071 .collect::<Vec<_>>();
4072
4073 let mut i = 0;
4074 for (position, delta, selection_id, pair) in new_autoclose_regions {
4075 let position = position.to_offset(&map.buffer_snapshot) + delta;
4076 let start = map.buffer_snapshot.anchor_before(position);
4077 let end = map.buffer_snapshot.anchor_after(position);
4078 while let Some(existing_state) = this.autoclose_regions.get(i) {
4079 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4080 Ordering::Less => i += 1,
4081 Ordering::Greater => break,
4082 Ordering::Equal => {
4083 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4084 Ordering::Less => i += 1,
4085 Ordering::Equal => break,
4086 Ordering::Greater => break,
4087 }
4088 }
4089 }
4090 }
4091 this.autoclose_regions.insert(
4092 i,
4093 AutocloseRegion {
4094 selection_id,
4095 range: start..end,
4096 pair,
4097 },
4098 );
4099 }
4100
4101 let had_active_inline_completion = this.has_active_inline_completion();
4102 this.change_selections(
4103 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4104 window,
4105 cx,
4106 |s| s.select(new_selections),
4107 );
4108
4109 if !bracket_inserted {
4110 if let Some(on_type_format_task) =
4111 this.trigger_on_type_formatting(text.to_string(), window, cx)
4112 {
4113 on_type_format_task.detach_and_log_err(cx);
4114 }
4115 }
4116
4117 let editor_settings = EditorSettings::get_global(cx);
4118 if bracket_inserted
4119 && (editor_settings.auto_signature_help
4120 || editor_settings.show_signature_help_after_edits)
4121 {
4122 this.show_signature_help(&ShowSignatureHelp, window, cx);
4123 }
4124
4125 let trigger_in_words =
4126 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4127 if this.hard_wrap.is_some() {
4128 let latest: Range<Point> = this.selections.newest(cx).range();
4129 if latest.is_empty()
4130 && this
4131 .buffer()
4132 .read(cx)
4133 .snapshot(cx)
4134 .line_len(MultiBufferRow(latest.start.row))
4135 == latest.start.column
4136 {
4137 this.rewrap_impl(
4138 RewrapOptions {
4139 override_language_settings: true,
4140 preserve_existing_whitespace: true,
4141 },
4142 cx,
4143 )
4144 }
4145 }
4146 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4147 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4148 this.refresh_inline_completion(true, false, window, cx);
4149 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4150 });
4151 }
4152
4153 fn find_possible_emoji_shortcode_at_position(
4154 snapshot: &MultiBufferSnapshot,
4155 position: Point,
4156 ) -> Option<String> {
4157 let mut chars = Vec::new();
4158 let mut found_colon = false;
4159 for char in snapshot.reversed_chars_at(position).take(100) {
4160 // Found a possible emoji shortcode in the middle of the buffer
4161 if found_colon {
4162 if char.is_whitespace() {
4163 chars.reverse();
4164 return Some(chars.iter().collect());
4165 }
4166 // If the previous character is not a whitespace, we are in the middle of a word
4167 // and we only want to complete the shortcode if the word is made up of other emojis
4168 let mut containing_word = String::new();
4169 for ch in snapshot
4170 .reversed_chars_at(position)
4171 .skip(chars.len() + 1)
4172 .take(100)
4173 {
4174 if ch.is_whitespace() {
4175 break;
4176 }
4177 containing_word.push(ch);
4178 }
4179 let containing_word = containing_word.chars().rev().collect::<String>();
4180 if util::word_consists_of_emojis(containing_word.as_str()) {
4181 chars.reverse();
4182 return Some(chars.iter().collect());
4183 }
4184 }
4185
4186 if char.is_whitespace() || !char.is_ascii() {
4187 return None;
4188 }
4189 if char == ':' {
4190 found_colon = true;
4191 } else {
4192 chars.push(char);
4193 }
4194 }
4195 // Found a possible emoji shortcode at the beginning of the buffer
4196 chars.reverse();
4197 Some(chars.iter().collect())
4198 }
4199
4200 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4202 self.transact(window, cx, |this, window, cx| {
4203 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4204 let selections = this.selections.all::<usize>(cx);
4205 let multi_buffer = this.buffer.read(cx);
4206 let buffer = multi_buffer.snapshot(cx);
4207 selections
4208 .iter()
4209 .map(|selection| {
4210 let start_point = selection.start.to_point(&buffer);
4211 let mut existing_indent =
4212 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4213 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4214 let start = selection.start;
4215 let end = selection.end;
4216 let selection_is_empty = start == end;
4217 let language_scope = buffer.language_scope_at(start);
4218 let (
4219 comment_delimiter,
4220 doc_delimiter,
4221 insert_extra_newline,
4222 indent_on_newline,
4223 indent_on_extra_newline,
4224 ) = if let Some(language) = &language_scope {
4225 let mut insert_extra_newline =
4226 insert_extra_newline_brackets(&buffer, start..end, language)
4227 || insert_extra_newline_tree_sitter(&buffer, start..end);
4228
4229 // Comment extension on newline is allowed only for cursor selections
4230 let comment_delimiter = maybe!({
4231 if !selection_is_empty {
4232 return None;
4233 }
4234
4235 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4236 return None;
4237 }
4238
4239 let delimiters = language.line_comment_prefixes();
4240 let max_len_of_delimiter =
4241 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4242 let (snapshot, range) =
4243 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4244
4245 let num_of_whitespaces = snapshot
4246 .chars_for_range(range.clone())
4247 .take_while(|c| c.is_whitespace())
4248 .count();
4249 let comment_candidate = snapshot
4250 .chars_for_range(range)
4251 .skip(num_of_whitespaces)
4252 .take(max_len_of_delimiter)
4253 .collect::<String>();
4254 let (delimiter, trimmed_len) = delimiters
4255 .iter()
4256 .filter_map(|delimiter| {
4257 let prefix = delimiter.trim_end();
4258 if comment_candidate.starts_with(prefix) {
4259 Some((delimiter, prefix.len()))
4260 } else {
4261 None
4262 }
4263 })
4264 .max_by_key(|(_, len)| *len)?;
4265
4266 let cursor_is_placed_after_comment_marker =
4267 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4268 if cursor_is_placed_after_comment_marker {
4269 Some(delimiter.clone())
4270 } else {
4271 None
4272 }
4273 });
4274
4275 let mut indent_on_newline = IndentSize::spaces(0);
4276 let mut indent_on_extra_newline = IndentSize::spaces(0);
4277
4278 let doc_delimiter = maybe!({
4279 if !selection_is_empty {
4280 return None;
4281 }
4282
4283 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4284 return None;
4285 }
4286
4287 let DocumentationConfig {
4288 start: start_tag,
4289 end: end_tag,
4290 prefix: delimiter,
4291 tab_size: len,
4292 } = language.documentation()?;
4293
4294 let is_within_block_comment = buffer
4295 .language_scope_at(start_point)
4296 .is_some_and(|scope| scope.override_name() == Some("comment"));
4297 if !is_within_block_comment {
4298 return None;
4299 }
4300
4301 let (snapshot, range) =
4302 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4303
4304 let num_of_whitespaces = snapshot
4305 .chars_for_range(range.clone())
4306 .take_while(|c| c.is_whitespace())
4307 .count();
4308
4309 // 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.
4310 let column = start_point.column;
4311 let cursor_is_after_start_tag = {
4312 let start_tag_len = start_tag.len();
4313 let start_tag_line = snapshot
4314 .chars_for_range(range.clone())
4315 .skip(num_of_whitespaces)
4316 .take(start_tag_len)
4317 .collect::<String>();
4318 if start_tag_line.starts_with(start_tag.as_ref()) {
4319 num_of_whitespaces + start_tag_len <= column as usize
4320 } else {
4321 false
4322 }
4323 };
4324
4325 let cursor_is_after_delimiter = {
4326 let delimiter_trim = delimiter.trim_end();
4327 let delimiter_line = snapshot
4328 .chars_for_range(range.clone())
4329 .skip(num_of_whitespaces)
4330 .take(delimiter_trim.len())
4331 .collect::<String>();
4332 if delimiter_line.starts_with(delimiter_trim) {
4333 num_of_whitespaces + delimiter_trim.len() <= column as usize
4334 } else {
4335 false
4336 }
4337 };
4338
4339 let cursor_is_before_end_tag_if_exists = {
4340 let mut char_position = 0u32;
4341 let mut end_tag_offset = None;
4342
4343 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4344 if let Some(byte_pos) = chunk.find(&**end_tag) {
4345 let chars_before_match =
4346 chunk[..byte_pos].chars().count() as u32;
4347 end_tag_offset =
4348 Some(char_position + chars_before_match);
4349 break 'outer;
4350 }
4351 char_position += chunk.chars().count() as u32;
4352 }
4353
4354 if let Some(end_tag_offset) = end_tag_offset {
4355 let cursor_is_before_end_tag = column <= end_tag_offset;
4356 if cursor_is_after_start_tag {
4357 if cursor_is_before_end_tag {
4358 insert_extra_newline = true;
4359 }
4360 let cursor_is_at_start_of_end_tag =
4361 column == end_tag_offset;
4362 if cursor_is_at_start_of_end_tag {
4363 indent_on_extra_newline.len = (*len).into();
4364 }
4365 }
4366 cursor_is_before_end_tag
4367 } else {
4368 true
4369 }
4370 };
4371
4372 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4373 && cursor_is_before_end_tag_if_exists
4374 {
4375 if cursor_is_after_start_tag {
4376 indent_on_newline.len = (*len).into();
4377 }
4378 Some(delimiter.clone())
4379 } else {
4380 None
4381 }
4382 });
4383
4384 (
4385 comment_delimiter,
4386 doc_delimiter,
4387 insert_extra_newline,
4388 indent_on_newline,
4389 indent_on_extra_newline,
4390 )
4391 } else {
4392 (
4393 None,
4394 None,
4395 false,
4396 IndentSize::default(),
4397 IndentSize::default(),
4398 )
4399 };
4400
4401 let prevent_auto_indent = doc_delimiter.is_some();
4402 let delimiter = comment_delimiter.or(doc_delimiter);
4403
4404 let capacity_for_delimiter =
4405 delimiter.as_deref().map(str::len).unwrap_or_default();
4406 let mut new_text = String::with_capacity(
4407 1 + capacity_for_delimiter
4408 + existing_indent.len as usize
4409 + indent_on_newline.len as usize
4410 + indent_on_extra_newline.len as usize,
4411 );
4412 new_text.push('\n');
4413 new_text.extend(existing_indent.chars());
4414 new_text.extend(indent_on_newline.chars());
4415
4416 if let Some(delimiter) = &delimiter {
4417 new_text.push_str(delimiter);
4418 }
4419
4420 if insert_extra_newline {
4421 new_text.push('\n');
4422 new_text.extend(existing_indent.chars());
4423 new_text.extend(indent_on_extra_newline.chars());
4424 }
4425
4426 let anchor = buffer.anchor_after(end);
4427 let new_selection = selection.map(|_| anchor);
4428 (
4429 ((start..end, new_text), prevent_auto_indent),
4430 (insert_extra_newline, new_selection),
4431 )
4432 })
4433 .unzip()
4434 };
4435
4436 let mut auto_indent_edits = Vec::new();
4437 let mut edits = Vec::new();
4438 for (edit, prevent_auto_indent) in edits_with_flags {
4439 if prevent_auto_indent {
4440 edits.push(edit);
4441 } else {
4442 auto_indent_edits.push(edit);
4443 }
4444 }
4445 if !edits.is_empty() {
4446 this.edit(edits, cx);
4447 }
4448 if !auto_indent_edits.is_empty() {
4449 this.edit_with_autoindent(auto_indent_edits, cx);
4450 }
4451
4452 let buffer = this.buffer.read(cx).snapshot(cx);
4453 let new_selections = selection_info
4454 .into_iter()
4455 .map(|(extra_newline_inserted, new_selection)| {
4456 let mut cursor = new_selection.end.to_point(&buffer);
4457 if extra_newline_inserted {
4458 cursor.row -= 1;
4459 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4460 }
4461 new_selection.map(|_| cursor)
4462 })
4463 .collect();
4464
4465 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4466 s.select(new_selections)
4467 });
4468 this.refresh_inline_completion(true, false, window, cx);
4469 });
4470 }
4471
4472 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4474
4475 let buffer = self.buffer.read(cx);
4476 let snapshot = buffer.snapshot(cx);
4477
4478 let mut edits = Vec::new();
4479 let mut rows = Vec::new();
4480
4481 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4482 let cursor = selection.head();
4483 let row = cursor.row;
4484
4485 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4486
4487 let newline = "\n".to_string();
4488 edits.push((start_of_line..start_of_line, newline));
4489
4490 rows.push(row + rows_inserted as u32);
4491 }
4492
4493 self.transact(window, cx, |editor, window, cx| {
4494 editor.edit(edits, cx);
4495
4496 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4497 let mut index = 0;
4498 s.move_cursors_with(|map, _, _| {
4499 let row = rows[index];
4500 index += 1;
4501
4502 let point = Point::new(row, 0);
4503 let boundary = map.next_line_boundary(point).1;
4504 let clipped = map.clip_point(boundary, Bias::Left);
4505
4506 (clipped, SelectionGoal::None)
4507 });
4508 });
4509
4510 let mut indent_edits = Vec::new();
4511 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4512 for row in rows {
4513 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4514 for (row, indent) in indents {
4515 if indent.len == 0 {
4516 continue;
4517 }
4518
4519 let text = match indent.kind {
4520 IndentKind::Space => " ".repeat(indent.len as usize),
4521 IndentKind::Tab => "\t".repeat(indent.len as usize),
4522 };
4523 let point = Point::new(row.0, 0);
4524 indent_edits.push((point..point, text));
4525 }
4526 }
4527 editor.edit(indent_edits, cx);
4528 });
4529 }
4530
4531 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4533
4534 let buffer = self.buffer.read(cx);
4535 let snapshot = buffer.snapshot(cx);
4536
4537 let mut edits = Vec::new();
4538 let mut rows = Vec::new();
4539 let mut rows_inserted = 0;
4540
4541 for selection in self.selections.all_adjusted(cx) {
4542 let cursor = selection.head();
4543 let row = cursor.row;
4544
4545 let point = Point::new(row + 1, 0);
4546 let start_of_line = snapshot.clip_point(point, Bias::Left);
4547
4548 let newline = "\n".to_string();
4549 edits.push((start_of_line..start_of_line, newline));
4550
4551 rows_inserted += 1;
4552 rows.push(row + rows_inserted);
4553 }
4554
4555 self.transact(window, cx, |editor, window, cx| {
4556 editor.edit(edits, cx);
4557
4558 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4559 let mut index = 0;
4560 s.move_cursors_with(|map, _, _| {
4561 let row = rows[index];
4562 index += 1;
4563
4564 let point = Point::new(row, 0);
4565 let boundary = map.next_line_boundary(point).1;
4566 let clipped = map.clip_point(boundary, Bias::Left);
4567
4568 (clipped, SelectionGoal::None)
4569 });
4570 });
4571
4572 let mut indent_edits = Vec::new();
4573 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4574 for row in rows {
4575 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4576 for (row, indent) in indents {
4577 if indent.len == 0 {
4578 continue;
4579 }
4580
4581 let text = match indent.kind {
4582 IndentKind::Space => " ".repeat(indent.len as usize),
4583 IndentKind::Tab => "\t".repeat(indent.len as usize),
4584 };
4585 let point = Point::new(row.0, 0);
4586 indent_edits.push((point..point, text));
4587 }
4588 }
4589 editor.edit(indent_edits, cx);
4590 });
4591 }
4592
4593 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4594 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4595 original_indent_columns: Vec::new(),
4596 });
4597 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4598 }
4599
4600 fn insert_with_autoindent_mode(
4601 &mut self,
4602 text: &str,
4603 autoindent_mode: Option<AutoindentMode>,
4604 window: &mut Window,
4605 cx: &mut Context<Self>,
4606 ) {
4607 if self.read_only(cx) {
4608 return;
4609 }
4610
4611 let text: Arc<str> = text.into();
4612 self.transact(window, cx, |this, window, cx| {
4613 let old_selections = this.selections.all_adjusted(cx);
4614 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4615 let anchors = {
4616 let snapshot = buffer.read(cx);
4617 old_selections
4618 .iter()
4619 .map(|s| {
4620 let anchor = snapshot.anchor_after(s.head());
4621 s.map(|_| anchor)
4622 })
4623 .collect::<Vec<_>>()
4624 };
4625 buffer.edit(
4626 old_selections
4627 .iter()
4628 .map(|s| (s.start..s.end, text.clone())),
4629 autoindent_mode,
4630 cx,
4631 );
4632 anchors
4633 });
4634
4635 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4636 s.select_anchors(selection_anchors);
4637 });
4638
4639 cx.notify();
4640 });
4641 }
4642
4643 fn trigger_completion_on_input(
4644 &mut self,
4645 text: &str,
4646 trigger_in_words: bool,
4647 window: &mut Window,
4648 cx: &mut Context<Self>,
4649 ) {
4650 let completions_source = self
4651 .context_menu
4652 .borrow()
4653 .as_ref()
4654 .and_then(|menu| match menu {
4655 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4656 CodeContextMenu::CodeActions(_) => None,
4657 });
4658
4659 match completions_source {
4660 Some(CompletionsMenuSource::Words) => {
4661 self.show_word_completions(&ShowWordCompletions, window, cx)
4662 }
4663 Some(CompletionsMenuSource::Normal)
4664 | Some(CompletionsMenuSource::SnippetChoices)
4665 | None
4666 if self.is_completion_trigger(
4667 text,
4668 trigger_in_words,
4669 completions_source.is_some(),
4670 cx,
4671 ) =>
4672 {
4673 self.show_completions(
4674 &ShowCompletions {
4675 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4676 },
4677 window,
4678 cx,
4679 )
4680 }
4681 _ => {
4682 self.hide_context_menu(window, cx);
4683 }
4684 }
4685 }
4686
4687 fn is_completion_trigger(
4688 &self,
4689 text: &str,
4690 trigger_in_words: bool,
4691 menu_is_open: bool,
4692 cx: &mut Context<Self>,
4693 ) -> bool {
4694 let position = self.selections.newest_anchor().head();
4695 let multibuffer = self.buffer.read(cx);
4696 let Some(buffer) = position
4697 .buffer_id
4698 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4699 else {
4700 return false;
4701 };
4702
4703 if let Some(completion_provider) = &self.completion_provider {
4704 completion_provider.is_completion_trigger(
4705 &buffer,
4706 position.text_anchor,
4707 text,
4708 trigger_in_words,
4709 menu_is_open,
4710 cx,
4711 )
4712 } else {
4713 false
4714 }
4715 }
4716
4717 /// If any empty selections is touching the start of its innermost containing autoclose
4718 /// region, expand it to select the brackets.
4719 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4720 let selections = self.selections.all::<usize>(cx);
4721 let buffer = self.buffer.read(cx).read(cx);
4722 let new_selections = self
4723 .selections_with_autoclose_regions(selections, &buffer)
4724 .map(|(mut selection, region)| {
4725 if !selection.is_empty() {
4726 return selection;
4727 }
4728
4729 if let Some(region) = region {
4730 let mut range = region.range.to_offset(&buffer);
4731 if selection.start == range.start && range.start >= region.pair.start.len() {
4732 range.start -= region.pair.start.len();
4733 if buffer.contains_str_at(range.start, ®ion.pair.start)
4734 && buffer.contains_str_at(range.end, ®ion.pair.end)
4735 {
4736 range.end += region.pair.end.len();
4737 selection.start = range.start;
4738 selection.end = range.end;
4739
4740 return selection;
4741 }
4742 }
4743 }
4744
4745 let always_treat_brackets_as_autoclosed = buffer
4746 .language_settings_at(selection.start, cx)
4747 .always_treat_brackets_as_autoclosed;
4748
4749 if !always_treat_brackets_as_autoclosed {
4750 return selection;
4751 }
4752
4753 if let Some(scope) = buffer.language_scope_at(selection.start) {
4754 for (pair, enabled) in scope.brackets() {
4755 if !enabled || !pair.close {
4756 continue;
4757 }
4758
4759 if buffer.contains_str_at(selection.start, &pair.end) {
4760 let pair_start_len = pair.start.len();
4761 if buffer.contains_str_at(
4762 selection.start.saturating_sub(pair_start_len),
4763 &pair.start,
4764 ) {
4765 selection.start -= pair_start_len;
4766 selection.end += pair.end.len();
4767
4768 return selection;
4769 }
4770 }
4771 }
4772 }
4773
4774 selection
4775 })
4776 .collect();
4777
4778 drop(buffer);
4779 self.change_selections(None, window, cx, |selections| {
4780 selections.select(new_selections)
4781 });
4782 }
4783
4784 /// Iterate the given selections, and for each one, find the smallest surrounding
4785 /// autoclose region. This uses the ordering of the selections and the autoclose
4786 /// regions to avoid repeated comparisons.
4787 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4788 &'a self,
4789 selections: impl IntoIterator<Item = Selection<D>>,
4790 buffer: &'a MultiBufferSnapshot,
4791 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4792 let mut i = 0;
4793 let mut regions = self.autoclose_regions.as_slice();
4794 selections.into_iter().map(move |selection| {
4795 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4796
4797 let mut enclosing = None;
4798 while let Some(pair_state) = regions.get(i) {
4799 if pair_state.range.end.to_offset(buffer) < range.start {
4800 regions = ®ions[i + 1..];
4801 i = 0;
4802 } else if pair_state.range.start.to_offset(buffer) > range.end {
4803 break;
4804 } else {
4805 if pair_state.selection_id == selection.id {
4806 enclosing = Some(pair_state);
4807 }
4808 i += 1;
4809 }
4810 }
4811
4812 (selection, enclosing)
4813 })
4814 }
4815
4816 /// Remove any autoclose regions that no longer contain their selection.
4817 fn invalidate_autoclose_regions(
4818 &mut self,
4819 mut selections: &[Selection<Anchor>],
4820 buffer: &MultiBufferSnapshot,
4821 ) {
4822 self.autoclose_regions.retain(|state| {
4823 let mut i = 0;
4824 while let Some(selection) = selections.get(i) {
4825 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4826 selections = &selections[1..];
4827 continue;
4828 }
4829 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4830 break;
4831 }
4832 if selection.id == state.selection_id {
4833 return true;
4834 } else {
4835 i += 1;
4836 }
4837 }
4838 false
4839 });
4840 }
4841
4842 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4843 let offset = position.to_offset(buffer);
4844 let (word_range, kind) = buffer.surrounding_word(offset, true);
4845 if offset > word_range.start && kind == Some(CharKind::Word) {
4846 Some(
4847 buffer
4848 .text_for_range(word_range.start..offset)
4849 .collect::<String>(),
4850 )
4851 } else {
4852 None
4853 }
4854 }
4855
4856 pub fn toggle_inline_values(
4857 &mut self,
4858 _: &ToggleInlineValues,
4859 _: &mut Window,
4860 cx: &mut Context<Self>,
4861 ) {
4862 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4863
4864 self.refresh_inline_values(cx);
4865 }
4866
4867 pub fn toggle_inlay_hints(
4868 &mut self,
4869 _: &ToggleInlayHints,
4870 _: &mut Window,
4871 cx: &mut Context<Self>,
4872 ) {
4873 self.refresh_inlay_hints(
4874 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4875 cx,
4876 );
4877 }
4878
4879 pub fn inlay_hints_enabled(&self) -> bool {
4880 self.inlay_hint_cache.enabled
4881 }
4882
4883 pub fn inline_values_enabled(&self) -> bool {
4884 self.inline_value_cache.enabled
4885 }
4886
4887 #[cfg(any(test, feature = "test-support"))]
4888 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4889 self.display_map
4890 .read(cx)
4891 .current_inlays()
4892 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4893 .cloned()
4894 .collect()
4895 }
4896
4897 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4898 if self.semantics_provider.is_none() || !self.mode.is_full() {
4899 return;
4900 }
4901
4902 let reason_description = reason.description();
4903 let ignore_debounce = matches!(
4904 reason,
4905 InlayHintRefreshReason::SettingsChange(_)
4906 | InlayHintRefreshReason::Toggle(_)
4907 | InlayHintRefreshReason::ExcerptsRemoved(_)
4908 | InlayHintRefreshReason::ModifiersChanged(_)
4909 );
4910 let (invalidate_cache, required_languages) = match reason {
4911 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4912 match self.inlay_hint_cache.modifiers_override(enabled) {
4913 Some(enabled) => {
4914 if enabled {
4915 (InvalidationStrategy::RefreshRequested, None)
4916 } else {
4917 self.splice_inlays(
4918 &self
4919 .visible_inlay_hints(cx)
4920 .iter()
4921 .map(|inlay| inlay.id)
4922 .collect::<Vec<InlayId>>(),
4923 Vec::new(),
4924 cx,
4925 );
4926 return;
4927 }
4928 }
4929 None => return,
4930 }
4931 }
4932 InlayHintRefreshReason::Toggle(enabled) => {
4933 if self.inlay_hint_cache.toggle(enabled) {
4934 if enabled {
4935 (InvalidationStrategy::RefreshRequested, None)
4936 } else {
4937 self.splice_inlays(
4938 &self
4939 .visible_inlay_hints(cx)
4940 .iter()
4941 .map(|inlay| inlay.id)
4942 .collect::<Vec<InlayId>>(),
4943 Vec::new(),
4944 cx,
4945 );
4946 return;
4947 }
4948 } else {
4949 return;
4950 }
4951 }
4952 InlayHintRefreshReason::SettingsChange(new_settings) => {
4953 match self.inlay_hint_cache.update_settings(
4954 &self.buffer,
4955 new_settings,
4956 self.visible_inlay_hints(cx),
4957 cx,
4958 ) {
4959 ControlFlow::Break(Some(InlaySplice {
4960 to_remove,
4961 to_insert,
4962 })) => {
4963 self.splice_inlays(&to_remove, to_insert, cx);
4964 return;
4965 }
4966 ControlFlow::Break(None) => return,
4967 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4968 }
4969 }
4970 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4971 if let Some(InlaySplice {
4972 to_remove,
4973 to_insert,
4974 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4975 {
4976 self.splice_inlays(&to_remove, to_insert, cx);
4977 }
4978 self.display_map.update(cx, |display_map, _| {
4979 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4980 });
4981 return;
4982 }
4983 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4984 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4985 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4986 }
4987 InlayHintRefreshReason::RefreshRequested => {
4988 (InvalidationStrategy::RefreshRequested, None)
4989 }
4990 };
4991
4992 if let Some(InlaySplice {
4993 to_remove,
4994 to_insert,
4995 }) = self.inlay_hint_cache.spawn_hint_refresh(
4996 reason_description,
4997 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4998 invalidate_cache,
4999 ignore_debounce,
5000 cx,
5001 ) {
5002 self.splice_inlays(&to_remove, to_insert, cx);
5003 }
5004 }
5005
5006 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5007 self.display_map
5008 .read(cx)
5009 .current_inlays()
5010 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5011 .cloned()
5012 .collect()
5013 }
5014
5015 pub fn excerpts_for_inlay_hints_query(
5016 &self,
5017 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5018 cx: &mut Context<Editor>,
5019 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5020 let Some(project) = self.project.as_ref() else {
5021 return HashMap::default();
5022 };
5023 let project = project.read(cx);
5024 let multi_buffer = self.buffer().read(cx);
5025 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5026 let multi_buffer_visible_start = self
5027 .scroll_manager
5028 .anchor()
5029 .anchor
5030 .to_point(&multi_buffer_snapshot);
5031 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5032 multi_buffer_visible_start
5033 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5034 Bias::Left,
5035 );
5036 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5037 multi_buffer_snapshot
5038 .range_to_buffer_ranges(multi_buffer_visible_range)
5039 .into_iter()
5040 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5041 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5042 let buffer_file = project::File::from_dyn(buffer.file())?;
5043 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5044 let worktree_entry = buffer_worktree
5045 .read(cx)
5046 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5047 if worktree_entry.is_ignored {
5048 return None;
5049 }
5050
5051 let language = buffer.language()?;
5052 if let Some(restrict_to_languages) = restrict_to_languages {
5053 if !restrict_to_languages.contains(language) {
5054 return None;
5055 }
5056 }
5057 Some((
5058 excerpt_id,
5059 (
5060 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5061 buffer.version().clone(),
5062 excerpt_visible_range,
5063 ),
5064 ))
5065 })
5066 .collect()
5067 }
5068
5069 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5070 TextLayoutDetails {
5071 text_system: window.text_system().clone(),
5072 editor_style: self.style.clone().unwrap(),
5073 rem_size: window.rem_size(),
5074 scroll_anchor: self.scroll_manager.anchor(),
5075 visible_rows: self.visible_line_count(),
5076 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5077 }
5078 }
5079
5080 pub fn splice_inlays(
5081 &self,
5082 to_remove: &[InlayId],
5083 to_insert: Vec<Inlay>,
5084 cx: &mut Context<Self>,
5085 ) {
5086 self.display_map.update(cx, |display_map, cx| {
5087 display_map.splice_inlays(to_remove, to_insert, cx)
5088 });
5089 cx.notify();
5090 }
5091
5092 fn trigger_on_type_formatting(
5093 &self,
5094 input: String,
5095 window: &mut Window,
5096 cx: &mut Context<Self>,
5097 ) -> Option<Task<Result<()>>> {
5098 if input.len() != 1 {
5099 return None;
5100 }
5101
5102 let project = self.project.as_ref()?;
5103 let position = self.selections.newest_anchor().head();
5104 let (buffer, buffer_position) = self
5105 .buffer
5106 .read(cx)
5107 .text_anchor_for_position(position, cx)?;
5108
5109 let settings = language_settings::language_settings(
5110 buffer
5111 .read(cx)
5112 .language_at(buffer_position)
5113 .map(|l| l.name()),
5114 buffer.read(cx).file(),
5115 cx,
5116 );
5117 if !settings.use_on_type_format {
5118 return None;
5119 }
5120
5121 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5122 // hence we do LSP request & edit on host side only — add formats to host's history.
5123 let push_to_lsp_host_history = true;
5124 // If this is not the host, append its history with new edits.
5125 let push_to_client_history = project.read(cx).is_via_collab();
5126
5127 let on_type_formatting = project.update(cx, |project, cx| {
5128 project.on_type_format(
5129 buffer.clone(),
5130 buffer_position,
5131 input,
5132 push_to_lsp_host_history,
5133 cx,
5134 )
5135 });
5136 Some(cx.spawn_in(window, async move |editor, cx| {
5137 if let Some(transaction) = on_type_formatting.await? {
5138 if push_to_client_history {
5139 buffer
5140 .update(cx, |buffer, _| {
5141 buffer.push_transaction(transaction, Instant::now());
5142 buffer.finalize_last_transaction();
5143 })
5144 .ok();
5145 }
5146 editor.update(cx, |editor, cx| {
5147 editor.refresh_document_highlights(cx);
5148 })?;
5149 }
5150 Ok(())
5151 }))
5152 }
5153
5154 pub fn show_word_completions(
5155 &mut self,
5156 _: &ShowWordCompletions,
5157 window: &mut Window,
5158 cx: &mut Context<Self>,
5159 ) {
5160 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5161 }
5162
5163 pub fn show_completions(
5164 &mut self,
5165 options: &ShowCompletions,
5166 window: &mut Window,
5167 cx: &mut Context<Self>,
5168 ) {
5169 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5170 }
5171
5172 fn open_or_update_completions_menu(
5173 &mut self,
5174 requested_source: Option<CompletionsMenuSource>,
5175 trigger: Option<&str>,
5176 window: &mut Window,
5177 cx: &mut Context<Self>,
5178 ) {
5179 if self.pending_rename.is_some() {
5180 return;
5181 }
5182
5183 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5184
5185 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5186 // inserted and selected. To handle that case, the start of the selection is used so that
5187 // the menu starts with all choices.
5188 let position = self
5189 .selections
5190 .newest_anchor()
5191 .start
5192 .bias_right(&multibuffer_snapshot);
5193 if position.diff_base_anchor.is_some() {
5194 return;
5195 }
5196 let (buffer, buffer_position) =
5197 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5198 output
5199 } else {
5200 return;
5201 };
5202 let buffer_snapshot = buffer.read(cx).snapshot();
5203
5204 let query: Option<Arc<String>> =
5205 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5206
5207 drop(multibuffer_snapshot);
5208
5209 let provider = match requested_source {
5210 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5211 Some(CompletionsMenuSource::Words) => None,
5212 Some(CompletionsMenuSource::SnippetChoices) => {
5213 log::error!("bug: SnippetChoices requested_source is not handled");
5214 None
5215 }
5216 };
5217
5218 let sort_completions = provider
5219 .as_ref()
5220 .map_or(false, |provider| provider.sort_completions());
5221
5222 let filter_completions = provider
5223 .as_ref()
5224 .map_or(true, |provider| provider.filter_completions());
5225
5226 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5227 if filter_completions {
5228 menu.filter(query.clone(), provider.clone(), window, cx);
5229 }
5230 // When `is_incomplete` is false, no need to re-query completions when the current query
5231 // is a suffix of the initial query.
5232 if !menu.is_incomplete {
5233 // If the new query is a suffix of the old query (typing more characters) and
5234 // the previous result was complete, the existing completions can be filtered.
5235 //
5236 // Note that this is always true for snippet completions.
5237 let query_matches = match (&menu.initial_query, &query) {
5238 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5239 (None, _) => true,
5240 _ => false,
5241 };
5242 if query_matches {
5243 let position_matches = if menu.initial_position == position {
5244 true
5245 } else {
5246 let snapshot = self.buffer.read(cx).read(cx);
5247 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5248 };
5249 if position_matches {
5250 return;
5251 }
5252 }
5253 }
5254 };
5255
5256 let trigger_kind = match trigger {
5257 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5258 CompletionTriggerKind::TRIGGER_CHARACTER
5259 }
5260 _ => CompletionTriggerKind::INVOKED,
5261 };
5262 let completion_context = CompletionContext {
5263 trigger_character: trigger.and_then(|trigger| {
5264 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5265 Some(String::from(trigger))
5266 } else {
5267 None
5268 }
5269 }),
5270 trigger_kind,
5271 };
5272
5273 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5274 buffer_snapshot.surrounding_word(buffer_position)
5275 {
5276 let word_to_exclude = buffer_snapshot
5277 .text_for_range(word_range.clone())
5278 .collect::<String>();
5279 (
5280 buffer_snapshot.anchor_before(word_range.start)
5281 ..buffer_snapshot.anchor_after(buffer_position),
5282 Some(word_to_exclude),
5283 )
5284 } else {
5285 (buffer_position..buffer_position, None)
5286 };
5287
5288 let language = buffer_snapshot
5289 .language_at(buffer_position)
5290 .map(|language| language.name());
5291
5292 let completion_settings =
5293 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5294
5295 let show_completion_documentation = buffer_snapshot
5296 .settings_at(buffer_position, cx)
5297 .show_completion_documentation;
5298
5299 // The document can be large, so stay in reasonable bounds when searching for words,
5300 // otherwise completion pop-up might be slow to appear.
5301 const WORD_LOOKUP_ROWS: u32 = 5_000;
5302 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5303 let min_word_search = buffer_snapshot.clip_point(
5304 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5305 Bias::Left,
5306 );
5307 let max_word_search = buffer_snapshot.clip_point(
5308 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5309 Bias::Right,
5310 );
5311 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5312 ..buffer_snapshot.point_to_offset(max_word_search);
5313
5314 let skip_digits = query
5315 .as_ref()
5316 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5317
5318 let (mut words, provider_responses) = match &provider {
5319 Some(provider) => {
5320 let provider_responses = provider.completions(
5321 position.excerpt_id,
5322 &buffer,
5323 buffer_position,
5324 completion_context,
5325 window,
5326 cx,
5327 );
5328
5329 let words = match completion_settings.words {
5330 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5331 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5332 .background_spawn(async move {
5333 buffer_snapshot.words_in_range(WordsQuery {
5334 fuzzy_contents: None,
5335 range: word_search_range,
5336 skip_digits,
5337 })
5338 }),
5339 };
5340
5341 (words, provider_responses)
5342 }
5343 None => (
5344 cx.background_spawn(async move {
5345 buffer_snapshot.words_in_range(WordsQuery {
5346 fuzzy_contents: None,
5347 range: word_search_range,
5348 skip_digits,
5349 })
5350 }),
5351 Task::ready(Ok(Vec::new())),
5352 ),
5353 };
5354
5355 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5356
5357 let id = post_inc(&mut self.next_completion_id);
5358 let task = cx.spawn_in(window, async move |editor, cx| {
5359 let Ok(()) = editor.update(cx, |this, _| {
5360 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5361 }) else {
5362 return;
5363 };
5364
5365 // TODO: Ideally completions from different sources would be selectively re-queried, so
5366 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5367 let mut completions = Vec::new();
5368 let mut is_incomplete = false;
5369 if let Some(provider_responses) = provider_responses.await.log_err() {
5370 if !provider_responses.is_empty() {
5371 for response in provider_responses {
5372 completions.extend(response.completions);
5373 is_incomplete = is_incomplete || response.is_incomplete;
5374 }
5375 if completion_settings.words == WordsCompletionMode::Fallback {
5376 words = Task::ready(BTreeMap::default());
5377 }
5378 }
5379 }
5380
5381 let mut words = words.await;
5382 if let Some(word_to_exclude) = &word_to_exclude {
5383 words.remove(word_to_exclude);
5384 }
5385 for lsp_completion in &completions {
5386 words.remove(&lsp_completion.new_text);
5387 }
5388 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5389 replace_range: word_replace_range.clone(),
5390 new_text: word.clone(),
5391 label: CodeLabel::plain(word, None),
5392 icon_path: None,
5393 documentation: None,
5394 source: CompletionSource::BufferWord {
5395 word_range,
5396 resolved: false,
5397 },
5398 insert_text_mode: Some(InsertTextMode::AS_IS),
5399 confirm: None,
5400 }));
5401
5402 let menu = if completions.is_empty() {
5403 None
5404 } else {
5405 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5406 let languages = editor
5407 .workspace
5408 .as_ref()
5409 .and_then(|(workspace, _)| workspace.upgrade())
5410 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5411 let menu = CompletionsMenu::new(
5412 id,
5413 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5414 sort_completions,
5415 show_completion_documentation,
5416 position,
5417 query.clone(),
5418 is_incomplete,
5419 buffer.clone(),
5420 completions.into(),
5421 snippet_sort_order,
5422 languages,
5423 language,
5424 cx,
5425 );
5426
5427 let query = if filter_completions { query } else { None };
5428 let matches_task = if let Some(query) = query {
5429 menu.do_async_filtering(query, cx)
5430 } else {
5431 Task::ready(menu.unfiltered_matches())
5432 };
5433 (menu, matches_task)
5434 }) else {
5435 return;
5436 };
5437
5438 let matches = matches_task.await;
5439
5440 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5441 // Newer menu already set, so exit.
5442 match editor.context_menu.borrow().as_ref() {
5443 Some(CodeContextMenu::Completions(prev_menu)) => {
5444 if prev_menu.id > id {
5445 return;
5446 }
5447 }
5448 _ => {}
5449 };
5450
5451 // Only valid to take prev_menu because it the new menu is immediately set
5452 // below, or the menu is hidden.
5453 match editor.context_menu.borrow_mut().take() {
5454 Some(CodeContextMenu::Completions(prev_menu)) => {
5455 let position_matches =
5456 if prev_menu.initial_position == menu.initial_position {
5457 true
5458 } else {
5459 let snapshot = editor.buffer.read(cx).read(cx);
5460 prev_menu.initial_position.to_offset(&snapshot)
5461 == menu.initial_position.to_offset(&snapshot)
5462 };
5463 if position_matches {
5464 // Preserve markdown cache before `set_filter_results` because it will
5465 // try to populate the documentation cache.
5466 menu.preserve_markdown_cache(prev_menu);
5467 }
5468 }
5469 _ => {}
5470 };
5471
5472 menu.set_filter_results(matches, provider, window, cx);
5473 }) else {
5474 return;
5475 };
5476
5477 menu.visible().then_some(menu)
5478 };
5479
5480 editor
5481 .update_in(cx, |editor, window, cx| {
5482 if editor.focus_handle.is_focused(window) {
5483 if let Some(menu) = menu {
5484 *editor.context_menu.borrow_mut() =
5485 Some(CodeContextMenu::Completions(menu));
5486
5487 crate::hover_popover::hide_hover(editor, cx);
5488 if editor.show_edit_predictions_in_menu() {
5489 editor.update_visible_inline_completion(window, cx);
5490 } else {
5491 editor.discard_inline_completion(false, cx);
5492 }
5493
5494 cx.notify();
5495 return;
5496 }
5497 }
5498
5499 if editor.completion_tasks.len() <= 1 {
5500 // If there are no more completion tasks and the last menu was empty, we should hide it.
5501 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5502 // If it was already hidden and we don't show inline completions in the menu, we should
5503 // also show the inline-completion when available.
5504 if was_hidden && editor.show_edit_predictions_in_menu() {
5505 editor.update_visible_inline_completion(window, cx);
5506 }
5507 }
5508 })
5509 .ok();
5510 });
5511
5512 self.completion_tasks.push((id, task));
5513 }
5514
5515 #[cfg(feature = "test-support")]
5516 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5517 let menu = self.context_menu.borrow();
5518 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5519 let completions = menu.completions.borrow();
5520 Some(completions.to_vec())
5521 } else {
5522 None
5523 }
5524 }
5525
5526 pub fn with_completions_menu_matching_id<R>(
5527 &self,
5528 id: CompletionId,
5529 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5530 ) -> R {
5531 let mut context_menu = self.context_menu.borrow_mut();
5532 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5533 return f(None);
5534 };
5535 if completions_menu.id != id {
5536 return f(None);
5537 }
5538 f(Some(completions_menu))
5539 }
5540
5541 pub fn confirm_completion(
5542 &mut self,
5543 action: &ConfirmCompletion,
5544 window: &mut Window,
5545 cx: &mut Context<Self>,
5546 ) -> Option<Task<Result<()>>> {
5547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5548 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5549 }
5550
5551 pub fn confirm_completion_insert(
5552 &mut self,
5553 _: &ConfirmCompletionInsert,
5554 window: &mut Window,
5555 cx: &mut Context<Self>,
5556 ) -> Option<Task<Result<()>>> {
5557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5558 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5559 }
5560
5561 pub fn confirm_completion_replace(
5562 &mut self,
5563 _: &ConfirmCompletionReplace,
5564 window: &mut Window,
5565 cx: &mut Context<Self>,
5566 ) -> Option<Task<Result<()>>> {
5567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5568 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5569 }
5570
5571 pub fn compose_completion(
5572 &mut self,
5573 action: &ComposeCompletion,
5574 window: &mut Window,
5575 cx: &mut Context<Self>,
5576 ) -> Option<Task<Result<()>>> {
5577 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5578 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5579 }
5580
5581 fn do_completion(
5582 &mut self,
5583 item_ix: Option<usize>,
5584 intent: CompletionIntent,
5585 window: &mut Window,
5586 cx: &mut Context<Editor>,
5587 ) -> Option<Task<Result<()>>> {
5588 use language::ToOffset as _;
5589
5590 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5591 else {
5592 return None;
5593 };
5594
5595 let candidate_id = {
5596 let entries = completions_menu.entries.borrow();
5597 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5598 if self.show_edit_predictions_in_menu() {
5599 self.discard_inline_completion(true, cx);
5600 }
5601 mat.candidate_id
5602 };
5603
5604 let completion = completions_menu
5605 .completions
5606 .borrow()
5607 .get(candidate_id)?
5608 .clone();
5609 cx.stop_propagation();
5610
5611 let buffer_handle = completions_menu.buffer.clone();
5612
5613 let CompletionEdit {
5614 new_text,
5615 snippet,
5616 replace_range,
5617 } = process_completion_for_edit(
5618 &completion,
5619 intent,
5620 &buffer_handle,
5621 &completions_menu.initial_position.text_anchor,
5622 cx,
5623 );
5624
5625 let buffer = buffer_handle.read(cx);
5626 let snapshot = self.buffer.read(cx).snapshot(cx);
5627 let newest_anchor = self.selections.newest_anchor();
5628 let replace_range_multibuffer = {
5629 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5630 let multibuffer_anchor = snapshot
5631 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5632 .unwrap()
5633 ..snapshot
5634 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5635 .unwrap();
5636 multibuffer_anchor.start.to_offset(&snapshot)
5637 ..multibuffer_anchor.end.to_offset(&snapshot)
5638 };
5639 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5640 return None;
5641 }
5642
5643 let old_text = buffer
5644 .text_for_range(replace_range.clone())
5645 .collect::<String>();
5646 let lookbehind = newest_anchor
5647 .start
5648 .text_anchor
5649 .to_offset(buffer)
5650 .saturating_sub(replace_range.start);
5651 let lookahead = replace_range
5652 .end
5653 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5654 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5655 let suffix = &old_text[lookbehind.min(old_text.len())..];
5656
5657 let selections = self.selections.all::<usize>(cx);
5658 let mut ranges = Vec::new();
5659 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5660
5661 for selection in &selections {
5662 let range = if selection.id == newest_anchor.id {
5663 replace_range_multibuffer.clone()
5664 } else {
5665 let mut range = selection.range();
5666
5667 // if prefix is present, don't duplicate it
5668 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5669 range.start = range.start.saturating_sub(lookbehind);
5670
5671 // if suffix is also present, mimic the newest cursor and replace it
5672 if selection.id != newest_anchor.id
5673 && snapshot.contains_str_at(range.end, suffix)
5674 {
5675 range.end += lookahead;
5676 }
5677 }
5678 range
5679 };
5680
5681 ranges.push(range.clone());
5682
5683 if !self.linked_edit_ranges.is_empty() {
5684 let start_anchor = snapshot.anchor_before(range.start);
5685 let end_anchor = snapshot.anchor_after(range.end);
5686 if let Some(ranges) = self
5687 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5688 {
5689 for (buffer, edits) in ranges {
5690 linked_edits
5691 .entry(buffer.clone())
5692 .or_default()
5693 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5694 }
5695 }
5696 }
5697 }
5698
5699 let common_prefix_len = old_text
5700 .chars()
5701 .zip(new_text.chars())
5702 .take_while(|(a, b)| a == b)
5703 .map(|(a, _)| a.len_utf8())
5704 .sum::<usize>();
5705
5706 cx.emit(EditorEvent::InputHandled {
5707 utf16_range_to_replace: None,
5708 text: new_text[common_prefix_len..].into(),
5709 });
5710
5711 self.transact(window, cx, |this, window, cx| {
5712 if let Some(mut snippet) = snippet {
5713 snippet.text = new_text.to_string();
5714 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5715 } else {
5716 this.buffer.update(cx, |buffer, cx| {
5717 let auto_indent = match completion.insert_text_mode {
5718 Some(InsertTextMode::AS_IS) => None,
5719 _ => this.autoindent_mode.clone(),
5720 };
5721 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5722 buffer.edit(edits, auto_indent, cx);
5723 });
5724 }
5725 for (buffer, edits) in linked_edits {
5726 buffer.update(cx, |buffer, cx| {
5727 let snapshot = buffer.snapshot();
5728 let edits = edits
5729 .into_iter()
5730 .map(|(range, text)| {
5731 use text::ToPoint as TP;
5732 let end_point = TP::to_point(&range.end, &snapshot);
5733 let start_point = TP::to_point(&range.start, &snapshot);
5734 (start_point..end_point, text)
5735 })
5736 .sorted_by_key(|(range, _)| range.start);
5737 buffer.edit(edits, None, cx);
5738 })
5739 }
5740
5741 this.refresh_inline_completion(true, false, window, cx);
5742 });
5743
5744 let show_new_completions_on_confirm = completion
5745 .confirm
5746 .as_ref()
5747 .map_or(false, |confirm| confirm(intent, window, cx));
5748 if show_new_completions_on_confirm {
5749 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5750 }
5751
5752 let provider = self.completion_provider.as_ref()?;
5753 drop(completion);
5754 let apply_edits = provider.apply_additional_edits_for_completion(
5755 buffer_handle,
5756 completions_menu.completions.clone(),
5757 candidate_id,
5758 true,
5759 cx,
5760 );
5761
5762 let editor_settings = EditorSettings::get_global(cx);
5763 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5764 // After the code completion is finished, users often want to know what signatures are needed.
5765 // so we should automatically call signature_help
5766 self.show_signature_help(&ShowSignatureHelp, window, cx);
5767 }
5768
5769 Some(cx.foreground_executor().spawn(async move {
5770 apply_edits.await?;
5771 Ok(())
5772 }))
5773 }
5774
5775 pub fn toggle_code_actions(
5776 &mut self,
5777 action: &ToggleCodeActions,
5778 window: &mut Window,
5779 cx: &mut Context<Self>,
5780 ) {
5781 let quick_launch = action.quick_launch;
5782 let mut context_menu = self.context_menu.borrow_mut();
5783 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5784 if code_actions.deployed_from == action.deployed_from {
5785 // Toggle if we're selecting the same one
5786 *context_menu = None;
5787 cx.notify();
5788 return;
5789 } else {
5790 // Otherwise, clear it and start a new one
5791 *context_menu = None;
5792 cx.notify();
5793 }
5794 }
5795 drop(context_menu);
5796 let snapshot = self.snapshot(window, cx);
5797 let deployed_from = action.deployed_from.clone();
5798 let action = action.clone();
5799 self.completion_tasks.clear();
5800 self.discard_inline_completion(false, cx);
5801
5802 let multibuffer_point = match &action.deployed_from {
5803 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5804 DisplayPoint::new(*row, 0).to_point(&snapshot)
5805 }
5806 _ => self.selections.newest::<Point>(cx).head(),
5807 };
5808 let Some((buffer, buffer_row)) = snapshot
5809 .buffer_snapshot
5810 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5811 .and_then(|(buffer_snapshot, range)| {
5812 self.buffer()
5813 .read(cx)
5814 .buffer(buffer_snapshot.remote_id())
5815 .map(|buffer| (buffer, range.start.row))
5816 })
5817 else {
5818 return;
5819 };
5820 let buffer_id = buffer.read(cx).remote_id();
5821 let tasks = self
5822 .tasks
5823 .get(&(buffer_id, buffer_row))
5824 .map(|t| Arc::new(t.to_owned()));
5825
5826 if !self.focus_handle.is_focused(window) {
5827 return;
5828 }
5829 let project = self.project.clone();
5830
5831 let code_actions_task = match deployed_from {
5832 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5833 _ => self.code_actions(buffer_row, window, cx),
5834 };
5835
5836 let runnable_task = match deployed_from {
5837 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5838 _ => {
5839 let mut task_context_task = Task::ready(None);
5840 if let Some(tasks) = &tasks {
5841 if let Some(project) = project {
5842 task_context_task =
5843 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5844 }
5845 }
5846
5847 cx.spawn_in(window, {
5848 let buffer = buffer.clone();
5849 async move |editor, cx| {
5850 let task_context = task_context_task.await;
5851
5852 let resolved_tasks =
5853 tasks
5854 .zip(task_context.clone())
5855 .map(|(tasks, task_context)| ResolvedTasks {
5856 templates: tasks.resolve(&task_context).collect(),
5857 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5858 multibuffer_point.row,
5859 tasks.column,
5860 )),
5861 });
5862 let debug_scenarios = editor
5863 .update(cx, |editor, cx| {
5864 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5865 })?
5866 .await;
5867 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5868 }
5869 })
5870 }
5871 };
5872
5873 cx.spawn_in(window, async move |editor, cx| {
5874 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5875 let code_actions = code_actions_task.await;
5876 let spawn_straight_away = quick_launch
5877 && resolved_tasks
5878 .as_ref()
5879 .map_or(false, |tasks| tasks.templates.len() == 1)
5880 && code_actions
5881 .as_ref()
5882 .map_or(true, |actions| actions.is_empty())
5883 && debug_scenarios.is_empty();
5884
5885 editor.update_in(cx, |editor, window, cx| {
5886 crate::hover_popover::hide_hover(editor, cx);
5887 *editor.context_menu.borrow_mut() =
5888 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5889 buffer,
5890 actions: CodeActionContents::new(
5891 resolved_tasks,
5892 code_actions,
5893 debug_scenarios,
5894 task_context.unwrap_or_default(),
5895 ),
5896 selected_item: Default::default(),
5897 scroll_handle: UniformListScrollHandle::default(),
5898 deployed_from,
5899 }));
5900 cx.notify();
5901 if spawn_straight_away {
5902 if let Some(task) = editor.confirm_code_action(
5903 &ConfirmCodeAction { item_ix: Some(0) },
5904 window,
5905 cx,
5906 ) {
5907 return task;
5908 }
5909 }
5910
5911 Task::ready(Ok(()))
5912 })
5913 })
5914 .detach_and_log_err(cx);
5915 }
5916
5917 fn debug_scenarios(
5918 &mut self,
5919 resolved_tasks: &Option<ResolvedTasks>,
5920 buffer: &Entity<Buffer>,
5921 cx: &mut App,
5922 ) -> Task<Vec<task::DebugScenario>> {
5923 if cx.has_flag::<DebuggerFeatureFlag>() {
5924 maybe!({
5925 let project = self.project.as_ref()?;
5926 let dap_store = project.read(cx).dap_store();
5927 let mut scenarios = vec![];
5928 let resolved_tasks = resolved_tasks.as_ref()?;
5929 let buffer = buffer.read(cx);
5930 let language = buffer.language()?;
5931 let file = buffer.file();
5932 let debug_adapter = language_settings(language.name().into(), file, cx)
5933 .debuggers
5934 .first()
5935 .map(SharedString::from)
5936 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5937
5938 dap_store.update(cx, |dap_store, cx| {
5939 for (_, task) in &resolved_tasks.templates {
5940 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5941 task.original_task().clone(),
5942 debug_adapter.clone().into(),
5943 task.display_label().to_owned().into(),
5944 cx,
5945 );
5946 scenarios.push(maybe_scenario);
5947 }
5948 });
5949 Some(cx.background_spawn(async move {
5950 let scenarios = futures::future::join_all(scenarios)
5951 .await
5952 .into_iter()
5953 .flatten()
5954 .collect::<Vec<_>>();
5955 scenarios
5956 }))
5957 })
5958 .unwrap_or_else(|| Task::ready(vec![]))
5959 } else {
5960 Task::ready(vec![])
5961 }
5962 }
5963
5964 fn code_actions(
5965 &mut self,
5966 buffer_row: u32,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5970 let mut task = self.code_actions_task.take();
5971 cx.spawn_in(window, async move |editor, cx| {
5972 while let Some(prev_task) = task {
5973 prev_task.await.log_err();
5974 task = editor
5975 .update(cx, |this, _| this.code_actions_task.take())
5976 .ok()?;
5977 }
5978
5979 editor
5980 .update(cx, |editor, cx| {
5981 editor
5982 .available_code_actions
5983 .clone()
5984 .and_then(|(location, code_actions)| {
5985 let snapshot = location.buffer.read(cx).snapshot();
5986 let point_range = location.range.to_point(&snapshot);
5987 let point_range = point_range.start.row..=point_range.end.row;
5988 if point_range.contains(&buffer_row) {
5989 Some(code_actions)
5990 } else {
5991 None
5992 }
5993 })
5994 })
5995 .ok()
5996 .flatten()
5997 })
5998 }
5999
6000 pub fn confirm_code_action(
6001 &mut self,
6002 action: &ConfirmCodeAction,
6003 window: &mut Window,
6004 cx: &mut Context<Self>,
6005 ) -> Option<Task<Result<()>>> {
6006 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6007
6008 let actions_menu =
6009 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6010 menu
6011 } else {
6012 return None;
6013 };
6014
6015 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6016 let action = actions_menu.actions.get(action_ix)?;
6017 let title = action.label();
6018 let buffer = actions_menu.buffer;
6019 let workspace = self.workspace()?;
6020
6021 match action {
6022 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6023 workspace.update(cx, |workspace, cx| {
6024 workspace.schedule_resolved_task(
6025 task_source_kind,
6026 resolved_task,
6027 false,
6028 window,
6029 cx,
6030 );
6031
6032 Some(Task::ready(Ok(())))
6033 })
6034 }
6035 CodeActionsItem::CodeAction {
6036 excerpt_id,
6037 action,
6038 provider,
6039 } => {
6040 let apply_code_action =
6041 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6042 let workspace = workspace.downgrade();
6043 Some(cx.spawn_in(window, async move |editor, cx| {
6044 let project_transaction = apply_code_action.await?;
6045 Self::open_project_transaction(
6046 &editor,
6047 workspace,
6048 project_transaction,
6049 title,
6050 cx,
6051 )
6052 .await
6053 }))
6054 }
6055 CodeActionsItem::DebugScenario(scenario) => {
6056 let context = actions_menu.actions.context.clone();
6057
6058 workspace.update(cx, |workspace, cx| {
6059 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6060 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6061 });
6062 Some(Task::ready(Ok(())))
6063 }
6064 }
6065 }
6066
6067 pub async fn open_project_transaction(
6068 this: &WeakEntity<Editor>,
6069 workspace: WeakEntity<Workspace>,
6070 transaction: ProjectTransaction,
6071 title: String,
6072 cx: &mut AsyncWindowContext,
6073 ) -> Result<()> {
6074 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6075 cx.update(|_, cx| {
6076 entries.sort_unstable_by_key(|(buffer, _)| {
6077 buffer.read(cx).file().map(|f| f.path().clone())
6078 });
6079 })?;
6080
6081 // If the project transaction's edits are all contained within this editor, then
6082 // avoid opening a new editor to display them.
6083
6084 if let Some((buffer, transaction)) = entries.first() {
6085 if entries.len() == 1 {
6086 let excerpt = this.update(cx, |editor, cx| {
6087 editor
6088 .buffer()
6089 .read(cx)
6090 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6091 })?;
6092 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6093 if excerpted_buffer == *buffer {
6094 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6095 let excerpt_range = excerpt_range.to_offset(buffer);
6096 buffer
6097 .edited_ranges_for_transaction::<usize>(transaction)
6098 .all(|range| {
6099 excerpt_range.start <= range.start
6100 && excerpt_range.end >= range.end
6101 })
6102 })?;
6103
6104 if all_edits_within_excerpt {
6105 return Ok(());
6106 }
6107 }
6108 }
6109 }
6110 } else {
6111 return Ok(());
6112 }
6113
6114 let mut ranges_to_highlight = Vec::new();
6115 let excerpt_buffer = cx.new(|cx| {
6116 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6117 for (buffer_handle, transaction) in &entries {
6118 let edited_ranges = buffer_handle
6119 .read(cx)
6120 .edited_ranges_for_transaction::<Point>(transaction)
6121 .collect::<Vec<_>>();
6122 let (ranges, _) = multibuffer.set_excerpts_for_path(
6123 PathKey::for_buffer(buffer_handle, cx),
6124 buffer_handle.clone(),
6125 edited_ranges,
6126 DEFAULT_MULTIBUFFER_CONTEXT,
6127 cx,
6128 );
6129
6130 ranges_to_highlight.extend(ranges);
6131 }
6132 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6133 multibuffer
6134 })?;
6135
6136 workspace.update_in(cx, |workspace, window, cx| {
6137 let project = workspace.project().clone();
6138 let editor =
6139 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6140 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6141 editor.update(cx, |editor, cx| {
6142 editor.highlight_background::<Self>(
6143 &ranges_to_highlight,
6144 |theme| theme.editor_highlighted_line_background,
6145 cx,
6146 );
6147 });
6148 })?;
6149
6150 Ok(())
6151 }
6152
6153 pub fn clear_code_action_providers(&mut self) {
6154 self.code_action_providers.clear();
6155 self.available_code_actions.take();
6156 }
6157
6158 pub fn add_code_action_provider(
6159 &mut self,
6160 provider: Rc<dyn CodeActionProvider>,
6161 window: &mut Window,
6162 cx: &mut Context<Self>,
6163 ) {
6164 if self
6165 .code_action_providers
6166 .iter()
6167 .any(|existing_provider| existing_provider.id() == provider.id())
6168 {
6169 return;
6170 }
6171
6172 self.code_action_providers.push(provider);
6173 self.refresh_code_actions(window, cx);
6174 }
6175
6176 pub fn remove_code_action_provider(
6177 &mut self,
6178 id: Arc<str>,
6179 window: &mut Window,
6180 cx: &mut Context<Self>,
6181 ) {
6182 self.code_action_providers
6183 .retain(|provider| provider.id() != id);
6184 self.refresh_code_actions(window, cx);
6185 }
6186
6187 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6188 !self.code_action_providers.is_empty()
6189 && EditorSettings::get_global(cx).toolbar.code_actions
6190 }
6191
6192 pub fn has_available_code_actions(&self) -> bool {
6193 self.available_code_actions
6194 .as_ref()
6195 .is_some_and(|(_, actions)| !actions.is_empty())
6196 }
6197
6198 fn render_inline_code_actions(
6199 &self,
6200 icon_size: ui::IconSize,
6201 display_row: DisplayRow,
6202 is_active: bool,
6203 cx: &mut Context<Self>,
6204 ) -> AnyElement {
6205 let show_tooltip = !self.context_menu_visible();
6206 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6207 .icon_size(icon_size)
6208 .shape(ui::IconButtonShape::Square)
6209 .style(ButtonStyle::Transparent)
6210 .icon_color(ui::Color::Hidden)
6211 .toggle_state(is_active)
6212 .when(show_tooltip, |this| {
6213 this.tooltip({
6214 let focus_handle = self.focus_handle.clone();
6215 move |window, cx| {
6216 Tooltip::for_action_in(
6217 "Toggle Code Actions",
6218 &ToggleCodeActions {
6219 deployed_from: None,
6220 quick_launch: false,
6221 },
6222 &focus_handle,
6223 window,
6224 cx,
6225 )
6226 }
6227 })
6228 })
6229 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6230 window.focus(&editor.focus_handle(cx));
6231 editor.toggle_code_actions(
6232 &crate::actions::ToggleCodeActions {
6233 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6234 display_row,
6235 )),
6236 quick_launch: false,
6237 },
6238 window,
6239 cx,
6240 );
6241 }))
6242 .into_any_element()
6243 }
6244
6245 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6246 &self.context_menu
6247 }
6248
6249 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6250 let newest_selection = self.selections.newest_anchor().clone();
6251 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6252 let buffer = self.buffer.read(cx);
6253 if newest_selection.head().diff_base_anchor.is_some() {
6254 return None;
6255 }
6256 let (start_buffer, start) =
6257 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6258 let (end_buffer, end) =
6259 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6260 if start_buffer != end_buffer {
6261 return None;
6262 }
6263
6264 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6265 cx.background_executor()
6266 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6267 .await;
6268
6269 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6270 let providers = this.code_action_providers.clone();
6271 let tasks = this
6272 .code_action_providers
6273 .iter()
6274 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6275 .collect::<Vec<_>>();
6276 (providers, tasks)
6277 })?;
6278
6279 let mut actions = Vec::new();
6280 for (provider, provider_actions) in
6281 providers.into_iter().zip(future::join_all(tasks).await)
6282 {
6283 if let Some(provider_actions) = provider_actions.log_err() {
6284 actions.extend(provider_actions.into_iter().map(|action| {
6285 AvailableCodeAction {
6286 excerpt_id: newest_selection.start.excerpt_id,
6287 action,
6288 provider: provider.clone(),
6289 }
6290 }));
6291 }
6292 }
6293
6294 this.update(cx, |this, cx| {
6295 this.available_code_actions = if actions.is_empty() {
6296 None
6297 } else {
6298 Some((
6299 Location {
6300 buffer: start_buffer,
6301 range: start..end,
6302 },
6303 actions.into(),
6304 ))
6305 };
6306 cx.notify();
6307 })
6308 }));
6309 None
6310 }
6311
6312 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6313 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6314 self.show_git_blame_inline = false;
6315
6316 self.show_git_blame_inline_delay_task =
6317 Some(cx.spawn_in(window, async move |this, cx| {
6318 cx.background_executor().timer(delay).await;
6319
6320 this.update(cx, |this, cx| {
6321 this.show_git_blame_inline = true;
6322 cx.notify();
6323 })
6324 .log_err();
6325 }));
6326 }
6327 }
6328
6329 fn show_blame_popover(
6330 &mut self,
6331 blame_entry: &BlameEntry,
6332 position: gpui::Point<Pixels>,
6333 cx: &mut Context<Self>,
6334 ) {
6335 if let Some(state) = &mut self.inline_blame_popover {
6336 state.hide_task.take();
6337 } else {
6338 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6339 let blame_entry = blame_entry.clone();
6340 let show_task = cx.spawn(async move |editor, cx| {
6341 cx.background_executor()
6342 .timer(std::time::Duration::from_millis(delay))
6343 .await;
6344 editor
6345 .update(cx, |editor, cx| {
6346 editor.inline_blame_popover_show_task.take();
6347 let Some(blame) = editor.blame.as_ref() else {
6348 return;
6349 };
6350 let blame = blame.read(cx);
6351 let details = blame.details_for_entry(&blame_entry);
6352 let markdown = cx.new(|cx| {
6353 Markdown::new(
6354 details
6355 .as_ref()
6356 .map(|message| message.message.clone())
6357 .unwrap_or_default(),
6358 None,
6359 None,
6360 cx,
6361 )
6362 });
6363 editor.inline_blame_popover = Some(InlineBlamePopover {
6364 position,
6365 hide_task: None,
6366 popover_bounds: None,
6367 popover_state: InlineBlamePopoverState {
6368 scroll_handle: ScrollHandle::new(),
6369 commit_message: details,
6370 markdown,
6371 },
6372 });
6373 cx.notify();
6374 })
6375 .ok();
6376 });
6377 self.inline_blame_popover_show_task = Some(show_task);
6378 }
6379 }
6380
6381 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6382 self.inline_blame_popover_show_task.take();
6383 if let Some(state) = &mut self.inline_blame_popover {
6384 let hide_task = cx.spawn(async move |editor, cx| {
6385 cx.background_executor()
6386 .timer(std::time::Duration::from_millis(100))
6387 .await;
6388 editor
6389 .update(cx, |editor, cx| {
6390 editor.inline_blame_popover.take();
6391 cx.notify();
6392 })
6393 .ok();
6394 });
6395 state.hide_task = Some(hide_task);
6396 }
6397 }
6398
6399 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6400 if self.pending_rename.is_some() {
6401 return None;
6402 }
6403
6404 let provider = self.semantics_provider.clone()?;
6405 let buffer = self.buffer.read(cx);
6406 let newest_selection = self.selections.newest_anchor().clone();
6407 let cursor_position = newest_selection.head();
6408 let (cursor_buffer, cursor_buffer_position) =
6409 buffer.text_anchor_for_position(cursor_position, cx)?;
6410 let (tail_buffer, tail_buffer_position) =
6411 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6412 if cursor_buffer != tail_buffer {
6413 return None;
6414 }
6415
6416 let snapshot = cursor_buffer.read(cx).snapshot();
6417 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6418 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6419 if start_word_range != end_word_range {
6420 self.document_highlights_task.take();
6421 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6422 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6423 return None;
6424 }
6425
6426 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6427 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6428 cx.background_executor()
6429 .timer(Duration::from_millis(debounce))
6430 .await;
6431
6432 let highlights = if let Some(highlights) = cx
6433 .update(|cx| {
6434 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6435 })
6436 .ok()
6437 .flatten()
6438 {
6439 highlights.await.log_err()
6440 } else {
6441 None
6442 };
6443
6444 if let Some(highlights) = highlights {
6445 this.update(cx, |this, cx| {
6446 if this.pending_rename.is_some() {
6447 return;
6448 }
6449
6450 let buffer_id = cursor_position.buffer_id;
6451 let buffer = this.buffer.read(cx);
6452 if !buffer
6453 .text_anchor_for_position(cursor_position, cx)
6454 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6455 {
6456 return;
6457 }
6458
6459 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6460 let mut write_ranges = Vec::new();
6461 let mut read_ranges = Vec::new();
6462 for highlight in highlights {
6463 for (excerpt_id, excerpt_range) in
6464 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6465 {
6466 let start = highlight
6467 .range
6468 .start
6469 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6470 let end = highlight
6471 .range
6472 .end
6473 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6474 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6475 continue;
6476 }
6477
6478 let range = Anchor {
6479 buffer_id,
6480 excerpt_id,
6481 text_anchor: start,
6482 diff_base_anchor: None,
6483 }..Anchor {
6484 buffer_id,
6485 excerpt_id,
6486 text_anchor: end,
6487 diff_base_anchor: None,
6488 };
6489 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6490 write_ranges.push(range);
6491 } else {
6492 read_ranges.push(range);
6493 }
6494 }
6495 }
6496
6497 this.highlight_background::<DocumentHighlightRead>(
6498 &read_ranges,
6499 |theme| theme.editor_document_highlight_read_background,
6500 cx,
6501 );
6502 this.highlight_background::<DocumentHighlightWrite>(
6503 &write_ranges,
6504 |theme| theme.editor_document_highlight_write_background,
6505 cx,
6506 );
6507 cx.notify();
6508 })
6509 .log_err();
6510 }
6511 }));
6512 None
6513 }
6514
6515 fn prepare_highlight_query_from_selection(
6516 &mut self,
6517 cx: &mut Context<Editor>,
6518 ) -> Option<(String, Range<Anchor>)> {
6519 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6520 return None;
6521 }
6522 if !EditorSettings::get_global(cx).selection_highlight {
6523 return None;
6524 }
6525 if self.selections.count() != 1 || self.selections.line_mode {
6526 return None;
6527 }
6528 let selection = self.selections.newest::<Point>(cx);
6529 if selection.is_empty() || selection.start.row != selection.end.row {
6530 return None;
6531 }
6532 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6533 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6534 let query = multi_buffer_snapshot
6535 .text_for_range(selection_anchor_range.clone())
6536 .collect::<String>();
6537 if query.trim().is_empty() {
6538 return None;
6539 }
6540 Some((query, selection_anchor_range))
6541 }
6542
6543 fn update_selection_occurrence_highlights(
6544 &mut self,
6545 query_text: String,
6546 query_range: Range<Anchor>,
6547 multi_buffer_range_to_query: Range<Point>,
6548 use_debounce: bool,
6549 window: &mut Window,
6550 cx: &mut Context<Editor>,
6551 ) -> Task<()> {
6552 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6553 cx.spawn_in(window, async move |editor, cx| {
6554 if use_debounce {
6555 cx.background_executor()
6556 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6557 .await;
6558 }
6559 let match_task = cx.background_spawn(async move {
6560 let buffer_ranges = multi_buffer_snapshot
6561 .range_to_buffer_ranges(multi_buffer_range_to_query)
6562 .into_iter()
6563 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6564 let mut match_ranges = Vec::new();
6565 let Ok(regex) = project::search::SearchQuery::text(
6566 query_text.clone(),
6567 false,
6568 false,
6569 false,
6570 Default::default(),
6571 Default::default(),
6572 false,
6573 None,
6574 ) else {
6575 return Vec::default();
6576 };
6577 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6578 match_ranges.extend(
6579 regex
6580 .search(&buffer_snapshot, Some(search_range.clone()))
6581 .await
6582 .into_iter()
6583 .filter_map(|match_range| {
6584 let match_start = buffer_snapshot
6585 .anchor_after(search_range.start + match_range.start);
6586 let match_end = buffer_snapshot
6587 .anchor_before(search_range.start + match_range.end);
6588 let match_anchor_range = Anchor::range_in_buffer(
6589 excerpt_id,
6590 buffer_snapshot.remote_id(),
6591 match_start..match_end,
6592 );
6593 (match_anchor_range != query_range).then_some(match_anchor_range)
6594 }),
6595 );
6596 }
6597 match_ranges
6598 });
6599 let match_ranges = match_task.await;
6600 editor
6601 .update_in(cx, |editor, _, cx| {
6602 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6603 if !match_ranges.is_empty() {
6604 editor.highlight_background::<SelectedTextHighlight>(
6605 &match_ranges,
6606 |theme| theme.editor_document_highlight_bracket_background,
6607 cx,
6608 )
6609 }
6610 })
6611 .log_err();
6612 })
6613 }
6614
6615 fn refresh_selected_text_highlights(
6616 &mut self,
6617 on_buffer_edit: bool,
6618 window: &mut Window,
6619 cx: &mut Context<Editor>,
6620 ) {
6621 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6622 else {
6623 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6624 self.quick_selection_highlight_task.take();
6625 self.debounced_selection_highlight_task.take();
6626 return;
6627 };
6628 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6629 if on_buffer_edit
6630 || self
6631 .quick_selection_highlight_task
6632 .as_ref()
6633 .map_or(true, |(prev_anchor_range, _)| {
6634 prev_anchor_range != &query_range
6635 })
6636 {
6637 let multi_buffer_visible_start = self
6638 .scroll_manager
6639 .anchor()
6640 .anchor
6641 .to_point(&multi_buffer_snapshot);
6642 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6643 multi_buffer_visible_start
6644 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6645 Bias::Left,
6646 );
6647 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6648 self.quick_selection_highlight_task = Some((
6649 query_range.clone(),
6650 self.update_selection_occurrence_highlights(
6651 query_text.clone(),
6652 query_range.clone(),
6653 multi_buffer_visible_range,
6654 false,
6655 window,
6656 cx,
6657 ),
6658 ));
6659 }
6660 if on_buffer_edit
6661 || self
6662 .debounced_selection_highlight_task
6663 .as_ref()
6664 .map_or(true, |(prev_anchor_range, _)| {
6665 prev_anchor_range != &query_range
6666 })
6667 {
6668 let multi_buffer_start = multi_buffer_snapshot
6669 .anchor_before(0)
6670 .to_point(&multi_buffer_snapshot);
6671 let multi_buffer_end = multi_buffer_snapshot
6672 .anchor_after(multi_buffer_snapshot.len())
6673 .to_point(&multi_buffer_snapshot);
6674 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6675 self.debounced_selection_highlight_task = Some((
6676 query_range.clone(),
6677 self.update_selection_occurrence_highlights(
6678 query_text,
6679 query_range,
6680 multi_buffer_full_range,
6681 true,
6682 window,
6683 cx,
6684 ),
6685 ));
6686 }
6687 }
6688
6689 pub fn refresh_inline_completion(
6690 &mut self,
6691 debounce: bool,
6692 user_requested: bool,
6693 window: &mut Window,
6694 cx: &mut Context<Self>,
6695 ) -> Option<()> {
6696 let provider = self.edit_prediction_provider()?;
6697 let cursor = self.selections.newest_anchor().head();
6698 let (buffer, cursor_buffer_position) =
6699 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6700
6701 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6702 self.discard_inline_completion(false, cx);
6703 return None;
6704 }
6705
6706 if !user_requested
6707 && (!self.should_show_edit_predictions()
6708 || !self.is_focused(window)
6709 || buffer.read(cx).is_empty())
6710 {
6711 self.discard_inline_completion(false, cx);
6712 return None;
6713 }
6714
6715 self.update_visible_inline_completion(window, cx);
6716 provider.refresh(
6717 self.project.clone(),
6718 buffer,
6719 cursor_buffer_position,
6720 debounce,
6721 cx,
6722 );
6723 Some(())
6724 }
6725
6726 fn show_edit_predictions_in_menu(&self) -> bool {
6727 match self.edit_prediction_settings {
6728 EditPredictionSettings::Disabled => false,
6729 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6730 }
6731 }
6732
6733 pub fn edit_predictions_enabled(&self) -> bool {
6734 match self.edit_prediction_settings {
6735 EditPredictionSettings::Disabled => false,
6736 EditPredictionSettings::Enabled { .. } => true,
6737 }
6738 }
6739
6740 fn edit_prediction_requires_modifier(&self) -> bool {
6741 match self.edit_prediction_settings {
6742 EditPredictionSettings::Disabled => false,
6743 EditPredictionSettings::Enabled {
6744 preview_requires_modifier,
6745 ..
6746 } => preview_requires_modifier,
6747 }
6748 }
6749
6750 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6751 if self.edit_prediction_provider.is_none() {
6752 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6753 } else {
6754 let selection = self.selections.newest_anchor();
6755 let cursor = selection.head();
6756
6757 if let Some((buffer, cursor_buffer_position)) =
6758 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6759 {
6760 self.edit_prediction_settings =
6761 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6762 }
6763 }
6764 }
6765
6766 fn edit_prediction_settings_at_position(
6767 &self,
6768 buffer: &Entity<Buffer>,
6769 buffer_position: language::Anchor,
6770 cx: &App,
6771 ) -> EditPredictionSettings {
6772 if !self.mode.is_full()
6773 || !self.show_inline_completions_override.unwrap_or(true)
6774 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6775 {
6776 return EditPredictionSettings::Disabled;
6777 }
6778
6779 let buffer = buffer.read(cx);
6780
6781 let file = buffer.file();
6782
6783 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6784 return EditPredictionSettings::Disabled;
6785 };
6786
6787 let by_provider = matches!(
6788 self.menu_inline_completions_policy,
6789 MenuInlineCompletionsPolicy::ByProvider
6790 );
6791
6792 let show_in_menu = by_provider
6793 && self
6794 .edit_prediction_provider
6795 .as_ref()
6796 .map_or(false, |provider| {
6797 provider.provider.show_completions_in_menu()
6798 });
6799
6800 let preview_requires_modifier =
6801 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6802
6803 EditPredictionSettings::Enabled {
6804 show_in_menu,
6805 preview_requires_modifier,
6806 }
6807 }
6808
6809 fn should_show_edit_predictions(&self) -> bool {
6810 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6811 }
6812
6813 pub fn edit_prediction_preview_is_active(&self) -> bool {
6814 matches!(
6815 self.edit_prediction_preview,
6816 EditPredictionPreview::Active { .. }
6817 )
6818 }
6819
6820 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6821 let cursor = self.selections.newest_anchor().head();
6822 if let Some((buffer, cursor_position)) =
6823 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6824 {
6825 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6826 } else {
6827 false
6828 }
6829 }
6830
6831 pub fn supports_minimap(&self, cx: &App) -> bool {
6832 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6833 }
6834
6835 fn edit_predictions_enabled_in_buffer(
6836 &self,
6837 buffer: &Entity<Buffer>,
6838 buffer_position: language::Anchor,
6839 cx: &App,
6840 ) -> bool {
6841 maybe!({
6842 if self.read_only(cx) {
6843 return Some(false);
6844 }
6845 let provider = self.edit_prediction_provider()?;
6846 if !provider.is_enabled(&buffer, buffer_position, cx) {
6847 return Some(false);
6848 }
6849 let buffer = buffer.read(cx);
6850 let Some(file) = buffer.file() else {
6851 return Some(true);
6852 };
6853 let settings = all_language_settings(Some(file), cx);
6854 Some(settings.edit_predictions_enabled_for_file(file, cx))
6855 })
6856 .unwrap_or(false)
6857 }
6858
6859 fn cycle_inline_completion(
6860 &mut self,
6861 direction: Direction,
6862 window: &mut Window,
6863 cx: &mut Context<Self>,
6864 ) -> Option<()> {
6865 let provider = self.edit_prediction_provider()?;
6866 let cursor = self.selections.newest_anchor().head();
6867 let (buffer, cursor_buffer_position) =
6868 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6869 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6870 return None;
6871 }
6872
6873 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6874 self.update_visible_inline_completion(window, cx);
6875
6876 Some(())
6877 }
6878
6879 pub fn show_inline_completion(
6880 &mut self,
6881 _: &ShowEditPrediction,
6882 window: &mut Window,
6883 cx: &mut Context<Self>,
6884 ) {
6885 if !self.has_active_inline_completion() {
6886 self.refresh_inline_completion(false, true, window, cx);
6887 return;
6888 }
6889
6890 self.update_visible_inline_completion(window, cx);
6891 }
6892
6893 pub fn display_cursor_names(
6894 &mut self,
6895 _: &DisplayCursorNames,
6896 window: &mut Window,
6897 cx: &mut Context<Self>,
6898 ) {
6899 self.show_cursor_names(window, cx);
6900 }
6901
6902 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6903 self.show_cursor_names = true;
6904 cx.notify();
6905 cx.spawn_in(window, async move |this, cx| {
6906 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6907 this.update(cx, |this, cx| {
6908 this.show_cursor_names = false;
6909 cx.notify()
6910 })
6911 .ok()
6912 })
6913 .detach();
6914 }
6915
6916 pub fn next_edit_prediction(
6917 &mut self,
6918 _: &NextEditPrediction,
6919 window: &mut Window,
6920 cx: &mut Context<Self>,
6921 ) {
6922 if self.has_active_inline_completion() {
6923 self.cycle_inline_completion(Direction::Next, window, cx);
6924 } else {
6925 let is_copilot_disabled = self
6926 .refresh_inline_completion(false, true, window, cx)
6927 .is_none();
6928 if is_copilot_disabled {
6929 cx.propagate();
6930 }
6931 }
6932 }
6933
6934 pub fn previous_edit_prediction(
6935 &mut self,
6936 _: &PreviousEditPrediction,
6937 window: &mut Window,
6938 cx: &mut Context<Self>,
6939 ) {
6940 if self.has_active_inline_completion() {
6941 self.cycle_inline_completion(Direction::Prev, window, cx);
6942 } else {
6943 let is_copilot_disabled = self
6944 .refresh_inline_completion(false, true, window, cx)
6945 .is_none();
6946 if is_copilot_disabled {
6947 cx.propagate();
6948 }
6949 }
6950 }
6951
6952 pub fn accept_edit_prediction(
6953 &mut self,
6954 _: &AcceptEditPrediction,
6955 window: &mut Window,
6956 cx: &mut Context<Self>,
6957 ) {
6958 if self.show_edit_predictions_in_menu() {
6959 self.hide_context_menu(window, cx);
6960 }
6961
6962 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6963 return;
6964 };
6965
6966 self.report_inline_completion_event(
6967 active_inline_completion.completion_id.clone(),
6968 true,
6969 cx,
6970 );
6971
6972 match &active_inline_completion.completion {
6973 InlineCompletion::Move { target, .. } => {
6974 let target = *target;
6975
6976 if let Some(position_map) = &self.last_position_map {
6977 if position_map
6978 .visible_row_range
6979 .contains(&target.to_display_point(&position_map.snapshot).row())
6980 || !self.edit_prediction_requires_modifier()
6981 {
6982 self.unfold_ranges(&[target..target], true, false, cx);
6983 // Note that this is also done in vim's handler of the Tab action.
6984 self.change_selections(
6985 Some(Autoscroll::newest()),
6986 window,
6987 cx,
6988 |selections| {
6989 selections.select_anchor_ranges([target..target]);
6990 },
6991 );
6992 self.clear_row_highlights::<EditPredictionPreview>();
6993
6994 self.edit_prediction_preview
6995 .set_previous_scroll_position(None);
6996 } else {
6997 self.edit_prediction_preview
6998 .set_previous_scroll_position(Some(
6999 position_map.snapshot.scroll_anchor,
7000 ));
7001
7002 self.highlight_rows::<EditPredictionPreview>(
7003 target..target,
7004 cx.theme().colors().editor_highlighted_line_background,
7005 RowHighlightOptions {
7006 autoscroll: true,
7007 ..Default::default()
7008 },
7009 cx,
7010 );
7011 self.request_autoscroll(Autoscroll::fit(), cx);
7012 }
7013 }
7014 }
7015 InlineCompletion::Edit { edits, .. } => {
7016 if let Some(provider) = self.edit_prediction_provider() {
7017 provider.accept(cx);
7018 }
7019
7020 // Store the transaction ID and selections before applying the edit
7021 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7022
7023 let snapshot = self.buffer.read(cx).snapshot(cx);
7024 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7025
7026 self.buffer.update(cx, |buffer, cx| {
7027 buffer.edit(edits.iter().cloned(), None, cx)
7028 });
7029
7030 self.change_selections(None, window, cx, |s| {
7031 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7032 });
7033
7034 let selections = self.selections.disjoint_anchors();
7035 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7036 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7037 if has_new_transaction {
7038 self.selection_history
7039 .insert_transaction(transaction_id_now, selections);
7040 }
7041 }
7042
7043 self.update_visible_inline_completion(window, cx);
7044 if self.active_inline_completion.is_none() {
7045 self.refresh_inline_completion(true, true, window, cx);
7046 }
7047
7048 cx.notify();
7049 }
7050 }
7051
7052 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7053 }
7054
7055 pub fn accept_partial_inline_completion(
7056 &mut self,
7057 _: &AcceptPartialEditPrediction,
7058 window: &mut Window,
7059 cx: &mut Context<Self>,
7060 ) {
7061 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7062 return;
7063 };
7064 if self.selections.count() != 1 {
7065 return;
7066 }
7067
7068 self.report_inline_completion_event(
7069 active_inline_completion.completion_id.clone(),
7070 true,
7071 cx,
7072 );
7073
7074 match &active_inline_completion.completion {
7075 InlineCompletion::Move { target, .. } => {
7076 let target = *target;
7077 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7078 selections.select_anchor_ranges([target..target]);
7079 });
7080 }
7081 InlineCompletion::Edit { edits, .. } => {
7082 // Find an insertion that starts at the cursor position.
7083 let snapshot = self.buffer.read(cx).snapshot(cx);
7084 let cursor_offset = self.selections.newest::<usize>(cx).head();
7085 let insertion = edits.iter().find_map(|(range, text)| {
7086 let range = range.to_offset(&snapshot);
7087 if range.is_empty() && range.start == cursor_offset {
7088 Some(text)
7089 } else {
7090 None
7091 }
7092 });
7093
7094 if let Some(text) = insertion {
7095 let mut partial_completion = text
7096 .chars()
7097 .by_ref()
7098 .take_while(|c| c.is_alphabetic())
7099 .collect::<String>();
7100 if partial_completion.is_empty() {
7101 partial_completion = text
7102 .chars()
7103 .by_ref()
7104 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7105 .collect::<String>();
7106 }
7107
7108 cx.emit(EditorEvent::InputHandled {
7109 utf16_range_to_replace: None,
7110 text: partial_completion.clone().into(),
7111 });
7112
7113 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7114
7115 self.refresh_inline_completion(true, true, window, cx);
7116 cx.notify();
7117 } else {
7118 self.accept_edit_prediction(&Default::default(), window, cx);
7119 }
7120 }
7121 }
7122 }
7123
7124 fn discard_inline_completion(
7125 &mut self,
7126 should_report_inline_completion_event: bool,
7127 cx: &mut Context<Self>,
7128 ) -> bool {
7129 if should_report_inline_completion_event {
7130 let completion_id = self
7131 .active_inline_completion
7132 .as_ref()
7133 .and_then(|active_completion| active_completion.completion_id.clone());
7134
7135 self.report_inline_completion_event(completion_id, false, cx);
7136 }
7137
7138 if let Some(provider) = self.edit_prediction_provider() {
7139 provider.discard(cx);
7140 }
7141
7142 self.take_active_inline_completion(cx)
7143 }
7144
7145 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7146 let Some(provider) = self.edit_prediction_provider() else {
7147 return;
7148 };
7149
7150 let Some((_, buffer, _)) = self
7151 .buffer
7152 .read(cx)
7153 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7154 else {
7155 return;
7156 };
7157
7158 let extension = buffer
7159 .read(cx)
7160 .file()
7161 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7162
7163 let event_type = match accepted {
7164 true => "Edit Prediction Accepted",
7165 false => "Edit Prediction Discarded",
7166 };
7167 telemetry::event!(
7168 event_type,
7169 provider = provider.name(),
7170 prediction_id = id,
7171 suggestion_accepted = accepted,
7172 file_extension = extension,
7173 );
7174 }
7175
7176 pub fn has_active_inline_completion(&self) -> bool {
7177 self.active_inline_completion.is_some()
7178 }
7179
7180 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7181 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7182 return false;
7183 };
7184
7185 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7186 self.clear_highlights::<InlineCompletionHighlight>(cx);
7187 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7188 true
7189 }
7190
7191 /// Returns true when we're displaying the edit prediction popover below the cursor
7192 /// like we are not previewing and the LSP autocomplete menu is visible
7193 /// or we are in `when_holding_modifier` mode.
7194 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7195 if self.edit_prediction_preview_is_active()
7196 || !self.show_edit_predictions_in_menu()
7197 || !self.edit_predictions_enabled()
7198 {
7199 return false;
7200 }
7201
7202 if self.has_visible_completions_menu() {
7203 return true;
7204 }
7205
7206 has_completion && self.edit_prediction_requires_modifier()
7207 }
7208
7209 fn handle_modifiers_changed(
7210 &mut self,
7211 modifiers: Modifiers,
7212 position_map: &PositionMap,
7213 window: &mut Window,
7214 cx: &mut Context<Self>,
7215 ) {
7216 if self.show_edit_predictions_in_menu() {
7217 self.update_edit_prediction_preview(&modifiers, window, cx);
7218 }
7219
7220 self.update_selection_mode(&modifiers, position_map, window, cx);
7221
7222 let mouse_position = window.mouse_position();
7223 if !position_map.text_hitbox.is_hovered(window) {
7224 return;
7225 }
7226
7227 self.update_hovered_link(
7228 position_map.point_for_position(mouse_position),
7229 &position_map.snapshot,
7230 modifiers,
7231 window,
7232 cx,
7233 )
7234 }
7235
7236 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7237 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7238 if invert {
7239 match multi_cursor_setting {
7240 MultiCursorModifier::Alt => modifiers.alt,
7241 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7242 }
7243 } else {
7244 match multi_cursor_setting {
7245 MultiCursorModifier::Alt => modifiers.secondary(),
7246 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7247 }
7248 }
7249 }
7250
7251 fn columnar_selection_mode(
7252 modifiers: &Modifiers,
7253 cx: &mut Context<Self>,
7254 ) -> Option<ColumnarMode> {
7255 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7256 if Self::multi_cursor_modifier(false, modifiers, cx) {
7257 Some(ColumnarMode::FromMouse)
7258 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7259 Some(ColumnarMode::FromSelection)
7260 } else {
7261 None
7262 }
7263 } else {
7264 None
7265 }
7266 }
7267
7268 fn update_selection_mode(
7269 &mut self,
7270 modifiers: &Modifiers,
7271 position_map: &PositionMap,
7272 window: &mut Window,
7273 cx: &mut Context<Self>,
7274 ) {
7275 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7276 return;
7277 };
7278 if self.selections.pending.is_none() {
7279 return;
7280 }
7281
7282 let mouse_position = window.mouse_position();
7283 let point_for_position = position_map.point_for_position(mouse_position);
7284 let position = point_for_position.previous_valid;
7285
7286 self.select(
7287 SelectPhase::BeginColumnar {
7288 position,
7289 reset: false,
7290 mode,
7291 goal_column: point_for_position.exact_unclipped.column(),
7292 },
7293 window,
7294 cx,
7295 );
7296 }
7297
7298 fn update_edit_prediction_preview(
7299 &mut self,
7300 modifiers: &Modifiers,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 let mut modifiers_held = false;
7305 if let Some(accept_keystroke) = self
7306 .accept_edit_prediction_keybind(false, window, cx)
7307 .keystroke()
7308 {
7309 modifiers_held = modifiers_held
7310 || (&accept_keystroke.modifiers == modifiers
7311 && accept_keystroke.modifiers.modified());
7312 };
7313 if let Some(accept_partial_keystroke) = self
7314 .accept_edit_prediction_keybind(true, window, cx)
7315 .keystroke()
7316 {
7317 modifiers_held = modifiers_held
7318 || (&accept_partial_keystroke.modifiers == modifiers
7319 && accept_partial_keystroke.modifiers.modified());
7320 }
7321
7322 if modifiers_held {
7323 if matches!(
7324 self.edit_prediction_preview,
7325 EditPredictionPreview::Inactive { .. }
7326 ) {
7327 self.edit_prediction_preview = EditPredictionPreview::Active {
7328 previous_scroll_position: None,
7329 since: Instant::now(),
7330 };
7331
7332 self.update_visible_inline_completion(window, cx);
7333 cx.notify();
7334 }
7335 } else if let EditPredictionPreview::Active {
7336 previous_scroll_position,
7337 since,
7338 } = self.edit_prediction_preview
7339 {
7340 if let (Some(previous_scroll_position), Some(position_map)) =
7341 (previous_scroll_position, self.last_position_map.as_ref())
7342 {
7343 self.set_scroll_position(
7344 previous_scroll_position
7345 .scroll_position(&position_map.snapshot.display_snapshot),
7346 window,
7347 cx,
7348 );
7349 }
7350
7351 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7352 released_too_fast: since.elapsed() < Duration::from_millis(200),
7353 };
7354 self.clear_row_highlights::<EditPredictionPreview>();
7355 self.update_visible_inline_completion(window, cx);
7356 cx.notify();
7357 }
7358 }
7359
7360 fn update_visible_inline_completion(
7361 &mut self,
7362 _window: &mut Window,
7363 cx: &mut Context<Self>,
7364 ) -> Option<()> {
7365 let selection = self.selections.newest_anchor();
7366 let cursor = selection.head();
7367 let multibuffer = self.buffer.read(cx).snapshot(cx);
7368 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7369 let excerpt_id = cursor.excerpt_id;
7370
7371 let show_in_menu = self.show_edit_predictions_in_menu();
7372 let completions_menu_has_precedence = !show_in_menu
7373 && (self.context_menu.borrow().is_some()
7374 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7375
7376 if completions_menu_has_precedence
7377 || !offset_selection.is_empty()
7378 || self
7379 .active_inline_completion
7380 .as_ref()
7381 .map_or(false, |completion| {
7382 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7383 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7384 !invalidation_range.contains(&offset_selection.head())
7385 })
7386 {
7387 self.discard_inline_completion(false, cx);
7388 return None;
7389 }
7390
7391 self.take_active_inline_completion(cx);
7392 let Some(provider) = self.edit_prediction_provider() else {
7393 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7394 return None;
7395 };
7396
7397 let (buffer, cursor_buffer_position) =
7398 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7399
7400 self.edit_prediction_settings =
7401 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7402
7403 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7404
7405 if self.edit_prediction_indent_conflict {
7406 let cursor_point = cursor.to_point(&multibuffer);
7407
7408 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7409
7410 if let Some((_, indent)) = indents.iter().next() {
7411 if indent.len == cursor_point.column {
7412 self.edit_prediction_indent_conflict = false;
7413 }
7414 }
7415 }
7416
7417 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7418 let edits = inline_completion
7419 .edits
7420 .into_iter()
7421 .flat_map(|(range, new_text)| {
7422 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7423 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7424 Some((start..end, new_text))
7425 })
7426 .collect::<Vec<_>>();
7427 if edits.is_empty() {
7428 return None;
7429 }
7430
7431 let first_edit_start = edits.first().unwrap().0.start;
7432 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7433 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7434
7435 let last_edit_end = edits.last().unwrap().0.end;
7436 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7437 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7438
7439 let cursor_row = cursor.to_point(&multibuffer).row;
7440
7441 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7442
7443 let mut inlay_ids = Vec::new();
7444 let invalidation_row_range;
7445 let move_invalidation_row_range = if cursor_row < edit_start_row {
7446 Some(cursor_row..edit_end_row)
7447 } else if cursor_row > edit_end_row {
7448 Some(edit_start_row..cursor_row)
7449 } else {
7450 None
7451 };
7452 let is_move =
7453 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7454 let completion = if is_move {
7455 invalidation_row_range =
7456 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7457 let target = first_edit_start;
7458 InlineCompletion::Move { target, snapshot }
7459 } else {
7460 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7461 && !self.inline_completions_hidden_for_vim_mode;
7462
7463 if show_completions_in_buffer {
7464 if edits
7465 .iter()
7466 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7467 {
7468 let mut inlays = Vec::new();
7469 for (range, new_text) in &edits {
7470 let inlay = Inlay::inline_completion(
7471 post_inc(&mut self.next_inlay_id),
7472 range.start,
7473 new_text.as_str(),
7474 );
7475 inlay_ids.push(inlay.id);
7476 inlays.push(inlay);
7477 }
7478
7479 self.splice_inlays(&[], inlays, cx);
7480 } else {
7481 let background_color = cx.theme().status().deleted_background;
7482 self.highlight_text::<InlineCompletionHighlight>(
7483 edits.iter().map(|(range, _)| range.clone()).collect(),
7484 HighlightStyle {
7485 background_color: Some(background_color),
7486 ..Default::default()
7487 },
7488 cx,
7489 );
7490 }
7491 }
7492
7493 invalidation_row_range = edit_start_row..edit_end_row;
7494
7495 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7496 if provider.show_tab_accept_marker() {
7497 EditDisplayMode::TabAccept
7498 } else {
7499 EditDisplayMode::Inline
7500 }
7501 } else {
7502 EditDisplayMode::DiffPopover
7503 };
7504
7505 InlineCompletion::Edit {
7506 edits,
7507 edit_preview: inline_completion.edit_preview,
7508 display_mode,
7509 snapshot,
7510 }
7511 };
7512
7513 let invalidation_range = multibuffer
7514 .anchor_before(Point::new(invalidation_row_range.start, 0))
7515 ..multibuffer.anchor_after(Point::new(
7516 invalidation_row_range.end,
7517 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7518 ));
7519
7520 self.stale_inline_completion_in_menu = None;
7521 self.active_inline_completion = Some(InlineCompletionState {
7522 inlay_ids,
7523 completion,
7524 completion_id: inline_completion.id,
7525 invalidation_range,
7526 });
7527
7528 cx.notify();
7529
7530 Some(())
7531 }
7532
7533 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7534 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7535 }
7536
7537 fn clear_tasks(&mut self) {
7538 self.tasks.clear()
7539 }
7540
7541 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7542 if self.tasks.insert(key, value).is_some() {
7543 // This case should hopefully be rare, but just in case...
7544 log::error!(
7545 "multiple different run targets found on a single line, only the last target will be rendered"
7546 )
7547 }
7548 }
7549
7550 /// Get all display points of breakpoints that will be rendered within editor
7551 ///
7552 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7553 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7554 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7555 fn active_breakpoints(
7556 &self,
7557 range: Range<DisplayRow>,
7558 window: &mut Window,
7559 cx: &mut Context<Self>,
7560 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7561 let mut breakpoint_display_points = HashMap::default();
7562
7563 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7564 return breakpoint_display_points;
7565 };
7566
7567 let snapshot = self.snapshot(window, cx);
7568
7569 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7570 let Some(project) = self.project.as_ref() else {
7571 return breakpoint_display_points;
7572 };
7573
7574 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7575 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7576
7577 for (buffer_snapshot, range, excerpt_id) in
7578 multi_buffer_snapshot.range_to_buffer_ranges(range)
7579 {
7580 let Some(buffer) = project
7581 .read(cx)
7582 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7583 else {
7584 continue;
7585 };
7586 let breakpoints = breakpoint_store.read(cx).breakpoints(
7587 &buffer,
7588 Some(
7589 buffer_snapshot.anchor_before(range.start)
7590 ..buffer_snapshot.anchor_after(range.end),
7591 ),
7592 buffer_snapshot,
7593 cx,
7594 );
7595 for (breakpoint, state) in breakpoints {
7596 let multi_buffer_anchor =
7597 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7598 let position = multi_buffer_anchor
7599 .to_point(&multi_buffer_snapshot)
7600 .to_display_point(&snapshot);
7601
7602 breakpoint_display_points.insert(
7603 position.row(),
7604 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7605 );
7606 }
7607 }
7608
7609 breakpoint_display_points
7610 }
7611
7612 fn breakpoint_context_menu(
7613 &self,
7614 anchor: Anchor,
7615 window: &mut Window,
7616 cx: &mut Context<Self>,
7617 ) -> Entity<ui::ContextMenu> {
7618 let weak_editor = cx.weak_entity();
7619 let focus_handle = self.focus_handle(cx);
7620
7621 let row = self
7622 .buffer
7623 .read(cx)
7624 .snapshot(cx)
7625 .summary_for_anchor::<Point>(&anchor)
7626 .row;
7627
7628 let breakpoint = self
7629 .breakpoint_at_row(row, window, cx)
7630 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7631
7632 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7633 "Edit Log Breakpoint"
7634 } else {
7635 "Set Log Breakpoint"
7636 };
7637
7638 let condition_breakpoint_msg = if breakpoint
7639 .as_ref()
7640 .is_some_and(|bp| bp.1.condition.is_some())
7641 {
7642 "Edit Condition Breakpoint"
7643 } else {
7644 "Set Condition Breakpoint"
7645 };
7646
7647 let hit_condition_breakpoint_msg = if breakpoint
7648 .as_ref()
7649 .is_some_and(|bp| bp.1.hit_condition.is_some())
7650 {
7651 "Edit Hit Condition Breakpoint"
7652 } else {
7653 "Set Hit Condition Breakpoint"
7654 };
7655
7656 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7657 "Unset Breakpoint"
7658 } else {
7659 "Set Breakpoint"
7660 };
7661
7662 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7663
7664 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7665 BreakpointState::Enabled => Some("Disable"),
7666 BreakpointState::Disabled => Some("Enable"),
7667 });
7668
7669 let (anchor, breakpoint) =
7670 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7671
7672 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7673 menu.on_blur_subscription(Subscription::new(|| {}))
7674 .context(focus_handle)
7675 .when(run_to_cursor, |this| {
7676 let weak_editor = weak_editor.clone();
7677 this.entry("Run to cursor", None, move |window, cx| {
7678 weak_editor
7679 .update(cx, |editor, cx| {
7680 editor.change_selections(None, window, cx, |s| {
7681 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7682 });
7683 })
7684 .ok();
7685
7686 window.dispatch_action(Box::new(RunToCursor), cx);
7687 })
7688 .separator()
7689 })
7690 .when_some(toggle_state_msg, |this, msg| {
7691 this.entry(msg, None, {
7692 let weak_editor = weak_editor.clone();
7693 let breakpoint = breakpoint.clone();
7694 move |_window, cx| {
7695 weak_editor
7696 .update(cx, |this, cx| {
7697 this.edit_breakpoint_at_anchor(
7698 anchor,
7699 breakpoint.as_ref().clone(),
7700 BreakpointEditAction::InvertState,
7701 cx,
7702 );
7703 })
7704 .log_err();
7705 }
7706 })
7707 })
7708 .entry(set_breakpoint_msg, None, {
7709 let weak_editor = weak_editor.clone();
7710 let breakpoint = breakpoint.clone();
7711 move |_window, cx| {
7712 weak_editor
7713 .update(cx, |this, cx| {
7714 this.edit_breakpoint_at_anchor(
7715 anchor,
7716 breakpoint.as_ref().clone(),
7717 BreakpointEditAction::Toggle,
7718 cx,
7719 );
7720 })
7721 .log_err();
7722 }
7723 })
7724 .entry(log_breakpoint_msg, None, {
7725 let breakpoint = breakpoint.clone();
7726 let weak_editor = weak_editor.clone();
7727 move |window, cx| {
7728 weak_editor
7729 .update(cx, |this, cx| {
7730 this.add_edit_breakpoint_block(
7731 anchor,
7732 breakpoint.as_ref(),
7733 BreakpointPromptEditAction::Log,
7734 window,
7735 cx,
7736 );
7737 })
7738 .log_err();
7739 }
7740 })
7741 .entry(condition_breakpoint_msg, None, {
7742 let breakpoint = breakpoint.clone();
7743 let weak_editor = weak_editor.clone();
7744 move |window, cx| {
7745 weak_editor
7746 .update(cx, |this, cx| {
7747 this.add_edit_breakpoint_block(
7748 anchor,
7749 breakpoint.as_ref(),
7750 BreakpointPromptEditAction::Condition,
7751 window,
7752 cx,
7753 );
7754 })
7755 .log_err();
7756 }
7757 })
7758 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7759 weak_editor
7760 .update(cx, |this, cx| {
7761 this.add_edit_breakpoint_block(
7762 anchor,
7763 breakpoint.as_ref(),
7764 BreakpointPromptEditAction::HitCondition,
7765 window,
7766 cx,
7767 );
7768 })
7769 .log_err();
7770 })
7771 })
7772 }
7773
7774 fn render_breakpoint(
7775 &self,
7776 position: Anchor,
7777 row: DisplayRow,
7778 breakpoint: &Breakpoint,
7779 state: Option<BreakpointSessionState>,
7780 cx: &mut Context<Self>,
7781 ) -> IconButton {
7782 let is_rejected = state.is_some_and(|s| !s.verified);
7783 // Is it a breakpoint that shows up when hovering over gutter?
7784 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7785 (false, false),
7786 |PhantomBreakpointIndicator {
7787 is_active,
7788 display_row,
7789 collides_with_existing_breakpoint,
7790 }| {
7791 (
7792 is_active && display_row == row,
7793 collides_with_existing_breakpoint,
7794 )
7795 },
7796 );
7797
7798 let (color, icon) = {
7799 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7800 (false, false) => ui::IconName::DebugBreakpoint,
7801 (true, false) => ui::IconName::DebugLogBreakpoint,
7802 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7803 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7804 };
7805
7806 let color = if is_phantom {
7807 Color::Hint
7808 } else if is_rejected {
7809 Color::Disabled
7810 } else {
7811 Color::Debugger
7812 };
7813
7814 (color, icon)
7815 };
7816
7817 let breakpoint = Arc::from(breakpoint.clone());
7818
7819 let alt_as_text = gpui::Keystroke {
7820 modifiers: Modifiers::secondary_key(),
7821 ..Default::default()
7822 };
7823 let primary_action_text = if breakpoint.is_disabled() {
7824 "Enable breakpoint"
7825 } else if is_phantom && !collides_with_existing {
7826 "Set breakpoint"
7827 } else {
7828 "Unset breakpoint"
7829 };
7830 let focus_handle = self.focus_handle.clone();
7831
7832 let meta = if is_rejected {
7833 SharedString::from("No executable code is associated with this line.")
7834 } else if collides_with_existing && !breakpoint.is_disabled() {
7835 SharedString::from(format!(
7836 "{alt_as_text}-click to disable,\nright-click for more options."
7837 ))
7838 } else {
7839 SharedString::from("Right-click for more options.")
7840 };
7841 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7842 .icon_size(IconSize::XSmall)
7843 .size(ui::ButtonSize::None)
7844 .when(is_rejected, |this| {
7845 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7846 })
7847 .icon_color(color)
7848 .style(ButtonStyle::Transparent)
7849 .on_click(cx.listener({
7850 let breakpoint = breakpoint.clone();
7851
7852 move |editor, event: &ClickEvent, window, cx| {
7853 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7854 BreakpointEditAction::InvertState
7855 } else {
7856 BreakpointEditAction::Toggle
7857 };
7858
7859 window.focus(&editor.focus_handle(cx));
7860 editor.edit_breakpoint_at_anchor(
7861 position,
7862 breakpoint.as_ref().clone(),
7863 edit_action,
7864 cx,
7865 );
7866 }
7867 }))
7868 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7869 editor.set_breakpoint_context_menu(
7870 row,
7871 Some(position),
7872 event.down.position,
7873 window,
7874 cx,
7875 );
7876 }))
7877 .tooltip(move |window, cx| {
7878 Tooltip::with_meta_in(
7879 primary_action_text,
7880 Some(&ToggleBreakpoint),
7881 meta.clone(),
7882 &focus_handle,
7883 window,
7884 cx,
7885 )
7886 })
7887 }
7888
7889 fn build_tasks_context(
7890 project: &Entity<Project>,
7891 buffer: &Entity<Buffer>,
7892 buffer_row: u32,
7893 tasks: &Arc<RunnableTasks>,
7894 cx: &mut Context<Self>,
7895 ) -> Task<Option<task::TaskContext>> {
7896 let position = Point::new(buffer_row, tasks.column);
7897 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7898 let location = Location {
7899 buffer: buffer.clone(),
7900 range: range_start..range_start,
7901 };
7902 // Fill in the environmental variables from the tree-sitter captures
7903 let mut captured_task_variables = TaskVariables::default();
7904 for (capture_name, value) in tasks.extra_variables.clone() {
7905 captured_task_variables.insert(
7906 task::VariableName::Custom(capture_name.into()),
7907 value.clone(),
7908 );
7909 }
7910 project.update(cx, |project, cx| {
7911 project.task_store().update(cx, |task_store, cx| {
7912 task_store.task_context_for_location(captured_task_variables, location, cx)
7913 })
7914 })
7915 }
7916
7917 pub fn spawn_nearest_task(
7918 &mut self,
7919 action: &SpawnNearestTask,
7920 window: &mut Window,
7921 cx: &mut Context<Self>,
7922 ) {
7923 let Some((workspace, _)) = self.workspace.clone() else {
7924 return;
7925 };
7926 let Some(project) = self.project.clone() else {
7927 return;
7928 };
7929
7930 // Try to find a closest, enclosing node using tree-sitter that has a
7931 // task
7932 let Some((buffer, buffer_row, tasks)) = self
7933 .find_enclosing_node_task(cx)
7934 // Or find the task that's closest in row-distance.
7935 .or_else(|| self.find_closest_task(cx))
7936 else {
7937 return;
7938 };
7939
7940 let reveal_strategy = action.reveal;
7941 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7942 cx.spawn_in(window, async move |_, cx| {
7943 let context = task_context.await?;
7944 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7945
7946 let resolved = &mut resolved_task.resolved;
7947 resolved.reveal = reveal_strategy;
7948
7949 workspace
7950 .update_in(cx, |workspace, window, cx| {
7951 workspace.schedule_resolved_task(
7952 task_source_kind,
7953 resolved_task,
7954 false,
7955 window,
7956 cx,
7957 );
7958 })
7959 .ok()
7960 })
7961 .detach();
7962 }
7963
7964 fn find_closest_task(
7965 &mut self,
7966 cx: &mut Context<Self>,
7967 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7968 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7969
7970 let ((buffer_id, row), tasks) = self
7971 .tasks
7972 .iter()
7973 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7974
7975 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7976 let tasks = Arc::new(tasks.to_owned());
7977 Some((buffer, *row, tasks))
7978 }
7979
7980 fn find_enclosing_node_task(
7981 &mut self,
7982 cx: &mut Context<Self>,
7983 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7984 let snapshot = self.buffer.read(cx).snapshot(cx);
7985 let offset = self.selections.newest::<usize>(cx).head();
7986 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7987 let buffer_id = excerpt.buffer().remote_id();
7988
7989 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7990 let mut cursor = layer.node().walk();
7991
7992 while cursor.goto_first_child_for_byte(offset).is_some() {
7993 if cursor.node().end_byte() == offset {
7994 cursor.goto_next_sibling();
7995 }
7996 }
7997
7998 // Ascend to the smallest ancestor that contains the range and has a task.
7999 loop {
8000 let node = cursor.node();
8001 let node_range = node.byte_range();
8002 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8003
8004 // Check if this node contains our offset
8005 if node_range.start <= offset && node_range.end >= offset {
8006 // If it contains offset, check for task
8007 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8008 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8009 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8010 }
8011 }
8012
8013 if !cursor.goto_parent() {
8014 break;
8015 }
8016 }
8017 None
8018 }
8019
8020 fn render_run_indicator(
8021 &self,
8022 _style: &EditorStyle,
8023 is_active: bool,
8024 row: DisplayRow,
8025 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8026 cx: &mut Context<Self>,
8027 ) -> IconButton {
8028 let color = Color::Muted;
8029 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8030
8031 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8032 .shape(ui::IconButtonShape::Square)
8033 .icon_size(IconSize::XSmall)
8034 .icon_color(color)
8035 .toggle_state(is_active)
8036 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8037 let quick_launch = e.down.button == MouseButton::Left;
8038 window.focus(&editor.focus_handle(cx));
8039 editor.toggle_code_actions(
8040 &ToggleCodeActions {
8041 deployed_from: Some(CodeActionSource::RunMenu(row)),
8042 quick_launch,
8043 },
8044 window,
8045 cx,
8046 );
8047 }))
8048 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8049 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8050 }))
8051 }
8052
8053 pub fn context_menu_visible(&self) -> bool {
8054 !self.edit_prediction_preview_is_active()
8055 && self
8056 .context_menu
8057 .borrow()
8058 .as_ref()
8059 .map_or(false, |menu| menu.visible())
8060 }
8061
8062 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8063 self.context_menu
8064 .borrow()
8065 .as_ref()
8066 .map(|menu| menu.origin())
8067 }
8068
8069 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8070 self.context_menu_options = Some(options);
8071 }
8072
8073 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8074 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8075
8076 fn render_edit_prediction_popover(
8077 &mut self,
8078 text_bounds: &Bounds<Pixels>,
8079 content_origin: gpui::Point<Pixels>,
8080 right_margin: Pixels,
8081 editor_snapshot: &EditorSnapshot,
8082 visible_row_range: Range<DisplayRow>,
8083 scroll_top: f32,
8084 scroll_bottom: f32,
8085 line_layouts: &[LineWithInvisibles],
8086 line_height: Pixels,
8087 scroll_pixel_position: gpui::Point<Pixels>,
8088 newest_selection_head: Option<DisplayPoint>,
8089 editor_width: Pixels,
8090 style: &EditorStyle,
8091 window: &mut Window,
8092 cx: &mut App,
8093 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8094 if self.mode().is_minimap() {
8095 return None;
8096 }
8097 let active_inline_completion = self.active_inline_completion.as_ref()?;
8098
8099 if self.edit_prediction_visible_in_cursor_popover(true) {
8100 return None;
8101 }
8102
8103 match &active_inline_completion.completion {
8104 InlineCompletion::Move { target, .. } => {
8105 let target_display_point = target.to_display_point(editor_snapshot);
8106
8107 if self.edit_prediction_requires_modifier() {
8108 if !self.edit_prediction_preview_is_active() {
8109 return None;
8110 }
8111
8112 self.render_edit_prediction_modifier_jump_popover(
8113 text_bounds,
8114 content_origin,
8115 visible_row_range,
8116 line_layouts,
8117 line_height,
8118 scroll_pixel_position,
8119 newest_selection_head,
8120 target_display_point,
8121 window,
8122 cx,
8123 )
8124 } else {
8125 self.render_edit_prediction_eager_jump_popover(
8126 text_bounds,
8127 content_origin,
8128 editor_snapshot,
8129 visible_row_range,
8130 scroll_top,
8131 scroll_bottom,
8132 line_height,
8133 scroll_pixel_position,
8134 target_display_point,
8135 editor_width,
8136 window,
8137 cx,
8138 )
8139 }
8140 }
8141 InlineCompletion::Edit {
8142 display_mode: EditDisplayMode::Inline,
8143 ..
8144 } => None,
8145 InlineCompletion::Edit {
8146 display_mode: EditDisplayMode::TabAccept,
8147 edits,
8148 ..
8149 } => {
8150 let range = &edits.first()?.0;
8151 let target_display_point = range.end.to_display_point(editor_snapshot);
8152
8153 self.render_edit_prediction_end_of_line_popover(
8154 "Accept",
8155 editor_snapshot,
8156 visible_row_range,
8157 target_display_point,
8158 line_height,
8159 scroll_pixel_position,
8160 content_origin,
8161 editor_width,
8162 window,
8163 cx,
8164 )
8165 }
8166 InlineCompletion::Edit {
8167 edits,
8168 edit_preview,
8169 display_mode: EditDisplayMode::DiffPopover,
8170 snapshot,
8171 } => self.render_edit_prediction_diff_popover(
8172 text_bounds,
8173 content_origin,
8174 right_margin,
8175 editor_snapshot,
8176 visible_row_range,
8177 line_layouts,
8178 line_height,
8179 scroll_pixel_position,
8180 newest_selection_head,
8181 editor_width,
8182 style,
8183 edits,
8184 edit_preview,
8185 snapshot,
8186 window,
8187 cx,
8188 ),
8189 }
8190 }
8191
8192 fn render_edit_prediction_modifier_jump_popover(
8193 &mut self,
8194 text_bounds: &Bounds<Pixels>,
8195 content_origin: gpui::Point<Pixels>,
8196 visible_row_range: Range<DisplayRow>,
8197 line_layouts: &[LineWithInvisibles],
8198 line_height: Pixels,
8199 scroll_pixel_position: gpui::Point<Pixels>,
8200 newest_selection_head: Option<DisplayPoint>,
8201 target_display_point: DisplayPoint,
8202 window: &mut Window,
8203 cx: &mut App,
8204 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8205 let scrolled_content_origin =
8206 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8207
8208 const SCROLL_PADDING_Y: Pixels = px(12.);
8209
8210 if target_display_point.row() < visible_row_range.start {
8211 return self.render_edit_prediction_scroll_popover(
8212 |_| SCROLL_PADDING_Y,
8213 IconName::ArrowUp,
8214 visible_row_range,
8215 line_layouts,
8216 newest_selection_head,
8217 scrolled_content_origin,
8218 window,
8219 cx,
8220 );
8221 } else if target_display_point.row() >= visible_row_range.end {
8222 return self.render_edit_prediction_scroll_popover(
8223 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8224 IconName::ArrowDown,
8225 visible_row_range,
8226 line_layouts,
8227 newest_selection_head,
8228 scrolled_content_origin,
8229 window,
8230 cx,
8231 );
8232 }
8233
8234 const POLE_WIDTH: Pixels = px(2.);
8235
8236 let line_layout =
8237 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8238 let target_column = target_display_point.column() as usize;
8239
8240 let target_x = line_layout.x_for_index(target_column);
8241 let target_y =
8242 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8243
8244 let flag_on_right = target_x < text_bounds.size.width / 2.;
8245
8246 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8247 border_color.l += 0.001;
8248
8249 let mut element = v_flex()
8250 .items_end()
8251 .when(flag_on_right, |el| el.items_start())
8252 .child(if flag_on_right {
8253 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8254 .rounded_bl(px(0.))
8255 .rounded_tl(px(0.))
8256 .border_l_2()
8257 .border_color(border_color)
8258 } else {
8259 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8260 .rounded_br(px(0.))
8261 .rounded_tr(px(0.))
8262 .border_r_2()
8263 .border_color(border_color)
8264 })
8265 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8266 .into_any();
8267
8268 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8269
8270 let mut origin = scrolled_content_origin + point(target_x, target_y)
8271 - point(
8272 if flag_on_right {
8273 POLE_WIDTH
8274 } else {
8275 size.width - POLE_WIDTH
8276 },
8277 size.height - line_height,
8278 );
8279
8280 origin.x = origin.x.max(content_origin.x);
8281
8282 element.prepaint_at(origin, window, cx);
8283
8284 Some((element, origin))
8285 }
8286
8287 fn render_edit_prediction_scroll_popover(
8288 &mut self,
8289 to_y: impl Fn(Size<Pixels>) -> Pixels,
8290 scroll_icon: IconName,
8291 visible_row_range: Range<DisplayRow>,
8292 line_layouts: &[LineWithInvisibles],
8293 newest_selection_head: Option<DisplayPoint>,
8294 scrolled_content_origin: gpui::Point<Pixels>,
8295 window: &mut Window,
8296 cx: &mut App,
8297 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8298 let mut element = self
8299 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8300 .into_any();
8301
8302 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8303
8304 let cursor = newest_selection_head?;
8305 let cursor_row_layout =
8306 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8307 let cursor_column = cursor.column() as usize;
8308
8309 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8310
8311 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8312
8313 element.prepaint_at(origin, window, cx);
8314 Some((element, origin))
8315 }
8316
8317 fn render_edit_prediction_eager_jump_popover(
8318 &mut self,
8319 text_bounds: &Bounds<Pixels>,
8320 content_origin: gpui::Point<Pixels>,
8321 editor_snapshot: &EditorSnapshot,
8322 visible_row_range: Range<DisplayRow>,
8323 scroll_top: f32,
8324 scroll_bottom: f32,
8325 line_height: Pixels,
8326 scroll_pixel_position: gpui::Point<Pixels>,
8327 target_display_point: DisplayPoint,
8328 editor_width: Pixels,
8329 window: &mut Window,
8330 cx: &mut App,
8331 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8332 if target_display_point.row().as_f32() < scroll_top {
8333 let mut element = self
8334 .render_edit_prediction_line_popover(
8335 "Jump to Edit",
8336 Some(IconName::ArrowUp),
8337 window,
8338 cx,
8339 )?
8340 .into_any();
8341
8342 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8343 let offset = point(
8344 (text_bounds.size.width - size.width) / 2.,
8345 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8346 );
8347
8348 let origin = text_bounds.origin + offset;
8349 element.prepaint_at(origin, window, cx);
8350 Some((element, origin))
8351 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8352 let mut element = self
8353 .render_edit_prediction_line_popover(
8354 "Jump to Edit",
8355 Some(IconName::ArrowDown),
8356 window,
8357 cx,
8358 )?
8359 .into_any();
8360
8361 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8362 let offset = point(
8363 (text_bounds.size.width - size.width) / 2.,
8364 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8365 );
8366
8367 let origin = text_bounds.origin + offset;
8368 element.prepaint_at(origin, window, cx);
8369 Some((element, origin))
8370 } else {
8371 self.render_edit_prediction_end_of_line_popover(
8372 "Jump to Edit",
8373 editor_snapshot,
8374 visible_row_range,
8375 target_display_point,
8376 line_height,
8377 scroll_pixel_position,
8378 content_origin,
8379 editor_width,
8380 window,
8381 cx,
8382 )
8383 }
8384 }
8385
8386 fn render_edit_prediction_end_of_line_popover(
8387 self: &mut Editor,
8388 label: &'static str,
8389 editor_snapshot: &EditorSnapshot,
8390 visible_row_range: Range<DisplayRow>,
8391 target_display_point: DisplayPoint,
8392 line_height: Pixels,
8393 scroll_pixel_position: gpui::Point<Pixels>,
8394 content_origin: gpui::Point<Pixels>,
8395 editor_width: Pixels,
8396 window: &mut Window,
8397 cx: &mut App,
8398 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8399 let target_line_end = DisplayPoint::new(
8400 target_display_point.row(),
8401 editor_snapshot.line_len(target_display_point.row()),
8402 );
8403
8404 let mut element = self
8405 .render_edit_prediction_line_popover(label, None, window, cx)?
8406 .into_any();
8407
8408 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8409
8410 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8411
8412 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8413 let mut origin = start_point
8414 + line_origin
8415 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8416 origin.x = origin.x.max(content_origin.x);
8417
8418 let max_x = content_origin.x + editor_width - size.width;
8419
8420 if origin.x > max_x {
8421 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8422
8423 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8424 origin.y += offset;
8425 IconName::ArrowUp
8426 } else {
8427 origin.y -= offset;
8428 IconName::ArrowDown
8429 };
8430
8431 element = self
8432 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8433 .into_any();
8434
8435 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8436
8437 origin.x = content_origin.x + editor_width - size.width - px(2.);
8438 }
8439
8440 element.prepaint_at(origin, window, cx);
8441 Some((element, origin))
8442 }
8443
8444 fn render_edit_prediction_diff_popover(
8445 self: &Editor,
8446 text_bounds: &Bounds<Pixels>,
8447 content_origin: gpui::Point<Pixels>,
8448 right_margin: Pixels,
8449 editor_snapshot: &EditorSnapshot,
8450 visible_row_range: Range<DisplayRow>,
8451 line_layouts: &[LineWithInvisibles],
8452 line_height: Pixels,
8453 scroll_pixel_position: gpui::Point<Pixels>,
8454 newest_selection_head: Option<DisplayPoint>,
8455 editor_width: Pixels,
8456 style: &EditorStyle,
8457 edits: &Vec<(Range<Anchor>, String)>,
8458 edit_preview: &Option<language::EditPreview>,
8459 snapshot: &language::BufferSnapshot,
8460 window: &mut Window,
8461 cx: &mut App,
8462 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8463 let edit_start = edits
8464 .first()
8465 .unwrap()
8466 .0
8467 .start
8468 .to_display_point(editor_snapshot);
8469 let edit_end = edits
8470 .last()
8471 .unwrap()
8472 .0
8473 .end
8474 .to_display_point(editor_snapshot);
8475
8476 let is_visible = visible_row_range.contains(&edit_start.row())
8477 || visible_row_range.contains(&edit_end.row());
8478 if !is_visible {
8479 return None;
8480 }
8481
8482 let highlighted_edits =
8483 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8484
8485 let styled_text = highlighted_edits.to_styled_text(&style.text);
8486 let line_count = highlighted_edits.text.lines().count();
8487
8488 const BORDER_WIDTH: Pixels = px(1.);
8489
8490 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8491 let has_keybind = keybind.is_some();
8492
8493 let mut element = h_flex()
8494 .items_start()
8495 .child(
8496 h_flex()
8497 .bg(cx.theme().colors().editor_background)
8498 .border(BORDER_WIDTH)
8499 .shadow_sm()
8500 .border_color(cx.theme().colors().border)
8501 .rounded_l_lg()
8502 .when(line_count > 1, |el| el.rounded_br_lg())
8503 .pr_1()
8504 .child(styled_text),
8505 )
8506 .child(
8507 h_flex()
8508 .h(line_height + BORDER_WIDTH * 2.)
8509 .px_1p5()
8510 .gap_1()
8511 // Workaround: For some reason, there's a gap if we don't do this
8512 .ml(-BORDER_WIDTH)
8513 .shadow(vec![gpui::BoxShadow {
8514 color: gpui::black().opacity(0.05),
8515 offset: point(px(1.), px(1.)),
8516 blur_radius: px(2.),
8517 spread_radius: px(0.),
8518 }])
8519 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8520 .border(BORDER_WIDTH)
8521 .border_color(cx.theme().colors().border)
8522 .rounded_r_lg()
8523 .id("edit_prediction_diff_popover_keybind")
8524 .when(!has_keybind, |el| {
8525 let status_colors = cx.theme().status();
8526
8527 el.bg(status_colors.error_background)
8528 .border_color(status_colors.error.opacity(0.6))
8529 .child(Icon::new(IconName::Info).color(Color::Error))
8530 .cursor_default()
8531 .hoverable_tooltip(move |_window, cx| {
8532 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8533 })
8534 })
8535 .children(keybind),
8536 )
8537 .into_any();
8538
8539 let longest_row =
8540 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8541 let longest_line_width = if visible_row_range.contains(&longest_row) {
8542 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8543 } else {
8544 layout_line(
8545 longest_row,
8546 editor_snapshot,
8547 style,
8548 editor_width,
8549 |_| false,
8550 window,
8551 cx,
8552 )
8553 .width
8554 };
8555
8556 let viewport_bounds =
8557 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8558 right: -right_margin,
8559 ..Default::default()
8560 });
8561
8562 let x_after_longest =
8563 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8564 - scroll_pixel_position.x;
8565
8566 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8567
8568 // Fully visible if it can be displayed within the window (allow overlapping other
8569 // panes). However, this is only allowed if the popover starts within text_bounds.
8570 let can_position_to_the_right = x_after_longest < text_bounds.right()
8571 && x_after_longest + element_bounds.width < viewport_bounds.right();
8572
8573 let mut origin = if can_position_to_the_right {
8574 point(
8575 x_after_longest,
8576 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8577 - scroll_pixel_position.y,
8578 )
8579 } else {
8580 let cursor_row = newest_selection_head.map(|head| head.row());
8581 let above_edit = edit_start
8582 .row()
8583 .0
8584 .checked_sub(line_count as u32)
8585 .map(DisplayRow);
8586 let below_edit = Some(edit_end.row() + 1);
8587 let above_cursor =
8588 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8589 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8590
8591 // Place the edit popover adjacent to the edit if there is a location
8592 // available that is onscreen and does not obscure the cursor. Otherwise,
8593 // place it adjacent to the cursor.
8594 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8595 .into_iter()
8596 .flatten()
8597 .find(|&start_row| {
8598 let end_row = start_row + line_count as u32;
8599 visible_row_range.contains(&start_row)
8600 && visible_row_range.contains(&end_row)
8601 && cursor_row.map_or(true, |cursor_row| {
8602 !((start_row..end_row).contains(&cursor_row))
8603 })
8604 })?;
8605
8606 content_origin
8607 + point(
8608 -scroll_pixel_position.x,
8609 row_target.as_f32() * line_height - scroll_pixel_position.y,
8610 )
8611 };
8612
8613 origin.x -= BORDER_WIDTH;
8614
8615 window.defer_draw(element, origin, 1);
8616
8617 // Do not return an element, since it will already be drawn due to defer_draw.
8618 None
8619 }
8620
8621 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8622 px(30.)
8623 }
8624
8625 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8626 if self.read_only(cx) {
8627 cx.theme().players().read_only()
8628 } else {
8629 self.style.as_ref().unwrap().local_player
8630 }
8631 }
8632
8633 fn render_edit_prediction_accept_keybind(
8634 &self,
8635 window: &mut Window,
8636 cx: &App,
8637 ) -> Option<AnyElement> {
8638 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8639 let accept_keystroke = accept_binding.keystroke()?;
8640
8641 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8642
8643 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8644 Color::Accent
8645 } else {
8646 Color::Muted
8647 };
8648
8649 h_flex()
8650 .px_0p5()
8651 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8652 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8653 .text_size(TextSize::XSmall.rems(cx))
8654 .child(h_flex().children(ui::render_modifiers(
8655 &accept_keystroke.modifiers,
8656 PlatformStyle::platform(),
8657 Some(modifiers_color),
8658 Some(IconSize::XSmall.rems().into()),
8659 true,
8660 )))
8661 .when(is_platform_style_mac, |parent| {
8662 parent.child(accept_keystroke.key.clone())
8663 })
8664 .when(!is_platform_style_mac, |parent| {
8665 parent.child(
8666 Key::new(
8667 util::capitalize(&accept_keystroke.key),
8668 Some(Color::Default),
8669 )
8670 .size(Some(IconSize::XSmall.rems().into())),
8671 )
8672 })
8673 .into_any()
8674 .into()
8675 }
8676
8677 fn render_edit_prediction_line_popover(
8678 &self,
8679 label: impl Into<SharedString>,
8680 icon: Option<IconName>,
8681 window: &mut Window,
8682 cx: &App,
8683 ) -> Option<Stateful<Div>> {
8684 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8685
8686 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8687 let has_keybind = keybind.is_some();
8688
8689 let result = h_flex()
8690 .id("ep-line-popover")
8691 .py_0p5()
8692 .pl_1()
8693 .pr(padding_right)
8694 .gap_1()
8695 .rounded_md()
8696 .border_1()
8697 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8698 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8699 .shadow_sm()
8700 .when(!has_keybind, |el| {
8701 let status_colors = cx.theme().status();
8702
8703 el.bg(status_colors.error_background)
8704 .border_color(status_colors.error.opacity(0.6))
8705 .pl_2()
8706 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8707 .cursor_default()
8708 .hoverable_tooltip(move |_window, cx| {
8709 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8710 })
8711 })
8712 .children(keybind)
8713 .child(
8714 Label::new(label)
8715 .size(LabelSize::Small)
8716 .when(!has_keybind, |el| {
8717 el.color(cx.theme().status().error.into()).strikethrough()
8718 }),
8719 )
8720 .when(!has_keybind, |el| {
8721 el.child(
8722 h_flex().ml_1().child(
8723 Icon::new(IconName::Info)
8724 .size(IconSize::Small)
8725 .color(cx.theme().status().error.into()),
8726 ),
8727 )
8728 })
8729 .when_some(icon, |element, icon| {
8730 element.child(
8731 div()
8732 .mt(px(1.5))
8733 .child(Icon::new(icon).size(IconSize::Small)),
8734 )
8735 });
8736
8737 Some(result)
8738 }
8739
8740 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8741 let accent_color = cx.theme().colors().text_accent;
8742 let editor_bg_color = cx.theme().colors().editor_background;
8743 editor_bg_color.blend(accent_color.opacity(0.1))
8744 }
8745
8746 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8747 let accent_color = cx.theme().colors().text_accent;
8748 let editor_bg_color = cx.theme().colors().editor_background;
8749 editor_bg_color.blend(accent_color.opacity(0.6))
8750 }
8751
8752 fn render_edit_prediction_cursor_popover(
8753 &self,
8754 min_width: Pixels,
8755 max_width: Pixels,
8756 cursor_point: Point,
8757 style: &EditorStyle,
8758 accept_keystroke: Option<&gpui::Keystroke>,
8759 _window: &Window,
8760 cx: &mut Context<Editor>,
8761 ) -> Option<AnyElement> {
8762 let provider = self.edit_prediction_provider.as_ref()?;
8763
8764 if provider.provider.needs_terms_acceptance(cx) {
8765 return Some(
8766 h_flex()
8767 .min_w(min_width)
8768 .flex_1()
8769 .px_2()
8770 .py_1()
8771 .gap_3()
8772 .elevation_2(cx)
8773 .hover(|style| style.bg(cx.theme().colors().element_hover))
8774 .id("accept-terms")
8775 .cursor_pointer()
8776 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8777 .on_click(cx.listener(|this, _event, window, cx| {
8778 cx.stop_propagation();
8779 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8780 window.dispatch_action(
8781 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8782 cx,
8783 );
8784 }))
8785 .child(
8786 h_flex()
8787 .flex_1()
8788 .gap_2()
8789 .child(Icon::new(IconName::ZedPredict))
8790 .child(Label::new("Accept Terms of Service"))
8791 .child(div().w_full())
8792 .child(
8793 Icon::new(IconName::ArrowUpRight)
8794 .color(Color::Muted)
8795 .size(IconSize::Small),
8796 )
8797 .into_any_element(),
8798 )
8799 .into_any(),
8800 );
8801 }
8802
8803 let is_refreshing = provider.provider.is_refreshing(cx);
8804
8805 fn pending_completion_container() -> Div {
8806 h_flex()
8807 .h_full()
8808 .flex_1()
8809 .gap_2()
8810 .child(Icon::new(IconName::ZedPredict))
8811 }
8812
8813 let completion = match &self.active_inline_completion {
8814 Some(prediction) => {
8815 if !self.has_visible_completions_menu() {
8816 const RADIUS: Pixels = px(6.);
8817 const BORDER_WIDTH: Pixels = px(1.);
8818
8819 return Some(
8820 h_flex()
8821 .elevation_2(cx)
8822 .border(BORDER_WIDTH)
8823 .border_color(cx.theme().colors().border)
8824 .when(accept_keystroke.is_none(), |el| {
8825 el.border_color(cx.theme().status().error)
8826 })
8827 .rounded(RADIUS)
8828 .rounded_tl(px(0.))
8829 .overflow_hidden()
8830 .child(div().px_1p5().child(match &prediction.completion {
8831 InlineCompletion::Move { target, snapshot } => {
8832 use text::ToPoint as _;
8833 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8834 {
8835 Icon::new(IconName::ZedPredictDown)
8836 } else {
8837 Icon::new(IconName::ZedPredictUp)
8838 }
8839 }
8840 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8841 }))
8842 .child(
8843 h_flex()
8844 .gap_1()
8845 .py_1()
8846 .px_2()
8847 .rounded_r(RADIUS - BORDER_WIDTH)
8848 .border_l_1()
8849 .border_color(cx.theme().colors().border)
8850 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8851 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8852 el.child(
8853 Label::new("Hold")
8854 .size(LabelSize::Small)
8855 .when(accept_keystroke.is_none(), |el| {
8856 el.strikethrough()
8857 })
8858 .line_height_style(LineHeightStyle::UiLabel),
8859 )
8860 })
8861 .id("edit_prediction_cursor_popover_keybind")
8862 .when(accept_keystroke.is_none(), |el| {
8863 let status_colors = cx.theme().status();
8864
8865 el.bg(status_colors.error_background)
8866 .border_color(status_colors.error.opacity(0.6))
8867 .child(Icon::new(IconName::Info).color(Color::Error))
8868 .cursor_default()
8869 .hoverable_tooltip(move |_window, cx| {
8870 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8871 .into()
8872 })
8873 })
8874 .when_some(
8875 accept_keystroke.as_ref(),
8876 |el, accept_keystroke| {
8877 el.child(h_flex().children(ui::render_modifiers(
8878 &accept_keystroke.modifiers,
8879 PlatformStyle::platform(),
8880 Some(Color::Default),
8881 Some(IconSize::XSmall.rems().into()),
8882 false,
8883 )))
8884 },
8885 ),
8886 )
8887 .into_any(),
8888 );
8889 }
8890
8891 self.render_edit_prediction_cursor_popover_preview(
8892 prediction,
8893 cursor_point,
8894 style,
8895 cx,
8896 )?
8897 }
8898
8899 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8900 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8901 stale_completion,
8902 cursor_point,
8903 style,
8904 cx,
8905 )?,
8906
8907 None => {
8908 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8909 }
8910 },
8911
8912 None => pending_completion_container().child(Label::new("No Prediction")),
8913 };
8914
8915 let completion = if is_refreshing {
8916 completion
8917 .with_animation(
8918 "loading-completion",
8919 Animation::new(Duration::from_secs(2))
8920 .repeat()
8921 .with_easing(pulsating_between(0.4, 0.8)),
8922 |label, delta| label.opacity(delta),
8923 )
8924 .into_any_element()
8925 } else {
8926 completion.into_any_element()
8927 };
8928
8929 let has_completion = self.active_inline_completion.is_some();
8930
8931 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8932 Some(
8933 h_flex()
8934 .min_w(min_width)
8935 .max_w(max_width)
8936 .flex_1()
8937 .elevation_2(cx)
8938 .border_color(cx.theme().colors().border)
8939 .child(
8940 div()
8941 .flex_1()
8942 .py_1()
8943 .px_2()
8944 .overflow_hidden()
8945 .child(completion),
8946 )
8947 .when_some(accept_keystroke, |el, accept_keystroke| {
8948 if !accept_keystroke.modifiers.modified() {
8949 return el;
8950 }
8951
8952 el.child(
8953 h_flex()
8954 .h_full()
8955 .border_l_1()
8956 .rounded_r_lg()
8957 .border_color(cx.theme().colors().border)
8958 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8959 .gap_1()
8960 .py_1()
8961 .px_2()
8962 .child(
8963 h_flex()
8964 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8965 .when(is_platform_style_mac, |parent| parent.gap_1())
8966 .child(h_flex().children(ui::render_modifiers(
8967 &accept_keystroke.modifiers,
8968 PlatformStyle::platform(),
8969 Some(if !has_completion {
8970 Color::Muted
8971 } else {
8972 Color::Default
8973 }),
8974 None,
8975 false,
8976 ))),
8977 )
8978 .child(Label::new("Preview").into_any_element())
8979 .opacity(if has_completion { 1.0 } else { 0.4 }),
8980 )
8981 })
8982 .into_any(),
8983 )
8984 }
8985
8986 fn render_edit_prediction_cursor_popover_preview(
8987 &self,
8988 completion: &InlineCompletionState,
8989 cursor_point: Point,
8990 style: &EditorStyle,
8991 cx: &mut Context<Editor>,
8992 ) -> Option<Div> {
8993 use text::ToPoint as _;
8994
8995 fn render_relative_row_jump(
8996 prefix: impl Into<String>,
8997 current_row: u32,
8998 target_row: u32,
8999 ) -> Div {
9000 let (row_diff, arrow) = if target_row < current_row {
9001 (current_row - target_row, IconName::ArrowUp)
9002 } else {
9003 (target_row - current_row, IconName::ArrowDown)
9004 };
9005
9006 h_flex()
9007 .child(
9008 Label::new(format!("{}{}", prefix.into(), row_diff))
9009 .color(Color::Muted)
9010 .size(LabelSize::Small),
9011 )
9012 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9013 }
9014
9015 match &completion.completion {
9016 InlineCompletion::Move {
9017 target, snapshot, ..
9018 } => Some(
9019 h_flex()
9020 .px_2()
9021 .gap_2()
9022 .flex_1()
9023 .child(
9024 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9025 Icon::new(IconName::ZedPredictDown)
9026 } else {
9027 Icon::new(IconName::ZedPredictUp)
9028 },
9029 )
9030 .child(Label::new("Jump to Edit")),
9031 ),
9032
9033 InlineCompletion::Edit {
9034 edits,
9035 edit_preview,
9036 snapshot,
9037 display_mode: _,
9038 } => {
9039 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9040
9041 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9042 &snapshot,
9043 &edits,
9044 edit_preview.as_ref()?,
9045 true,
9046 cx,
9047 )
9048 .first_line_preview();
9049
9050 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9051 .with_default_highlights(&style.text, highlighted_edits.highlights);
9052
9053 let preview = h_flex()
9054 .gap_1()
9055 .min_w_16()
9056 .child(styled_text)
9057 .when(has_more_lines, |parent| parent.child("…"));
9058
9059 let left = if first_edit_row != cursor_point.row {
9060 render_relative_row_jump("", cursor_point.row, first_edit_row)
9061 .into_any_element()
9062 } else {
9063 Icon::new(IconName::ZedPredict).into_any_element()
9064 };
9065
9066 Some(
9067 h_flex()
9068 .h_full()
9069 .flex_1()
9070 .gap_2()
9071 .pr_1()
9072 .overflow_x_hidden()
9073 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9074 .child(left)
9075 .child(preview),
9076 )
9077 }
9078 }
9079 }
9080
9081 pub fn render_context_menu(
9082 &self,
9083 style: &EditorStyle,
9084 max_height_in_lines: u32,
9085 window: &mut Window,
9086 cx: &mut Context<Editor>,
9087 ) -> Option<AnyElement> {
9088 let menu = self.context_menu.borrow();
9089 let menu = menu.as_ref()?;
9090 if !menu.visible() {
9091 return None;
9092 };
9093 Some(menu.render(style, max_height_in_lines, window, cx))
9094 }
9095
9096 fn render_context_menu_aside(
9097 &mut self,
9098 max_size: Size<Pixels>,
9099 window: &mut Window,
9100 cx: &mut Context<Editor>,
9101 ) -> Option<AnyElement> {
9102 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9103 if menu.visible() {
9104 menu.render_aside(max_size, window, cx)
9105 } else {
9106 None
9107 }
9108 })
9109 }
9110
9111 fn hide_context_menu(
9112 &mut self,
9113 window: &mut Window,
9114 cx: &mut Context<Self>,
9115 ) -> Option<CodeContextMenu> {
9116 cx.notify();
9117 self.completion_tasks.clear();
9118 let context_menu = self.context_menu.borrow_mut().take();
9119 self.stale_inline_completion_in_menu.take();
9120 self.update_visible_inline_completion(window, cx);
9121 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9122 if let Some(completion_provider) = &self.completion_provider {
9123 completion_provider.selection_changed(None, window, cx);
9124 }
9125 }
9126 context_menu
9127 }
9128
9129 fn show_snippet_choices(
9130 &mut self,
9131 choices: &Vec<String>,
9132 selection: Range<Anchor>,
9133 cx: &mut Context<Self>,
9134 ) {
9135 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9136 (Some(a), Some(b)) if a == b => a,
9137 _ => {
9138 log::error!("expected anchor range to have matching buffer IDs");
9139 return;
9140 }
9141 };
9142 let multi_buffer = self.buffer().read(cx);
9143 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9144 return;
9145 };
9146
9147 let id = post_inc(&mut self.next_completion_id);
9148 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9149 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9150 CompletionsMenu::new_snippet_choices(
9151 id,
9152 true,
9153 choices,
9154 selection,
9155 buffer,
9156 snippet_sort_order,
9157 ),
9158 ));
9159 }
9160
9161 pub fn insert_snippet(
9162 &mut self,
9163 insertion_ranges: &[Range<usize>],
9164 snippet: Snippet,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) -> Result<()> {
9168 struct Tabstop<T> {
9169 is_end_tabstop: bool,
9170 ranges: Vec<Range<T>>,
9171 choices: Option<Vec<String>>,
9172 }
9173
9174 let tabstops = self.buffer.update(cx, |buffer, cx| {
9175 let snippet_text: Arc<str> = snippet.text.clone().into();
9176 let edits = insertion_ranges
9177 .iter()
9178 .cloned()
9179 .map(|range| (range, snippet_text.clone()));
9180 let autoindent_mode = AutoindentMode::Block {
9181 original_indent_columns: Vec::new(),
9182 };
9183 buffer.edit(edits, Some(autoindent_mode), cx);
9184
9185 let snapshot = &*buffer.read(cx);
9186 let snippet = &snippet;
9187 snippet
9188 .tabstops
9189 .iter()
9190 .map(|tabstop| {
9191 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9192 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9193 });
9194 let mut tabstop_ranges = tabstop
9195 .ranges
9196 .iter()
9197 .flat_map(|tabstop_range| {
9198 let mut delta = 0_isize;
9199 insertion_ranges.iter().map(move |insertion_range| {
9200 let insertion_start = insertion_range.start as isize + delta;
9201 delta +=
9202 snippet.text.len() as isize - insertion_range.len() as isize;
9203
9204 let start = ((insertion_start + tabstop_range.start) as usize)
9205 .min(snapshot.len());
9206 let end = ((insertion_start + tabstop_range.end) as usize)
9207 .min(snapshot.len());
9208 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9209 })
9210 })
9211 .collect::<Vec<_>>();
9212 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9213
9214 Tabstop {
9215 is_end_tabstop,
9216 ranges: tabstop_ranges,
9217 choices: tabstop.choices.clone(),
9218 }
9219 })
9220 .collect::<Vec<_>>()
9221 });
9222 if let Some(tabstop) = tabstops.first() {
9223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9224 // Reverse order so that the first range is the newest created selection.
9225 // Completions will use it and autoscroll will prioritize it.
9226 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9227 });
9228
9229 if let Some(choices) = &tabstop.choices {
9230 if let Some(selection) = tabstop.ranges.first() {
9231 self.show_snippet_choices(choices, selection.clone(), cx)
9232 }
9233 }
9234
9235 // If we're already at the last tabstop and it's at the end of the snippet,
9236 // we're done, we don't need to keep the state around.
9237 if !tabstop.is_end_tabstop {
9238 let choices = tabstops
9239 .iter()
9240 .map(|tabstop| tabstop.choices.clone())
9241 .collect();
9242
9243 let ranges = tabstops
9244 .into_iter()
9245 .map(|tabstop| tabstop.ranges)
9246 .collect::<Vec<_>>();
9247
9248 self.snippet_stack.push(SnippetState {
9249 active_index: 0,
9250 ranges,
9251 choices,
9252 });
9253 }
9254
9255 // Check whether the just-entered snippet ends with an auto-closable bracket.
9256 if self.autoclose_regions.is_empty() {
9257 let snapshot = self.buffer.read(cx).snapshot(cx);
9258 for selection in &mut self.selections.all::<Point>(cx) {
9259 let selection_head = selection.head();
9260 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9261 continue;
9262 };
9263
9264 let mut bracket_pair = None;
9265 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9266 let prev_chars = snapshot
9267 .reversed_chars_at(selection_head)
9268 .collect::<String>();
9269 for (pair, enabled) in scope.brackets() {
9270 if enabled
9271 && pair.close
9272 && prev_chars.starts_with(pair.start.as_str())
9273 && next_chars.starts_with(pair.end.as_str())
9274 {
9275 bracket_pair = Some(pair.clone());
9276 break;
9277 }
9278 }
9279 if let Some(pair) = bracket_pair {
9280 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9281 let autoclose_enabled =
9282 self.use_autoclose && snapshot_settings.use_autoclose;
9283 if autoclose_enabled {
9284 let start = snapshot.anchor_after(selection_head);
9285 let end = snapshot.anchor_after(selection_head);
9286 self.autoclose_regions.push(AutocloseRegion {
9287 selection_id: selection.id,
9288 range: start..end,
9289 pair,
9290 });
9291 }
9292 }
9293 }
9294 }
9295 }
9296 Ok(())
9297 }
9298
9299 pub fn move_to_next_snippet_tabstop(
9300 &mut self,
9301 window: &mut Window,
9302 cx: &mut Context<Self>,
9303 ) -> bool {
9304 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9305 }
9306
9307 pub fn move_to_prev_snippet_tabstop(
9308 &mut self,
9309 window: &mut Window,
9310 cx: &mut Context<Self>,
9311 ) -> bool {
9312 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9313 }
9314
9315 pub fn move_to_snippet_tabstop(
9316 &mut self,
9317 bias: Bias,
9318 window: &mut Window,
9319 cx: &mut Context<Self>,
9320 ) -> bool {
9321 if let Some(mut snippet) = self.snippet_stack.pop() {
9322 match bias {
9323 Bias::Left => {
9324 if snippet.active_index > 0 {
9325 snippet.active_index -= 1;
9326 } else {
9327 self.snippet_stack.push(snippet);
9328 return false;
9329 }
9330 }
9331 Bias::Right => {
9332 if snippet.active_index + 1 < snippet.ranges.len() {
9333 snippet.active_index += 1;
9334 } else {
9335 self.snippet_stack.push(snippet);
9336 return false;
9337 }
9338 }
9339 }
9340 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9341 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9342 // Reverse order so that the first range is the newest created selection.
9343 // Completions will use it and autoscroll will prioritize it.
9344 s.select_ranges(current_ranges.iter().rev().cloned())
9345 });
9346
9347 if let Some(choices) = &snippet.choices[snippet.active_index] {
9348 if let Some(selection) = current_ranges.first() {
9349 self.show_snippet_choices(&choices, selection.clone(), cx);
9350 }
9351 }
9352
9353 // If snippet state is not at the last tabstop, push it back on the stack
9354 if snippet.active_index + 1 < snippet.ranges.len() {
9355 self.snippet_stack.push(snippet);
9356 }
9357 return true;
9358 }
9359 }
9360
9361 false
9362 }
9363
9364 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9365 self.transact(window, cx, |this, window, cx| {
9366 this.select_all(&SelectAll, window, cx);
9367 this.insert("", window, cx);
9368 });
9369 }
9370
9371 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9373 self.transact(window, cx, |this, window, cx| {
9374 this.select_autoclose_pair(window, cx);
9375 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9376 if !this.linked_edit_ranges.is_empty() {
9377 let selections = this.selections.all::<MultiBufferPoint>(cx);
9378 let snapshot = this.buffer.read(cx).snapshot(cx);
9379
9380 for selection in selections.iter() {
9381 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9382 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9383 if selection_start.buffer_id != selection_end.buffer_id {
9384 continue;
9385 }
9386 if let Some(ranges) =
9387 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9388 {
9389 for (buffer, entries) in ranges {
9390 linked_ranges.entry(buffer).or_default().extend(entries);
9391 }
9392 }
9393 }
9394 }
9395
9396 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9397 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9398 for selection in &mut selections {
9399 if selection.is_empty() {
9400 let old_head = selection.head();
9401 let mut new_head =
9402 movement::left(&display_map, old_head.to_display_point(&display_map))
9403 .to_point(&display_map);
9404 if let Some((buffer, line_buffer_range)) = display_map
9405 .buffer_snapshot
9406 .buffer_line_for_row(MultiBufferRow(old_head.row))
9407 {
9408 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9409 let indent_len = match indent_size.kind {
9410 IndentKind::Space => {
9411 buffer.settings_at(line_buffer_range.start, cx).tab_size
9412 }
9413 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9414 };
9415 if old_head.column <= indent_size.len && old_head.column > 0 {
9416 let indent_len = indent_len.get();
9417 new_head = cmp::min(
9418 new_head,
9419 MultiBufferPoint::new(
9420 old_head.row,
9421 ((old_head.column - 1) / indent_len) * indent_len,
9422 ),
9423 );
9424 }
9425 }
9426
9427 selection.set_head(new_head, SelectionGoal::None);
9428 }
9429 }
9430
9431 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9432 s.select(selections)
9433 });
9434 this.insert("", window, cx);
9435 let empty_str: Arc<str> = Arc::from("");
9436 for (buffer, edits) in linked_ranges {
9437 let snapshot = buffer.read(cx).snapshot();
9438 use text::ToPoint as TP;
9439
9440 let edits = edits
9441 .into_iter()
9442 .map(|range| {
9443 let end_point = TP::to_point(&range.end, &snapshot);
9444 let mut start_point = TP::to_point(&range.start, &snapshot);
9445
9446 if end_point == start_point {
9447 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9448 .saturating_sub(1);
9449 start_point =
9450 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9451 };
9452
9453 (start_point..end_point, empty_str.clone())
9454 })
9455 .sorted_by_key(|(range, _)| range.start)
9456 .collect::<Vec<_>>();
9457 buffer.update(cx, |this, cx| {
9458 this.edit(edits, None, cx);
9459 })
9460 }
9461 this.refresh_inline_completion(true, false, window, cx);
9462 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9463 });
9464 }
9465
9466 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9468 self.transact(window, cx, |this, window, cx| {
9469 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9470 s.move_with(|map, selection| {
9471 if selection.is_empty() {
9472 let cursor = movement::right(map, selection.head());
9473 selection.end = cursor;
9474 selection.reversed = true;
9475 selection.goal = SelectionGoal::None;
9476 }
9477 })
9478 });
9479 this.insert("", window, cx);
9480 this.refresh_inline_completion(true, false, window, cx);
9481 });
9482 }
9483
9484 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9486 if self.move_to_prev_snippet_tabstop(window, cx) {
9487 return;
9488 }
9489 self.outdent(&Outdent, window, cx);
9490 }
9491
9492 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9493 if self.move_to_next_snippet_tabstop(window, cx) {
9494 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9495 return;
9496 }
9497 if self.read_only(cx) {
9498 return;
9499 }
9500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9501 let mut selections = self.selections.all_adjusted(cx);
9502 let buffer = self.buffer.read(cx);
9503 let snapshot = buffer.snapshot(cx);
9504 let rows_iter = selections.iter().map(|s| s.head().row);
9505 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9506
9507 let has_some_cursor_in_whitespace = selections
9508 .iter()
9509 .filter(|selection| selection.is_empty())
9510 .any(|selection| {
9511 let cursor = selection.head();
9512 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9513 cursor.column < current_indent.len
9514 });
9515
9516 let mut edits = Vec::new();
9517 let mut prev_edited_row = 0;
9518 let mut row_delta = 0;
9519 for selection in &mut selections {
9520 if selection.start.row != prev_edited_row {
9521 row_delta = 0;
9522 }
9523 prev_edited_row = selection.end.row;
9524
9525 // If the selection is non-empty, then increase the indentation of the selected lines.
9526 if !selection.is_empty() {
9527 row_delta =
9528 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9529 continue;
9530 }
9531
9532 let cursor = selection.head();
9533 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9534 if let Some(suggested_indent) =
9535 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9536 {
9537 // Don't do anything if already at suggested indent
9538 // and there is any other cursor which is not
9539 if has_some_cursor_in_whitespace
9540 && cursor.column == current_indent.len
9541 && current_indent.len == suggested_indent.len
9542 {
9543 continue;
9544 }
9545
9546 // Adjust line and move cursor to suggested indent
9547 // if cursor is not at suggested indent
9548 if cursor.column < suggested_indent.len
9549 && cursor.column <= current_indent.len
9550 && current_indent.len <= suggested_indent.len
9551 {
9552 selection.start = Point::new(cursor.row, suggested_indent.len);
9553 selection.end = selection.start;
9554 if row_delta == 0 {
9555 edits.extend(Buffer::edit_for_indent_size_adjustment(
9556 cursor.row,
9557 current_indent,
9558 suggested_indent,
9559 ));
9560 row_delta = suggested_indent.len - current_indent.len;
9561 }
9562 continue;
9563 }
9564
9565 // If current indent is more than suggested indent
9566 // only move cursor to current indent and skip indent
9567 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9568 selection.start = Point::new(cursor.row, current_indent.len);
9569 selection.end = selection.start;
9570 continue;
9571 }
9572 }
9573
9574 // Otherwise, insert a hard or soft tab.
9575 let settings = buffer.language_settings_at(cursor, cx);
9576 let tab_size = if settings.hard_tabs {
9577 IndentSize::tab()
9578 } else {
9579 let tab_size = settings.tab_size.get();
9580 let indent_remainder = snapshot
9581 .text_for_range(Point::new(cursor.row, 0)..cursor)
9582 .flat_map(str::chars)
9583 .fold(row_delta % tab_size, |counter: u32, c| {
9584 if c == '\t' {
9585 0
9586 } else {
9587 (counter + 1) % tab_size
9588 }
9589 });
9590
9591 let chars_to_next_tab_stop = tab_size - indent_remainder;
9592 IndentSize::spaces(chars_to_next_tab_stop)
9593 };
9594 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9595 selection.end = selection.start;
9596 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9597 row_delta += tab_size.len;
9598 }
9599
9600 self.transact(window, cx, |this, window, cx| {
9601 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9602 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9603 s.select(selections)
9604 });
9605 this.refresh_inline_completion(true, false, window, cx);
9606 });
9607 }
9608
9609 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9610 if self.read_only(cx) {
9611 return;
9612 }
9613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9614 let mut selections = self.selections.all::<Point>(cx);
9615 let mut prev_edited_row = 0;
9616 let mut row_delta = 0;
9617 let mut edits = Vec::new();
9618 let buffer = self.buffer.read(cx);
9619 let snapshot = buffer.snapshot(cx);
9620 for selection in &mut selections {
9621 if selection.start.row != prev_edited_row {
9622 row_delta = 0;
9623 }
9624 prev_edited_row = selection.end.row;
9625
9626 row_delta =
9627 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9628 }
9629
9630 self.transact(window, cx, |this, window, cx| {
9631 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9632 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9633 s.select(selections)
9634 });
9635 });
9636 }
9637
9638 fn indent_selection(
9639 buffer: &MultiBuffer,
9640 snapshot: &MultiBufferSnapshot,
9641 selection: &mut Selection<Point>,
9642 edits: &mut Vec<(Range<Point>, String)>,
9643 delta_for_start_row: u32,
9644 cx: &App,
9645 ) -> u32 {
9646 let settings = buffer.language_settings_at(selection.start, cx);
9647 let tab_size = settings.tab_size.get();
9648 let indent_kind = if settings.hard_tabs {
9649 IndentKind::Tab
9650 } else {
9651 IndentKind::Space
9652 };
9653 let mut start_row = selection.start.row;
9654 let mut end_row = selection.end.row + 1;
9655
9656 // If a selection ends at the beginning of a line, don't indent
9657 // that last line.
9658 if selection.end.column == 0 && selection.end.row > selection.start.row {
9659 end_row -= 1;
9660 }
9661
9662 // Avoid re-indenting a row that has already been indented by a
9663 // previous selection, but still update this selection's column
9664 // to reflect that indentation.
9665 if delta_for_start_row > 0 {
9666 start_row += 1;
9667 selection.start.column += delta_for_start_row;
9668 if selection.end.row == selection.start.row {
9669 selection.end.column += delta_for_start_row;
9670 }
9671 }
9672
9673 let mut delta_for_end_row = 0;
9674 let has_multiple_rows = start_row + 1 != end_row;
9675 for row in start_row..end_row {
9676 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9677 let indent_delta = match (current_indent.kind, indent_kind) {
9678 (IndentKind::Space, IndentKind::Space) => {
9679 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9680 IndentSize::spaces(columns_to_next_tab_stop)
9681 }
9682 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9683 (_, IndentKind::Tab) => IndentSize::tab(),
9684 };
9685
9686 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9687 0
9688 } else {
9689 selection.start.column
9690 };
9691 let row_start = Point::new(row, start);
9692 edits.push((
9693 row_start..row_start,
9694 indent_delta.chars().collect::<String>(),
9695 ));
9696
9697 // Update this selection's endpoints to reflect the indentation.
9698 if row == selection.start.row {
9699 selection.start.column += indent_delta.len;
9700 }
9701 if row == selection.end.row {
9702 selection.end.column += indent_delta.len;
9703 delta_for_end_row = indent_delta.len;
9704 }
9705 }
9706
9707 if selection.start.row == selection.end.row {
9708 delta_for_start_row + delta_for_end_row
9709 } else {
9710 delta_for_end_row
9711 }
9712 }
9713
9714 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9715 if self.read_only(cx) {
9716 return;
9717 }
9718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9719 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9720 let selections = self.selections.all::<Point>(cx);
9721 let mut deletion_ranges = Vec::new();
9722 let mut last_outdent = None;
9723 {
9724 let buffer = self.buffer.read(cx);
9725 let snapshot = buffer.snapshot(cx);
9726 for selection in &selections {
9727 let settings = buffer.language_settings_at(selection.start, cx);
9728 let tab_size = settings.tab_size.get();
9729 let mut rows = selection.spanned_rows(false, &display_map);
9730
9731 // Avoid re-outdenting a row that has already been outdented by a
9732 // previous selection.
9733 if let Some(last_row) = last_outdent {
9734 if last_row == rows.start {
9735 rows.start = rows.start.next_row();
9736 }
9737 }
9738 let has_multiple_rows = rows.len() > 1;
9739 for row in rows.iter_rows() {
9740 let indent_size = snapshot.indent_size_for_line(row);
9741 if indent_size.len > 0 {
9742 let deletion_len = match indent_size.kind {
9743 IndentKind::Space => {
9744 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9745 if columns_to_prev_tab_stop == 0 {
9746 tab_size
9747 } else {
9748 columns_to_prev_tab_stop
9749 }
9750 }
9751 IndentKind::Tab => 1,
9752 };
9753 let start = if has_multiple_rows
9754 || deletion_len > selection.start.column
9755 || indent_size.len < selection.start.column
9756 {
9757 0
9758 } else {
9759 selection.start.column - deletion_len
9760 };
9761 deletion_ranges.push(
9762 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9763 );
9764 last_outdent = Some(row);
9765 }
9766 }
9767 }
9768 }
9769
9770 self.transact(window, cx, |this, window, cx| {
9771 this.buffer.update(cx, |buffer, cx| {
9772 let empty_str: Arc<str> = Arc::default();
9773 buffer.edit(
9774 deletion_ranges
9775 .into_iter()
9776 .map(|range| (range, empty_str.clone())),
9777 None,
9778 cx,
9779 );
9780 });
9781 let selections = this.selections.all::<usize>(cx);
9782 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9783 s.select(selections)
9784 });
9785 });
9786 }
9787
9788 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9789 if self.read_only(cx) {
9790 return;
9791 }
9792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9793 let selections = self
9794 .selections
9795 .all::<usize>(cx)
9796 .into_iter()
9797 .map(|s| s.range());
9798
9799 self.transact(window, cx, |this, window, cx| {
9800 this.buffer.update(cx, |buffer, cx| {
9801 buffer.autoindent_ranges(selections, cx);
9802 });
9803 let selections = this.selections.all::<usize>(cx);
9804 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9805 s.select(selections)
9806 });
9807 });
9808 }
9809
9810 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9811 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9813 let selections = self.selections.all::<Point>(cx);
9814
9815 let mut new_cursors = Vec::new();
9816 let mut edit_ranges = Vec::new();
9817 let mut selections = selections.iter().peekable();
9818 while let Some(selection) = selections.next() {
9819 let mut rows = selection.spanned_rows(false, &display_map);
9820 let goal_display_column = selection.head().to_display_point(&display_map).column();
9821
9822 // Accumulate contiguous regions of rows that we want to delete.
9823 while let Some(next_selection) = selections.peek() {
9824 let next_rows = next_selection.spanned_rows(false, &display_map);
9825 if next_rows.start <= rows.end {
9826 rows.end = next_rows.end;
9827 selections.next().unwrap();
9828 } else {
9829 break;
9830 }
9831 }
9832
9833 let buffer = &display_map.buffer_snapshot;
9834 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9835 let edit_end;
9836 let cursor_buffer_row;
9837 if buffer.max_point().row >= rows.end.0 {
9838 // If there's a line after the range, delete the \n from the end of the row range
9839 // and position the cursor on the next line.
9840 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9841 cursor_buffer_row = rows.end;
9842 } else {
9843 // If there isn't a line after the range, delete the \n from the line before the
9844 // start of the row range and position the cursor there.
9845 edit_start = edit_start.saturating_sub(1);
9846 edit_end = buffer.len();
9847 cursor_buffer_row = rows.start.previous_row();
9848 }
9849
9850 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9851 *cursor.column_mut() =
9852 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9853
9854 new_cursors.push((
9855 selection.id,
9856 buffer.anchor_after(cursor.to_point(&display_map)),
9857 ));
9858 edit_ranges.push(edit_start..edit_end);
9859 }
9860
9861 self.transact(window, cx, |this, window, cx| {
9862 let buffer = this.buffer.update(cx, |buffer, cx| {
9863 let empty_str: Arc<str> = Arc::default();
9864 buffer.edit(
9865 edit_ranges
9866 .into_iter()
9867 .map(|range| (range, empty_str.clone())),
9868 None,
9869 cx,
9870 );
9871 buffer.snapshot(cx)
9872 });
9873 let new_selections = new_cursors
9874 .into_iter()
9875 .map(|(id, cursor)| {
9876 let cursor = cursor.to_point(&buffer);
9877 Selection {
9878 id,
9879 start: cursor,
9880 end: cursor,
9881 reversed: false,
9882 goal: SelectionGoal::None,
9883 }
9884 })
9885 .collect();
9886
9887 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9888 s.select(new_selections);
9889 });
9890 });
9891 }
9892
9893 pub fn join_lines_impl(
9894 &mut self,
9895 insert_whitespace: bool,
9896 window: &mut Window,
9897 cx: &mut Context<Self>,
9898 ) {
9899 if self.read_only(cx) {
9900 return;
9901 }
9902 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9903 for selection in self.selections.all::<Point>(cx) {
9904 let start = MultiBufferRow(selection.start.row);
9905 // Treat single line selections as if they include the next line. Otherwise this action
9906 // would do nothing for single line selections individual cursors.
9907 let end = if selection.start.row == selection.end.row {
9908 MultiBufferRow(selection.start.row + 1)
9909 } else {
9910 MultiBufferRow(selection.end.row)
9911 };
9912
9913 if let Some(last_row_range) = row_ranges.last_mut() {
9914 if start <= last_row_range.end {
9915 last_row_range.end = end;
9916 continue;
9917 }
9918 }
9919 row_ranges.push(start..end);
9920 }
9921
9922 let snapshot = self.buffer.read(cx).snapshot(cx);
9923 let mut cursor_positions = Vec::new();
9924 for row_range in &row_ranges {
9925 let anchor = snapshot.anchor_before(Point::new(
9926 row_range.end.previous_row().0,
9927 snapshot.line_len(row_range.end.previous_row()),
9928 ));
9929 cursor_positions.push(anchor..anchor);
9930 }
9931
9932 self.transact(window, cx, |this, window, cx| {
9933 for row_range in row_ranges.into_iter().rev() {
9934 for row in row_range.iter_rows().rev() {
9935 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9936 let next_line_row = row.next_row();
9937 let indent = snapshot.indent_size_for_line(next_line_row);
9938 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9939
9940 let replace =
9941 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9942 " "
9943 } else {
9944 ""
9945 };
9946
9947 this.buffer.update(cx, |buffer, cx| {
9948 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9949 });
9950 }
9951 }
9952
9953 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9954 s.select_anchor_ranges(cursor_positions)
9955 });
9956 });
9957 }
9958
9959 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 self.join_lines_impl(true, window, cx);
9962 }
9963
9964 pub fn sort_lines_case_sensitive(
9965 &mut self,
9966 _: &SortLinesCaseSensitive,
9967 window: &mut Window,
9968 cx: &mut Context<Self>,
9969 ) {
9970 self.manipulate_lines(window, cx, |lines| lines.sort())
9971 }
9972
9973 pub fn sort_lines_case_insensitive(
9974 &mut self,
9975 _: &SortLinesCaseInsensitive,
9976 window: &mut Window,
9977 cx: &mut Context<Self>,
9978 ) {
9979 self.manipulate_lines(window, cx, |lines| {
9980 lines.sort_by_key(|line| line.to_lowercase())
9981 })
9982 }
9983
9984 pub fn unique_lines_case_insensitive(
9985 &mut self,
9986 _: &UniqueLinesCaseInsensitive,
9987 window: &mut Window,
9988 cx: &mut Context<Self>,
9989 ) {
9990 self.manipulate_lines(window, cx, |lines| {
9991 let mut seen = HashSet::default();
9992 lines.retain(|line| seen.insert(line.to_lowercase()));
9993 })
9994 }
9995
9996 pub fn unique_lines_case_sensitive(
9997 &mut self,
9998 _: &UniqueLinesCaseSensitive,
9999 window: &mut Window,
10000 cx: &mut Context<Self>,
10001 ) {
10002 self.manipulate_lines(window, cx, |lines| {
10003 let mut seen = HashSet::default();
10004 lines.retain(|line| seen.insert(*line));
10005 })
10006 }
10007
10008 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10009 let Some(project) = self.project.clone() else {
10010 return;
10011 };
10012 self.reload(project, window, cx)
10013 .detach_and_notify_err(window, cx);
10014 }
10015
10016 pub fn restore_file(
10017 &mut self,
10018 _: &::git::RestoreFile,
10019 window: &mut Window,
10020 cx: &mut Context<Self>,
10021 ) {
10022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10023 let mut buffer_ids = HashSet::default();
10024 let snapshot = self.buffer().read(cx).snapshot(cx);
10025 for selection in self.selections.all::<usize>(cx) {
10026 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10027 }
10028
10029 let buffer = self.buffer().read(cx);
10030 let ranges = buffer_ids
10031 .into_iter()
10032 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10033 .collect::<Vec<_>>();
10034
10035 self.restore_hunks_in_ranges(ranges, window, cx);
10036 }
10037
10038 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10040 let selections = self
10041 .selections
10042 .all(cx)
10043 .into_iter()
10044 .map(|s| s.range())
10045 .collect();
10046 self.restore_hunks_in_ranges(selections, window, cx);
10047 }
10048
10049 pub fn restore_hunks_in_ranges(
10050 &mut self,
10051 ranges: Vec<Range<Point>>,
10052 window: &mut Window,
10053 cx: &mut Context<Editor>,
10054 ) {
10055 let mut revert_changes = HashMap::default();
10056 let chunk_by = self
10057 .snapshot(window, cx)
10058 .hunks_for_ranges(ranges)
10059 .into_iter()
10060 .chunk_by(|hunk| hunk.buffer_id);
10061 for (buffer_id, hunks) in &chunk_by {
10062 let hunks = hunks.collect::<Vec<_>>();
10063 for hunk in &hunks {
10064 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10065 }
10066 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10067 }
10068 drop(chunk_by);
10069 if !revert_changes.is_empty() {
10070 self.transact(window, cx, |editor, window, cx| {
10071 editor.restore(revert_changes, window, cx);
10072 });
10073 }
10074 }
10075
10076 pub fn open_active_item_in_terminal(
10077 &mut self,
10078 _: &OpenInTerminal,
10079 window: &mut Window,
10080 cx: &mut Context<Self>,
10081 ) {
10082 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10083 let project_path = buffer.read(cx).project_path(cx)?;
10084 let project = self.project.as_ref()?.read(cx);
10085 let entry = project.entry_for_path(&project_path, cx)?;
10086 let parent = match &entry.canonical_path {
10087 Some(canonical_path) => canonical_path.to_path_buf(),
10088 None => project.absolute_path(&project_path, cx)?,
10089 }
10090 .parent()?
10091 .to_path_buf();
10092 Some(parent)
10093 }) {
10094 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10095 }
10096 }
10097
10098 fn set_breakpoint_context_menu(
10099 &mut self,
10100 display_row: DisplayRow,
10101 position: Option<Anchor>,
10102 clicked_point: gpui::Point<Pixels>,
10103 window: &mut Window,
10104 cx: &mut Context<Self>,
10105 ) {
10106 if !cx.has_flag::<DebuggerFeatureFlag>() {
10107 return;
10108 }
10109 let source = self
10110 .buffer
10111 .read(cx)
10112 .snapshot(cx)
10113 .anchor_before(Point::new(display_row.0, 0u32));
10114
10115 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10116
10117 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10118 self,
10119 source,
10120 clicked_point,
10121 context_menu,
10122 window,
10123 cx,
10124 );
10125 }
10126
10127 fn add_edit_breakpoint_block(
10128 &mut self,
10129 anchor: Anchor,
10130 breakpoint: &Breakpoint,
10131 edit_action: BreakpointPromptEditAction,
10132 window: &mut Window,
10133 cx: &mut Context<Self>,
10134 ) {
10135 let weak_editor = cx.weak_entity();
10136 let bp_prompt = cx.new(|cx| {
10137 BreakpointPromptEditor::new(
10138 weak_editor,
10139 anchor,
10140 breakpoint.clone(),
10141 edit_action,
10142 window,
10143 cx,
10144 )
10145 });
10146
10147 let height = bp_prompt.update(cx, |this, cx| {
10148 this.prompt
10149 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10150 });
10151 let cloned_prompt = bp_prompt.clone();
10152 let blocks = vec![BlockProperties {
10153 style: BlockStyle::Sticky,
10154 placement: BlockPlacement::Above(anchor),
10155 height: Some(height),
10156 render: Arc::new(move |cx| {
10157 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10158 cloned_prompt.clone().into_any_element()
10159 }),
10160 priority: 0,
10161 render_in_minimap: true,
10162 }];
10163
10164 let focus_handle = bp_prompt.focus_handle(cx);
10165 window.focus(&focus_handle);
10166
10167 let block_ids = self.insert_blocks(blocks, None, cx);
10168 bp_prompt.update(cx, |prompt, _| {
10169 prompt.add_block_ids(block_ids);
10170 });
10171 }
10172
10173 pub(crate) fn breakpoint_at_row(
10174 &self,
10175 row: u32,
10176 window: &mut Window,
10177 cx: &mut Context<Self>,
10178 ) -> Option<(Anchor, Breakpoint)> {
10179 let snapshot = self.snapshot(window, cx);
10180 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10181
10182 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10183 }
10184
10185 pub(crate) fn breakpoint_at_anchor(
10186 &self,
10187 breakpoint_position: Anchor,
10188 snapshot: &EditorSnapshot,
10189 cx: &mut Context<Self>,
10190 ) -> Option<(Anchor, Breakpoint)> {
10191 let project = self.project.clone()?;
10192
10193 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10194 snapshot
10195 .buffer_snapshot
10196 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10197 })?;
10198
10199 let enclosing_excerpt = breakpoint_position.excerpt_id;
10200 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10201 let buffer_snapshot = buffer.read(cx).snapshot();
10202
10203 let row = buffer_snapshot
10204 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10205 .row;
10206
10207 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10208 let anchor_end = snapshot
10209 .buffer_snapshot
10210 .anchor_after(Point::new(row, line_len));
10211
10212 let bp = self
10213 .breakpoint_store
10214 .as_ref()?
10215 .read_with(cx, |breakpoint_store, cx| {
10216 breakpoint_store
10217 .breakpoints(
10218 &buffer,
10219 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10220 &buffer_snapshot,
10221 cx,
10222 )
10223 .next()
10224 .and_then(|(bp, _)| {
10225 let breakpoint_row = buffer_snapshot
10226 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10227 .row;
10228
10229 if breakpoint_row == row {
10230 snapshot
10231 .buffer_snapshot
10232 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10233 .map(|position| (position, bp.bp.clone()))
10234 } else {
10235 None
10236 }
10237 })
10238 });
10239 bp
10240 }
10241
10242 pub fn edit_log_breakpoint(
10243 &mut self,
10244 _: &EditLogBreakpoint,
10245 window: &mut Window,
10246 cx: &mut Context<Self>,
10247 ) {
10248 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10249 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10250 message: None,
10251 state: BreakpointState::Enabled,
10252 condition: None,
10253 hit_condition: None,
10254 });
10255
10256 self.add_edit_breakpoint_block(
10257 anchor,
10258 &breakpoint,
10259 BreakpointPromptEditAction::Log,
10260 window,
10261 cx,
10262 );
10263 }
10264 }
10265
10266 fn breakpoints_at_cursors(
10267 &self,
10268 window: &mut Window,
10269 cx: &mut Context<Self>,
10270 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10271 let snapshot = self.snapshot(window, cx);
10272 let cursors = self
10273 .selections
10274 .disjoint_anchors()
10275 .into_iter()
10276 .map(|selection| {
10277 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10278
10279 let breakpoint_position = self
10280 .breakpoint_at_row(cursor_position.row, window, cx)
10281 .map(|bp| bp.0)
10282 .unwrap_or_else(|| {
10283 snapshot
10284 .display_snapshot
10285 .buffer_snapshot
10286 .anchor_after(Point::new(cursor_position.row, 0))
10287 });
10288
10289 let breakpoint = self
10290 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10291 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10292
10293 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10294 })
10295 // 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.
10296 .collect::<HashMap<Anchor, _>>();
10297
10298 cursors.into_iter().collect()
10299 }
10300
10301 pub fn enable_breakpoint(
10302 &mut self,
10303 _: &crate::actions::EnableBreakpoint,
10304 window: &mut Window,
10305 cx: &mut Context<Self>,
10306 ) {
10307 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10308 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10309 continue;
10310 };
10311 self.edit_breakpoint_at_anchor(
10312 anchor,
10313 breakpoint,
10314 BreakpointEditAction::InvertState,
10315 cx,
10316 );
10317 }
10318 }
10319
10320 pub fn disable_breakpoint(
10321 &mut self,
10322 _: &crate::actions::DisableBreakpoint,
10323 window: &mut Window,
10324 cx: &mut Context<Self>,
10325 ) {
10326 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10327 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10328 continue;
10329 };
10330 self.edit_breakpoint_at_anchor(
10331 anchor,
10332 breakpoint,
10333 BreakpointEditAction::InvertState,
10334 cx,
10335 );
10336 }
10337 }
10338
10339 pub fn toggle_breakpoint(
10340 &mut self,
10341 _: &crate::actions::ToggleBreakpoint,
10342 window: &mut Window,
10343 cx: &mut Context<Self>,
10344 ) {
10345 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10346 if let Some(breakpoint) = breakpoint {
10347 self.edit_breakpoint_at_anchor(
10348 anchor,
10349 breakpoint,
10350 BreakpointEditAction::Toggle,
10351 cx,
10352 );
10353 } else {
10354 self.edit_breakpoint_at_anchor(
10355 anchor,
10356 Breakpoint::new_standard(),
10357 BreakpointEditAction::Toggle,
10358 cx,
10359 );
10360 }
10361 }
10362 }
10363
10364 pub fn edit_breakpoint_at_anchor(
10365 &mut self,
10366 breakpoint_position: Anchor,
10367 breakpoint: Breakpoint,
10368 edit_action: BreakpointEditAction,
10369 cx: &mut Context<Self>,
10370 ) {
10371 let Some(breakpoint_store) = &self.breakpoint_store else {
10372 return;
10373 };
10374
10375 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10376 if breakpoint_position == Anchor::min() {
10377 self.buffer()
10378 .read(cx)
10379 .excerpt_buffer_ids()
10380 .into_iter()
10381 .next()
10382 } else {
10383 None
10384 }
10385 }) else {
10386 return;
10387 };
10388
10389 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10390 return;
10391 };
10392
10393 breakpoint_store.update(cx, |breakpoint_store, cx| {
10394 breakpoint_store.toggle_breakpoint(
10395 buffer,
10396 BreakpointWithPosition {
10397 position: breakpoint_position.text_anchor,
10398 bp: breakpoint,
10399 },
10400 edit_action,
10401 cx,
10402 );
10403 });
10404
10405 cx.notify();
10406 }
10407
10408 #[cfg(any(test, feature = "test-support"))]
10409 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10410 self.breakpoint_store.clone()
10411 }
10412
10413 pub fn prepare_restore_change(
10414 &self,
10415 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10416 hunk: &MultiBufferDiffHunk,
10417 cx: &mut App,
10418 ) -> Option<()> {
10419 if hunk.is_created_file() {
10420 return None;
10421 }
10422 let buffer = self.buffer.read(cx);
10423 let diff = buffer.diff_for(hunk.buffer_id)?;
10424 let buffer = buffer.buffer(hunk.buffer_id)?;
10425 let buffer = buffer.read(cx);
10426 let original_text = diff
10427 .read(cx)
10428 .base_text()
10429 .as_rope()
10430 .slice(hunk.diff_base_byte_range.clone());
10431 let buffer_snapshot = buffer.snapshot();
10432 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10433 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10434 probe
10435 .0
10436 .start
10437 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10438 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10439 }) {
10440 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10441 Some(())
10442 } else {
10443 None
10444 }
10445 }
10446
10447 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10448 self.manipulate_lines(window, cx, |lines| lines.reverse())
10449 }
10450
10451 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10452 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10453 }
10454
10455 fn manipulate_lines<Fn>(
10456 &mut self,
10457 window: &mut Window,
10458 cx: &mut Context<Self>,
10459 mut callback: Fn,
10460 ) where
10461 Fn: FnMut(&mut Vec<&str>),
10462 {
10463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10464
10465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10466 let buffer = self.buffer.read(cx).snapshot(cx);
10467
10468 let mut edits = Vec::new();
10469
10470 let selections = self.selections.all::<Point>(cx);
10471 let mut selections = selections.iter().peekable();
10472 let mut contiguous_row_selections = Vec::new();
10473 let mut new_selections = Vec::new();
10474 let mut added_lines = 0;
10475 let mut removed_lines = 0;
10476
10477 while let Some(selection) = selections.next() {
10478 let (start_row, end_row) = consume_contiguous_rows(
10479 &mut contiguous_row_selections,
10480 selection,
10481 &display_map,
10482 &mut selections,
10483 );
10484
10485 let start_point = Point::new(start_row.0, 0);
10486 let end_point = Point::new(
10487 end_row.previous_row().0,
10488 buffer.line_len(end_row.previous_row()),
10489 );
10490 let text = buffer
10491 .text_for_range(start_point..end_point)
10492 .collect::<String>();
10493
10494 let mut lines = text.split('\n').collect_vec();
10495
10496 let lines_before = lines.len();
10497 callback(&mut lines);
10498 let lines_after = lines.len();
10499
10500 edits.push((start_point..end_point, lines.join("\n")));
10501
10502 // Selections must change based on added and removed line count
10503 let start_row =
10504 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10505 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10506 new_selections.push(Selection {
10507 id: selection.id,
10508 start: start_row,
10509 end: end_row,
10510 goal: SelectionGoal::None,
10511 reversed: selection.reversed,
10512 });
10513
10514 if lines_after > lines_before {
10515 added_lines += lines_after - lines_before;
10516 } else if lines_before > lines_after {
10517 removed_lines += lines_before - lines_after;
10518 }
10519 }
10520
10521 self.transact(window, cx, |this, window, cx| {
10522 let buffer = this.buffer.update(cx, |buffer, cx| {
10523 buffer.edit(edits, None, cx);
10524 buffer.snapshot(cx)
10525 });
10526
10527 // Recalculate offsets on newly edited buffer
10528 let new_selections = new_selections
10529 .iter()
10530 .map(|s| {
10531 let start_point = Point::new(s.start.0, 0);
10532 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10533 Selection {
10534 id: s.id,
10535 start: buffer.point_to_offset(start_point),
10536 end: buffer.point_to_offset(end_point),
10537 goal: s.goal,
10538 reversed: s.reversed,
10539 }
10540 })
10541 .collect();
10542
10543 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10544 s.select(new_selections);
10545 });
10546
10547 this.request_autoscroll(Autoscroll::fit(), cx);
10548 });
10549 }
10550
10551 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10552 self.manipulate_text(window, cx, |text| {
10553 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10554 if has_upper_case_characters {
10555 text.to_lowercase()
10556 } else {
10557 text.to_uppercase()
10558 }
10559 })
10560 }
10561
10562 pub fn convert_to_upper_case(
10563 &mut self,
10564 _: &ConvertToUpperCase,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 self.manipulate_text(window, cx, |text| text.to_uppercase())
10569 }
10570
10571 pub fn convert_to_lower_case(
10572 &mut self,
10573 _: &ConvertToLowerCase,
10574 window: &mut Window,
10575 cx: &mut Context<Self>,
10576 ) {
10577 self.manipulate_text(window, cx, |text| text.to_lowercase())
10578 }
10579
10580 pub fn convert_to_title_case(
10581 &mut self,
10582 _: &ConvertToTitleCase,
10583 window: &mut Window,
10584 cx: &mut Context<Self>,
10585 ) {
10586 self.manipulate_text(window, cx, |text| {
10587 text.split('\n')
10588 .map(|line| line.to_case(Case::Title))
10589 .join("\n")
10590 })
10591 }
10592
10593 pub fn convert_to_snake_case(
10594 &mut self,
10595 _: &ConvertToSnakeCase,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10600 }
10601
10602 pub fn convert_to_kebab_case(
10603 &mut self,
10604 _: &ConvertToKebabCase,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10609 }
10610
10611 pub fn convert_to_upper_camel_case(
10612 &mut self,
10613 _: &ConvertToUpperCamelCase,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) {
10617 self.manipulate_text(window, cx, |text| {
10618 text.split('\n')
10619 .map(|line| line.to_case(Case::UpperCamel))
10620 .join("\n")
10621 })
10622 }
10623
10624 pub fn convert_to_lower_camel_case(
10625 &mut self,
10626 _: &ConvertToLowerCamelCase,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10631 }
10632
10633 pub fn convert_to_opposite_case(
10634 &mut self,
10635 _: &ConvertToOppositeCase,
10636 window: &mut Window,
10637 cx: &mut Context<Self>,
10638 ) {
10639 self.manipulate_text(window, cx, |text| {
10640 text.chars()
10641 .fold(String::with_capacity(text.len()), |mut t, c| {
10642 if c.is_uppercase() {
10643 t.extend(c.to_lowercase());
10644 } else {
10645 t.extend(c.to_uppercase());
10646 }
10647 t
10648 })
10649 })
10650 }
10651
10652 pub fn convert_to_rot13(
10653 &mut self,
10654 _: &ConvertToRot13,
10655 window: &mut Window,
10656 cx: &mut Context<Self>,
10657 ) {
10658 self.manipulate_text(window, cx, |text| {
10659 text.chars()
10660 .map(|c| match c {
10661 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10662 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10663 _ => c,
10664 })
10665 .collect()
10666 })
10667 }
10668
10669 pub fn convert_to_rot47(
10670 &mut self,
10671 _: &ConvertToRot47,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 self.manipulate_text(window, cx, |text| {
10676 text.chars()
10677 .map(|c| {
10678 let code_point = c as u32;
10679 if code_point >= 33 && code_point <= 126 {
10680 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10681 }
10682 c
10683 })
10684 .collect()
10685 })
10686 }
10687
10688 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10689 where
10690 Fn: FnMut(&str) -> String,
10691 {
10692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10693 let buffer = self.buffer.read(cx).snapshot(cx);
10694
10695 let mut new_selections = Vec::new();
10696 let mut edits = Vec::new();
10697 let mut selection_adjustment = 0i32;
10698
10699 for selection in self.selections.all::<usize>(cx) {
10700 let selection_is_empty = selection.is_empty();
10701
10702 let (start, end) = if selection_is_empty {
10703 let word_range = movement::surrounding_word(
10704 &display_map,
10705 selection.start.to_display_point(&display_map),
10706 );
10707 let start = word_range.start.to_offset(&display_map, Bias::Left);
10708 let end = word_range.end.to_offset(&display_map, Bias::Left);
10709 (start, end)
10710 } else {
10711 (selection.start, selection.end)
10712 };
10713
10714 let text = buffer.text_for_range(start..end).collect::<String>();
10715 let old_length = text.len() as i32;
10716 let text = callback(&text);
10717
10718 new_selections.push(Selection {
10719 start: (start as i32 - selection_adjustment) as usize,
10720 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10721 goal: SelectionGoal::None,
10722 ..selection
10723 });
10724
10725 selection_adjustment += old_length - text.len() as i32;
10726
10727 edits.push((start..end, text));
10728 }
10729
10730 self.transact(window, cx, |this, window, cx| {
10731 this.buffer.update(cx, |buffer, cx| {
10732 buffer.edit(edits, None, cx);
10733 });
10734
10735 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10736 s.select(new_selections);
10737 });
10738
10739 this.request_autoscroll(Autoscroll::fit(), cx);
10740 });
10741 }
10742
10743 pub fn move_selection_on_drop(
10744 &mut self,
10745 selection: &Selection<Anchor>,
10746 target: DisplayPoint,
10747 is_cut: bool,
10748 window: &mut Window,
10749 cx: &mut Context<Self>,
10750 ) {
10751 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10752 let buffer = &display_map.buffer_snapshot;
10753 let mut edits = Vec::new();
10754 let insert_point = display_map
10755 .clip_point(target, Bias::Left)
10756 .to_point(&display_map);
10757 let text = buffer
10758 .text_for_range(selection.start..selection.end)
10759 .collect::<String>();
10760 if is_cut {
10761 edits.push(((selection.start..selection.end), String::new()));
10762 }
10763 let insert_anchor = buffer.anchor_before(insert_point);
10764 edits.push(((insert_anchor..insert_anchor), text));
10765 let last_edit_start = insert_anchor.bias_left(buffer);
10766 let last_edit_end = insert_anchor.bias_right(buffer);
10767 self.transact(window, cx, |this, window, cx| {
10768 this.buffer.update(cx, |buffer, cx| {
10769 buffer.edit(edits, None, cx);
10770 });
10771 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10772 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10773 });
10774 });
10775 }
10776
10777 pub fn clear_selection_drag_state(&mut self) {
10778 self.selection_drag_state = SelectionDragState::None;
10779 }
10780
10781 pub fn duplicate(
10782 &mut self,
10783 upwards: bool,
10784 whole_lines: bool,
10785 window: &mut Window,
10786 cx: &mut Context<Self>,
10787 ) {
10788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10789
10790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10791 let buffer = &display_map.buffer_snapshot;
10792 let selections = self.selections.all::<Point>(cx);
10793
10794 let mut edits = Vec::new();
10795 let mut selections_iter = selections.iter().peekable();
10796 while let Some(selection) = selections_iter.next() {
10797 let mut rows = selection.spanned_rows(false, &display_map);
10798 // duplicate line-wise
10799 if whole_lines || selection.start == selection.end {
10800 // Avoid duplicating the same lines twice.
10801 while let Some(next_selection) = selections_iter.peek() {
10802 let next_rows = next_selection.spanned_rows(false, &display_map);
10803 if next_rows.start < rows.end {
10804 rows.end = next_rows.end;
10805 selections_iter.next().unwrap();
10806 } else {
10807 break;
10808 }
10809 }
10810
10811 // Copy the text from the selected row region and splice it either at the start
10812 // or end of the region.
10813 let start = Point::new(rows.start.0, 0);
10814 let end = Point::new(
10815 rows.end.previous_row().0,
10816 buffer.line_len(rows.end.previous_row()),
10817 );
10818 let text = buffer
10819 .text_for_range(start..end)
10820 .chain(Some("\n"))
10821 .collect::<String>();
10822 let insert_location = if upwards {
10823 Point::new(rows.end.0, 0)
10824 } else {
10825 start
10826 };
10827 edits.push((insert_location..insert_location, text));
10828 } else {
10829 // duplicate character-wise
10830 let start = selection.start;
10831 let end = selection.end;
10832 let text = buffer.text_for_range(start..end).collect::<String>();
10833 edits.push((selection.end..selection.end, text));
10834 }
10835 }
10836
10837 self.transact(window, cx, |this, _, cx| {
10838 this.buffer.update(cx, |buffer, cx| {
10839 buffer.edit(edits, None, cx);
10840 });
10841
10842 this.request_autoscroll(Autoscroll::fit(), cx);
10843 });
10844 }
10845
10846 pub fn duplicate_line_up(
10847 &mut self,
10848 _: &DuplicateLineUp,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 self.duplicate(true, true, window, cx);
10853 }
10854
10855 pub fn duplicate_line_down(
10856 &mut self,
10857 _: &DuplicateLineDown,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 self.duplicate(false, true, window, cx);
10862 }
10863
10864 pub fn duplicate_selection(
10865 &mut self,
10866 _: &DuplicateSelection,
10867 window: &mut Window,
10868 cx: &mut Context<Self>,
10869 ) {
10870 self.duplicate(false, false, window, cx);
10871 }
10872
10873 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10875
10876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10877 let buffer = self.buffer.read(cx).snapshot(cx);
10878
10879 let mut edits = Vec::new();
10880 let mut unfold_ranges = Vec::new();
10881 let mut refold_creases = Vec::new();
10882
10883 let selections = self.selections.all::<Point>(cx);
10884 let mut selections = selections.iter().peekable();
10885 let mut contiguous_row_selections = Vec::new();
10886 let mut new_selections = Vec::new();
10887
10888 while let Some(selection) = selections.next() {
10889 // Find all the selections that span a contiguous row range
10890 let (start_row, end_row) = consume_contiguous_rows(
10891 &mut contiguous_row_selections,
10892 selection,
10893 &display_map,
10894 &mut selections,
10895 );
10896
10897 // Move the text spanned by the row range to be before the line preceding the row range
10898 if start_row.0 > 0 {
10899 let range_to_move = Point::new(
10900 start_row.previous_row().0,
10901 buffer.line_len(start_row.previous_row()),
10902 )
10903 ..Point::new(
10904 end_row.previous_row().0,
10905 buffer.line_len(end_row.previous_row()),
10906 );
10907 let insertion_point = display_map
10908 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10909 .0;
10910
10911 // Don't move lines across excerpts
10912 if buffer
10913 .excerpt_containing(insertion_point..range_to_move.end)
10914 .is_some()
10915 {
10916 let text = buffer
10917 .text_for_range(range_to_move.clone())
10918 .flat_map(|s| s.chars())
10919 .skip(1)
10920 .chain(['\n'])
10921 .collect::<String>();
10922
10923 edits.push((
10924 buffer.anchor_after(range_to_move.start)
10925 ..buffer.anchor_before(range_to_move.end),
10926 String::new(),
10927 ));
10928 let insertion_anchor = buffer.anchor_after(insertion_point);
10929 edits.push((insertion_anchor..insertion_anchor, text));
10930
10931 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10932
10933 // Move selections up
10934 new_selections.extend(contiguous_row_selections.drain(..).map(
10935 |mut selection| {
10936 selection.start.row -= row_delta;
10937 selection.end.row -= row_delta;
10938 selection
10939 },
10940 ));
10941
10942 // Move folds up
10943 unfold_ranges.push(range_to_move.clone());
10944 for fold in display_map.folds_in_range(
10945 buffer.anchor_before(range_to_move.start)
10946 ..buffer.anchor_after(range_to_move.end),
10947 ) {
10948 let mut start = fold.range.start.to_point(&buffer);
10949 let mut end = fold.range.end.to_point(&buffer);
10950 start.row -= row_delta;
10951 end.row -= row_delta;
10952 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10953 }
10954 }
10955 }
10956
10957 // If we didn't move line(s), preserve the existing selections
10958 new_selections.append(&mut contiguous_row_selections);
10959 }
10960
10961 self.transact(window, cx, |this, window, cx| {
10962 this.unfold_ranges(&unfold_ranges, true, true, cx);
10963 this.buffer.update(cx, |buffer, cx| {
10964 for (range, text) in edits {
10965 buffer.edit([(range, text)], None, cx);
10966 }
10967 });
10968 this.fold_creases(refold_creases, true, window, cx);
10969 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10970 s.select(new_selections);
10971 })
10972 });
10973 }
10974
10975 pub fn move_line_down(
10976 &mut self,
10977 _: &MoveLineDown,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 ) {
10981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10982
10983 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10984 let buffer = self.buffer.read(cx).snapshot(cx);
10985
10986 let mut edits = Vec::new();
10987 let mut unfold_ranges = Vec::new();
10988 let mut refold_creases = Vec::new();
10989
10990 let selections = self.selections.all::<Point>(cx);
10991 let mut selections = selections.iter().peekable();
10992 let mut contiguous_row_selections = Vec::new();
10993 let mut new_selections = Vec::new();
10994
10995 while let Some(selection) = selections.next() {
10996 // Find all the selections that span a contiguous row range
10997 let (start_row, end_row) = consume_contiguous_rows(
10998 &mut contiguous_row_selections,
10999 selection,
11000 &display_map,
11001 &mut selections,
11002 );
11003
11004 // Move the text spanned by the row range to be after the last line of the row range
11005 if end_row.0 <= buffer.max_point().row {
11006 let range_to_move =
11007 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11008 let insertion_point = display_map
11009 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11010 .0;
11011
11012 // Don't move lines across excerpt boundaries
11013 if buffer
11014 .excerpt_containing(range_to_move.start..insertion_point)
11015 .is_some()
11016 {
11017 let mut text = String::from("\n");
11018 text.extend(buffer.text_for_range(range_to_move.clone()));
11019 text.pop(); // Drop trailing newline
11020 edits.push((
11021 buffer.anchor_after(range_to_move.start)
11022 ..buffer.anchor_before(range_to_move.end),
11023 String::new(),
11024 ));
11025 let insertion_anchor = buffer.anchor_after(insertion_point);
11026 edits.push((insertion_anchor..insertion_anchor, text));
11027
11028 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11029
11030 // Move selections down
11031 new_selections.extend(contiguous_row_selections.drain(..).map(
11032 |mut selection| {
11033 selection.start.row += row_delta;
11034 selection.end.row += row_delta;
11035 selection
11036 },
11037 ));
11038
11039 // Move folds down
11040 unfold_ranges.push(range_to_move.clone());
11041 for fold in display_map.folds_in_range(
11042 buffer.anchor_before(range_to_move.start)
11043 ..buffer.anchor_after(range_to_move.end),
11044 ) {
11045 let mut start = fold.range.start.to_point(&buffer);
11046 let mut end = fold.range.end.to_point(&buffer);
11047 start.row += row_delta;
11048 end.row += row_delta;
11049 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11050 }
11051 }
11052 }
11053
11054 // If we didn't move line(s), preserve the existing selections
11055 new_selections.append(&mut contiguous_row_selections);
11056 }
11057
11058 self.transact(window, cx, |this, window, cx| {
11059 this.unfold_ranges(&unfold_ranges, true, true, cx);
11060 this.buffer.update(cx, |buffer, cx| {
11061 for (range, text) in edits {
11062 buffer.edit([(range, text)], None, cx);
11063 }
11064 });
11065 this.fold_creases(refold_creases, true, window, cx);
11066 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11067 s.select(new_selections)
11068 });
11069 });
11070 }
11071
11072 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11074 let text_layout_details = &self.text_layout_details(window);
11075 self.transact(window, cx, |this, window, cx| {
11076 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11077 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11078 s.move_with(|display_map, selection| {
11079 if !selection.is_empty() {
11080 return;
11081 }
11082
11083 let mut head = selection.head();
11084 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11085 if head.column() == display_map.line_len(head.row()) {
11086 transpose_offset = display_map
11087 .buffer_snapshot
11088 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11089 }
11090
11091 if transpose_offset == 0 {
11092 return;
11093 }
11094
11095 *head.column_mut() += 1;
11096 head = display_map.clip_point(head, Bias::Right);
11097 let goal = SelectionGoal::HorizontalPosition(
11098 display_map
11099 .x_for_display_point(head, text_layout_details)
11100 .into(),
11101 );
11102 selection.collapse_to(head, goal);
11103
11104 let transpose_start = display_map
11105 .buffer_snapshot
11106 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11107 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11108 let transpose_end = display_map
11109 .buffer_snapshot
11110 .clip_offset(transpose_offset + 1, Bias::Right);
11111 if let Some(ch) =
11112 display_map.buffer_snapshot.chars_at(transpose_start).next()
11113 {
11114 edits.push((transpose_start..transpose_offset, String::new()));
11115 edits.push((transpose_end..transpose_end, ch.to_string()));
11116 }
11117 }
11118 });
11119 edits
11120 });
11121 this.buffer
11122 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11123 let selections = this.selections.all::<usize>(cx);
11124 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11125 s.select(selections);
11126 });
11127 });
11128 }
11129
11130 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11132 self.rewrap_impl(RewrapOptions::default(), cx)
11133 }
11134
11135 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11136 let buffer = self.buffer.read(cx).snapshot(cx);
11137 let selections = self.selections.all::<Point>(cx);
11138
11139 // Shrink and split selections to respect paragraph boundaries.
11140 let ranges = selections.into_iter().flat_map(|selection| {
11141 let language_settings = buffer.language_settings_at(selection.head(), cx);
11142 let language_scope = buffer.language_scope_at(selection.head());
11143
11144 let Some(start_row) = (selection.start.row..=selection.end.row)
11145 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11146 else {
11147 return vec![];
11148 };
11149 let Some(end_row) = (selection.start.row..=selection.end.row)
11150 .rev()
11151 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11152 else {
11153 return vec![];
11154 };
11155
11156 let mut row = start_row;
11157 let mut ranges = Vec::new();
11158 while let Some(blank_row) =
11159 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11160 {
11161 let next_paragraph_start = (blank_row + 1..=end_row)
11162 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11163 .unwrap();
11164 ranges.push((
11165 language_settings.clone(),
11166 language_scope.clone(),
11167 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11168 ));
11169 row = next_paragraph_start;
11170 }
11171 ranges.push((
11172 language_settings.clone(),
11173 language_scope.clone(),
11174 Point::new(row, 0)..Point::new(end_row, 0),
11175 ));
11176
11177 ranges
11178 });
11179
11180 let mut edits = Vec::new();
11181 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11182
11183 for (language_settings, language_scope, range) in ranges {
11184 let mut start_row = range.start.row;
11185 let mut end_row = range.end.row;
11186
11187 // Skip selections that overlap with a range that has already been rewrapped.
11188 let selection_range = start_row..end_row;
11189 if rewrapped_row_ranges
11190 .iter()
11191 .any(|range| range.overlaps(&selection_range))
11192 {
11193 continue;
11194 }
11195
11196 let tab_size = language_settings.tab_size;
11197
11198 // Since not all lines in the selection may be at the same indent
11199 // level, choose the indent size that is the most common between all
11200 // of the lines.
11201 //
11202 // If there is a tie, we use the deepest indent.
11203 let (indent_size, indent_end) = {
11204 let mut indent_size_occurrences = HashMap::default();
11205 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11206
11207 for row in start_row..=end_row {
11208 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11209 rows_by_indent_size.entry(indent).or_default().push(row);
11210 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11211 }
11212
11213 let indent_size = indent_size_occurrences
11214 .into_iter()
11215 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11216 .map(|(indent, _)| indent)
11217 .unwrap_or_default();
11218 let row = rows_by_indent_size[&indent_size][0];
11219 let indent_end = Point::new(row, indent_size.len);
11220
11221 (indent_size, indent_end)
11222 };
11223
11224 let mut line_prefix = indent_size.chars().collect::<String>();
11225
11226 let mut inside_comment = false;
11227 if let Some(comment_prefix) = language_scope.and_then(|language| {
11228 language
11229 .line_comment_prefixes()
11230 .iter()
11231 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11232 .cloned()
11233 }) {
11234 line_prefix.push_str(&comment_prefix);
11235 inside_comment = true;
11236 }
11237
11238 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11239 RewrapBehavior::InComments => inside_comment,
11240 RewrapBehavior::InSelections => !range.is_empty(),
11241 RewrapBehavior::Anywhere => true,
11242 };
11243
11244 let should_rewrap = options.override_language_settings
11245 || allow_rewrap_based_on_language
11246 || self.hard_wrap.is_some();
11247 if !should_rewrap {
11248 continue;
11249 }
11250
11251 if range.is_empty() {
11252 'expand_upwards: while start_row > 0 {
11253 let prev_row = start_row - 1;
11254 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11255 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11256 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11257 {
11258 start_row = prev_row;
11259 } else {
11260 break 'expand_upwards;
11261 }
11262 }
11263
11264 'expand_downwards: while end_row < buffer.max_point().row {
11265 let next_row = end_row + 1;
11266 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11267 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11268 && !buffer.is_line_blank(MultiBufferRow(next_row))
11269 {
11270 end_row = next_row;
11271 } else {
11272 break 'expand_downwards;
11273 }
11274 }
11275 }
11276
11277 let start = Point::new(start_row, 0);
11278 let start_offset = start.to_offset(&buffer);
11279 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11280 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11281 let Some(lines_without_prefixes) = selection_text
11282 .lines()
11283 .map(|line| {
11284 line.strip_prefix(&line_prefix)
11285 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11286 .with_context(|| {
11287 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11288 })
11289 })
11290 .collect::<Result<Vec<_>, _>>()
11291 .log_err()
11292 else {
11293 continue;
11294 };
11295
11296 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11297 buffer
11298 .language_settings_at(Point::new(start_row, 0), cx)
11299 .preferred_line_length as usize
11300 });
11301 let wrapped_text = wrap_with_prefix(
11302 line_prefix,
11303 lines_without_prefixes.join("\n"),
11304 wrap_column,
11305 tab_size,
11306 options.preserve_existing_whitespace,
11307 );
11308
11309 // TODO: should always use char-based diff while still supporting cursor behavior that
11310 // matches vim.
11311 let mut diff_options = DiffOptions::default();
11312 if options.override_language_settings {
11313 diff_options.max_word_diff_len = 0;
11314 diff_options.max_word_diff_line_count = 0;
11315 } else {
11316 diff_options.max_word_diff_len = usize::MAX;
11317 diff_options.max_word_diff_line_count = usize::MAX;
11318 }
11319
11320 for (old_range, new_text) in
11321 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11322 {
11323 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11324 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11325 edits.push((edit_start..edit_end, new_text));
11326 }
11327
11328 rewrapped_row_ranges.push(start_row..=end_row);
11329 }
11330
11331 self.buffer
11332 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11333 }
11334
11335 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11336 let mut text = String::new();
11337 let buffer = self.buffer.read(cx).snapshot(cx);
11338 let mut selections = self.selections.all::<Point>(cx);
11339 let mut clipboard_selections = Vec::with_capacity(selections.len());
11340 {
11341 let max_point = buffer.max_point();
11342 let mut is_first = true;
11343 for selection in &mut selections {
11344 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11345 if is_entire_line {
11346 selection.start = Point::new(selection.start.row, 0);
11347 if !selection.is_empty() && selection.end.column == 0 {
11348 selection.end = cmp::min(max_point, selection.end);
11349 } else {
11350 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11351 }
11352 selection.goal = SelectionGoal::None;
11353 }
11354 if is_first {
11355 is_first = false;
11356 } else {
11357 text += "\n";
11358 }
11359 let mut len = 0;
11360 for chunk in buffer.text_for_range(selection.start..selection.end) {
11361 text.push_str(chunk);
11362 len += chunk.len();
11363 }
11364 clipboard_selections.push(ClipboardSelection {
11365 len,
11366 is_entire_line,
11367 first_line_indent: buffer
11368 .indent_size_for_line(MultiBufferRow(selection.start.row))
11369 .len,
11370 });
11371 }
11372 }
11373
11374 self.transact(window, cx, |this, window, cx| {
11375 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11376 s.select(selections);
11377 });
11378 this.insert("", window, cx);
11379 });
11380 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11381 }
11382
11383 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11385 let item = self.cut_common(window, cx);
11386 cx.write_to_clipboard(item);
11387 }
11388
11389 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11391 self.change_selections(None, window, cx, |s| {
11392 s.move_with(|snapshot, sel| {
11393 if sel.is_empty() {
11394 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11395 }
11396 });
11397 });
11398 let item = self.cut_common(window, cx);
11399 cx.set_global(KillRing(item))
11400 }
11401
11402 pub fn kill_ring_yank(
11403 &mut self,
11404 _: &KillRingYank,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11409 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11410 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11411 (kill_ring.text().to_string(), kill_ring.metadata_json())
11412 } else {
11413 return;
11414 }
11415 } else {
11416 return;
11417 };
11418 self.do_paste(&text, metadata, false, window, cx);
11419 }
11420
11421 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11422 self.do_copy(true, cx);
11423 }
11424
11425 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11426 self.do_copy(false, cx);
11427 }
11428
11429 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11430 let selections = self.selections.all::<Point>(cx);
11431 let buffer = self.buffer.read(cx).read(cx);
11432 let mut text = String::new();
11433
11434 let mut clipboard_selections = Vec::with_capacity(selections.len());
11435 {
11436 let max_point = buffer.max_point();
11437 let mut is_first = true;
11438 for selection in &selections {
11439 let mut start = selection.start;
11440 let mut end = selection.end;
11441 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11442 if is_entire_line {
11443 start = Point::new(start.row, 0);
11444 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11445 }
11446
11447 let mut trimmed_selections = Vec::new();
11448 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11449 let row = MultiBufferRow(start.row);
11450 let first_indent = buffer.indent_size_for_line(row);
11451 if first_indent.len == 0 || start.column > first_indent.len {
11452 trimmed_selections.push(start..end);
11453 } else {
11454 trimmed_selections.push(
11455 Point::new(row.0, first_indent.len)
11456 ..Point::new(row.0, buffer.line_len(row)),
11457 );
11458 for row in start.row + 1..=end.row {
11459 let mut line_len = buffer.line_len(MultiBufferRow(row));
11460 if row == end.row {
11461 line_len = end.column;
11462 }
11463 if line_len == 0 {
11464 trimmed_selections
11465 .push(Point::new(row, 0)..Point::new(row, line_len));
11466 continue;
11467 }
11468 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11469 if row_indent_size.len >= first_indent.len {
11470 trimmed_selections.push(
11471 Point::new(row, first_indent.len)..Point::new(row, line_len),
11472 );
11473 } else {
11474 trimmed_selections.clear();
11475 trimmed_selections.push(start..end);
11476 break;
11477 }
11478 }
11479 }
11480 } else {
11481 trimmed_selections.push(start..end);
11482 }
11483
11484 for trimmed_range in trimmed_selections {
11485 if is_first {
11486 is_first = false;
11487 } else {
11488 text += "\n";
11489 }
11490 let mut len = 0;
11491 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11492 text.push_str(chunk);
11493 len += chunk.len();
11494 }
11495 clipboard_selections.push(ClipboardSelection {
11496 len,
11497 is_entire_line,
11498 first_line_indent: buffer
11499 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11500 .len,
11501 });
11502 }
11503 }
11504 }
11505
11506 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11507 text,
11508 clipboard_selections,
11509 ));
11510 }
11511
11512 pub fn do_paste(
11513 &mut self,
11514 text: &String,
11515 clipboard_selections: Option<Vec<ClipboardSelection>>,
11516 handle_entire_lines: bool,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 if self.read_only(cx) {
11521 return;
11522 }
11523
11524 let clipboard_text = Cow::Borrowed(text);
11525
11526 self.transact(window, cx, |this, window, cx| {
11527 if let Some(mut clipboard_selections) = clipboard_selections {
11528 let old_selections = this.selections.all::<usize>(cx);
11529 let all_selections_were_entire_line =
11530 clipboard_selections.iter().all(|s| s.is_entire_line);
11531 let first_selection_indent_column =
11532 clipboard_selections.first().map(|s| s.first_line_indent);
11533 if clipboard_selections.len() != old_selections.len() {
11534 clipboard_selections.drain(..);
11535 }
11536 let cursor_offset = this.selections.last::<usize>(cx).head();
11537 let mut auto_indent_on_paste = true;
11538
11539 this.buffer.update(cx, |buffer, cx| {
11540 let snapshot = buffer.read(cx);
11541 auto_indent_on_paste = snapshot
11542 .language_settings_at(cursor_offset, cx)
11543 .auto_indent_on_paste;
11544
11545 let mut start_offset = 0;
11546 let mut edits = Vec::new();
11547 let mut original_indent_columns = Vec::new();
11548 for (ix, selection) in old_selections.iter().enumerate() {
11549 let to_insert;
11550 let entire_line;
11551 let original_indent_column;
11552 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11553 let end_offset = start_offset + clipboard_selection.len;
11554 to_insert = &clipboard_text[start_offset..end_offset];
11555 entire_line = clipboard_selection.is_entire_line;
11556 start_offset = end_offset + 1;
11557 original_indent_column = Some(clipboard_selection.first_line_indent);
11558 } else {
11559 to_insert = clipboard_text.as_str();
11560 entire_line = all_selections_were_entire_line;
11561 original_indent_column = first_selection_indent_column
11562 }
11563
11564 // If the corresponding selection was empty when this slice of the
11565 // clipboard text was written, then the entire line containing the
11566 // selection was copied. If this selection is also currently empty,
11567 // then paste the line before the current line of the buffer.
11568 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11569 let column = selection.start.to_point(&snapshot).column as usize;
11570 let line_start = selection.start - column;
11571 line_start..line_start
11572 } else {
11573 selection.range()
11574 };
11575
11576 edits.push((range, to_insert));
11577 original_indent_columns.push(original_indent_column);
11578 }
11579 drop(snapshot);
11580
11581 buffer.edit(
11582 edits,
11583 if auto_indent_on_paste {
11584 Some(AutoindentMode::Block {
11585 original_indent_columns,
11586 })
11587 } else {
11588 None
11589 },
11590 cx,
11591 );
11592 });
11593
11594 let selections = this.selections.all::<usize>(cx);
11595 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11596 s.select(selections)
11597 });
11598 } else {
11599 this.insert(&clipboard_text, window, cx);
11600 }
11601 });
11602 }
11603
11604 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11606 if let Some(item) = cx.read_from_clipboard() {
11607 let entries = item.entries();
11608
11609 match entries.first() {
11610 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11611 // of all the pasted entries.
11612 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11613 .do_paste(
11614 clipboard_string.text(),
11615 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11616 true,
11617 window,
11618 cx,
11619 ),
11620 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11621 }
11622 }
11623 }
11624
11625 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11626 if self.read_only(cx) {
11627 return;
11628 }
11629
11630 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11631
11632 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11633 if let Some((selections, _)) =
11634 self.selection_history.transaction(transaction_id).cloned()
11635 {
11636 self.change_selections(None, window, cx, |s| {
11637 s.select_anchors(selections.to_vec());
11638 });
11639 } else {
11640 log::error!(
11641 "No entry in selection_history found for undo. \
11642 This may correspond to a bug where undo does not update the selection. \
11643 If this is occurring, please add details to \
11644 https://github.com/zed-industries/zed/issues/22692"
11645 );
11646 }
11647 self.request_autoscroll(Autoscroll::fit(), cx);
11648 self.unmark_text(window, cx);
11649 self.refresh_inline_completion(true, false, window, cx);
11650 cx.emit(EditorEvent::Edited { transaction_id });
11651 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11652 }
11653 }
11654
11655 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11656 if self.read_only(cx) {
11657 return;
11658 }
11659
11660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11661
11662 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11663 if let Some((_, Some(selections))) =
11664 self.selection_history.transaction(transaction_id).cloned()
11665 {
11666 self.change_selections(None, window, cx, |s| {
11667 s.select_anchors(selections.to_vec());
11668 });
11669 } else {
11670 log::error!(
11671 "No entry in selection_history found for redo. \
11672 This may correspond to a bug where undo does not update the selection. \
11673 If this is occurring, please add details to \
11674 https://github.com/zed-industries/zed/issues/22692"
11675 );
11676 }
11677 self.request_autoscroll(Autoscroll::fit(), cx);
11678 self.unmark_text(window, cx);
11679 self.refresh_inline_completion(true, false, window, cx);
11680 cx.emit(EditorEvent::Edited { transaction_id });
11681 }
11682 }
11683
11684 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11685 self.buffer
11686 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11687 }
11688
11689 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11690 self.buffer
11691 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11692 }
11693
11694 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11697 s.move_with(|map, selection| {
11698 let cursor = if selection.is_empty() {
11699 movement::left(map, selection.start)
11700 } else {
11701 selection.start
11702 };
11703 selection.collapse_to(cursor, SelectionGoal::None);
11704 });
11705 })
11706 }
11707
11708 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11711 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11712 })
11713 }
11714
11715 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11718 s.move_with(|map, selection| {
11719 let cursor = if selection.is_empty() {
11720 movement::right(map, selection.end)
11721 } else {
11722 selection.end
11723 };
11724 selection.collapse_to(cursor, SelectionGoal::None)
11725 });
11726 })
11727 }
11728
11729 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11732 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11733 })
11734 }
11735
11736 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11737 if self.take_rename(true, window, cx).is_some() {
11738 return;
11739 }
11740
11741 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11742 cx.propagate();
11743 return;
11744 }
11745
11746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11747
11748 let text_layout_details = &self.text_layout_details(window);
11749 let selection_count = self.selections.count();
11750 let first_selection = self.selections.first_anchor();
11751
11752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11753 s.move_with(|map, selection| {
11754 if !selection.is_empty() {
11755 selection.goal = SelectionGoal::None;
11756 }
11757 let (cursor, goal) = movement::up(
11758 map,
11759 selection.start,
11760 selection.goal,
11761 false,
11762 text_layout_details,
11763 );
11764 selection.collapse_to(cursor, goal);
11765 });
11766 });
11767
11768 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11769 {
11770 cx.propagate();
11771 }
11772 }
11773
11774 pub fn move_up_by_lines(
11775 &mut self,
11776 action: &MoveUpByLines,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) {
11780 if self.take_rename(true, window, cx).is_some() {
11781 return;
11782 }
11783
11784 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11785 cx.propagate();
11786 return;
11787 }
11788
11789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11790
11791 let text_layout_details = &self.text_layout_details(window);
11792
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.move_with(|map, selection| {
11795 if !selection.is_empty() {
11796 selection.goal = SelectionGoal::None;
11797 }
11798 let (cursor, goal) = movement::up_by_rows(
11799 map,
11800 selection.start,
11801 action.lines,
11802 selection.goal,
11803 false,
11804 text_layout_details,
11805 );
11806 selection.collapse_to(cursor, goal);
11807 });
11808 })
11809 }
11810
11811 pub fn move_down_by_lines(
11812 &mut self,
11813 action: &MoveDownByLines,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 if self.take_rename(true, window, cx).is_some() {
11818 return;
11819 }
11820
11821 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11822 cx.propagate();
11823 return;
11824 }
11825
11826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11827
11828 let text_layout_details = &self.text_layout_details(window);
11829
11830 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11831 s.move_with(|map, selection| {
11832 if !selection.is_empty() {
11833 selection.goal = SelectionGoal::None;
11834 }
11835 let (cursor, goal) = movement::down_by_rows(
11836 map,
11837 selection.start,
11838 action.lines,
11839 selection.goal,
11840 false,
11841 text_layout_details,
11842 );
11843 selection.collapse_to(cursor, goal);
11844 });
11845 })
11846 }
11847
11848 pub fn select_down_by_lines(
11849 &mut self,
11850 action: &SelectDownByLines,
11851 window: &mut Window,
11852 cx: &mut Context<Self>,
11853 ) {
11854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11855 let text_layout_details = &self.text_layout_details(window);
11856 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11857 s.move_heads_with(|map, head, goal| {
11858 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11859 })
11860 })
11861 }
11862
11863 pub fn select_up_by_lines(
11864 &mut self,
11865 action: &SelectUpByLines,
11866 window: &mut Window,
11867 cx: &mut Context<Self>,
11868 ) {
11869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11870 let text_layout_details = &self.text_layout_details(window);
11871 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11872 s.move_heads_with(|map, head, goal| {
11873 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11874 })
11875 })
11876 }
11877
11878 pub fn select_page_up(
11879 &mut self,
11880 _: &SelectPageUp,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 let Some(row_count) = self.visible_row_count() else {
11885 return;
11886 };
11887
11888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11889
11890 let text_layout_details = &self.text_layout_details(window);
11891
11892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11893 s.move_heads_with(|map, head, goal| {
11894 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11895 })
11896 })
11897 }
11898
11899 pub fn move_page_up(
11900 &mut self,
11901 action: &MovePageUp,
11902 window: &mut Window,
11903 cx: &mut Context<Self>,
11904 ) {
11905 if self.take_rename(true, window, cx).is_some() {
11906 return;
11907 }
11908
11909 if self
11910 .context_menu
11911 .borrow_mut()
11912 .as_mut()
11913 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11914 .unwrap_or(false)
11915 {
11916 return;
11917 }
11918
11919 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11920 cx.propagate();
11921 return;
11922 }
11923
11924 let Some(row_count) = self.visible_row_count() else {
11925 return;
11926 };
11927
11928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11929
11930 let autoscroll = if action.center_cursor {
11931 Autoscroll::center()
11932 } else {
11933 Autoscroll::fit()
11934 };
11935
11936 let text_layout_details = &self.text_layout_details(window);
11937
11938 self.change_selections(Some(autoscroll), window, cx, |s| {
11939 s.move_with(|map, selection| {
11940 if !selection.is_empty() {
11941 selection.goal = SelectionGoal::None;
11942 }
11943 let (cursor, goal) = movement::up_by_rows(
11944 map,
11945 selection.end,
11946 row_count,
11947 selection.goal,
11948 false,
11949 text_layout_details,
11950 );
11951 selection.collapse_to(cursor, goal);
11952 });
11953 });
11954 }
11955
11956 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11958 let text_layout_details = &self.text_layout_details(window);
11959 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11960 s.move_heads_with(|map, head, goal| {
11961 movement::up(map, head, goal, false, text_layout_details)
11962 })
11963 })
11964 }
11965
11966 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11967 self.take_rename(true, window, cx);
11968
11969 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11970 cx.propagate();
11971 return;
11972 }
11973
11974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11975
11976 let text_layout_details = &self.text_layout_details(window);
11977 let selection_count = self.selections.count();
11978 let first_selection = self.selections.first_anchor();
11979
11980 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11981 s.move_with(|map, selection| {
11982 if !selection.is_empty() {
11983 selection.goal = SelectionGoal::None;
11984 }
11985 let (cursor, goal) = movement::down(
11986 map,
11987 selection.end,
11988 selection.goal,
11989 false,
11990 text_layout_details,
11991 );
11992 selection.collapse_to(cursor, goal);
11993 });
11994 });
11995
11996 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11997 {
11998 cx.propagate();
11999 }
12000 }
12001
12002 pub fn select_page_down(
12003 &mut self,
12004 _: &SelectPageDown,
12005 window: &mut Window,
12006 cx: &mut Context<Self>,
12007 ) {
12008 let Some(row_count) = self.visible_row_count() else {
12009 return;
12010 };
12011
12012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12013
12014 let text_layout_details = &self.text_layout_details(window);
12015
12016 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12017 s.move_heads_with(|map, head, goal| {
12018 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12019 })
12020 })
12021 }
12022
12023 pub fn move_page_down(
12024 &mut self,
12025 action: &MovePageDown,
12026 window: &mut Window,
12027 cx: &mut Context<Self>,
12028 ) {
12029 if self.take_rename(true, window, cx).is_some() {
12030 return;
12031 }
12032
12033 if self
12034 .context_menu
12035 .borrow_mut()
12036 .as_mut()
12037 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12038 .unwrap_or(false)
12039 {
12040 return;
12041 }
12042
12043 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12044 cx.propagate();
12045 return;
12046 }
12047
12048 let Some(row_count) = self.visible_row_count() else {
12049 return;
12050 };
12051
12052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12053
12054 let autoscroll = if action.center_cursor {
12055 Autoscroll::center()
12056 } else {
12057 Autoscroll::fit()
12058 };
12059
12060 let text_layout_details = &self.text_layout_details(window);
12061 self.change_selections(Some(autoscroll), window, cx, |s| {
12062 s.move_with(|map, selection| {
12063 if !selection.is_empty() {
12064 selection.goal = SelectionGoal::None;
12065 }
12066 let (cursor, goal) = movement::down_by_rows(
12067 map,
12068 selection.end,
12069 row_count,
12070 selection.goal,
12071 false,
12072 text_layout_details,
12073 );
12074 selection.collapse_to(cursor, goal);
12075 });
12076 });
12077 }
12078
12079 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12081 let text_layout_details = &self.text_layout_details(window);
12082 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12083 s.move_heads_with(|map, head, goal| {
12084 movement::down(map, head, goal, false, text_layout_details)
12085 })
12086 });
12087 }
12088
12089 pub fn context_menu_first(
12090 &mut self,
12091 _: &ContextMenuFirst,
12092 window: &mut Window,
12093 cx: &mut Context<Self>,
12094 ) {
12095 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12096 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12097 }
12098 }
12099
12100 pub fn context_menu_prev(
12101 &mut self,
12102 _: &ContextMenuPrevious,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12107 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12108 }
12109 }
12110
12111 pub fn context_menu_next(
12112 &mut self,
12113 _: &ContextMenuNext,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12118 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12119 }
12120 }
12121
12122 pub fn context_menu_last(
12123 &mut self,
12124 _: &ContextMenuLast,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12129 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12130 }
12131 }
12132
12133 pub fn move_to_previous_word_start(
12134 &mut self,
12135 _: &MoveToPreviousWordStart,
12136 window: &mut Window,
12137 cx: &mut Context<Self>,
12138 ) {
12139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_cursors_with(|map, head, _| {
12142 (
12143 movement::previous_word_start(map, head),
12144 SelectionGoal::None,
12145 )
12146 });
12147 })
12148 }
12149
12150 pub fn move_to_previous_subword_start(
12151 &mut self,
12152 _: &MoveToPreviousSubwordStart,
12153 window: &mut Window,
12154 cx: &mut Context<Self>,
12155 ) {
12156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12158 s.move_cursors_with(|map, head, _| {
12159 (
12160 movement::previous_subword_start(map, head),
12161 SelectionGoal::None,
12162 )
12163 });
12164 })
12165 }
12166
12167 pub fn select_to_previous_word_start(
12168 &mut self,
12169 _: &SelectToPreviousWordStart,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12175 s.move_heads_with(|map, head, _| {
12176 (
12177 movement::previous_word_start(map, head),
12178 SelectionGoal::None,
12179 )
12180 });
12181 })
12182 }
12183
12184 pub fn select_to_previous_subword_start(
12185 &mut self,
12186 _: &SelectToPreviousSubwordStart,
12187 window: &mut Window,
12188 cx: &mut Context<Self>,
12189 ) {
12190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12192 s.move_heads_with(|map, head, _| {
12193 (
12194 movement::previous_subword_start(map, head),
12195 SelectionGoal::None,
12196 )
12197 });
12198 })
12199 }
12200
12201 pub fn delete_to_previous_word_start(
12202 &mut self,
12203 action: &DeleteToPreviousWordStart,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) {
12207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12208 self.transact(window, cx, |this, window, cx| {
12209 this.select_autoclose_pair(window, cx);
12210 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12211 s.move_with(|map, selection| {
12212 if selection.is_empty() {
12213 let cursor = if action.ignore_newlines {
12214 movement::previous_word_start(map, selection.head())
12215 } else {
12216 movement::previous_word_start_or_newline(map, selection.head())
12217 };
12218 selection.set_head(cursor, SelectionGoal::None);
12219 }
12220 });
12221 });
12222 this.insert("", window, cx);
12223 });
12224 }
12225
12226 pub fn delete_to_previous_subword_start(
12227 &mut self,
12228 _: &DeleteToPreviousSubwordStart,
12229 window: &mut Window,
12230 cx: &mut Context<Self>,
12231 ) {
12232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12233 self.transact(window, cx, |this, window, cx| {
12234 this.select_autoclose_pair(window, cx);
12235 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12236 s.move_with(|map, selection| {
12237 if selection.is_empty() {
12238 let cursor = movement::previous_subword_start(map, selection.head());
12239 selection.set_head(cursor, SelectionGoal::None);
12240 }
12241 });
12242 });
12243 this.insert("", window, cx);
12244 });
12245 }
12246
12247 pub fn move_to_next_word_end(
12248 &mut self,
12249 _: &MoveToNextWordEnd,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12254 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12255 s.move_cursors_with(|map, head, _| {
12256 (movement::next_word_end(map, head), SelectionGoal::None)
12257 });
12258 })
12259 }
12260
12261 pub fn move_to_next_subword_end(
12262 &mut self,
12263 _: &MoveToNextSubwordEnd,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) {
12267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12268 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12269 s.move_cursors_with(|map, head, _| {
12270 (movement::next_subword_end(map, head), SelectionGoal::None)
12271 });
12272 })
12273 }
12274
12275 pub fn select_to_next_word_end(
12276 &mut self,
12277 _: &SelectToNextWordEnd,
12278 window: &mut Window,
12279 cx: &mut Context<Self>,
12280 ) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12283 s.move_heads_with(|map, head, _| {
12284 (movement::next_word_end(map, head), SelectionGoal::None)
12285 });
12286 })
12287 }
12288
12289 pub fn select_to_next_subword_end(
12290 &mut self,
12291 _: &SelectToNextSubwordEnd,
12292 window: &mut Window,
12293 cx: &mut Context<Self>,
12294 ) {
12295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12296 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12297 s.move_heads_with(|map, head, _| {
12298 (movement::next_subword_end(map, head), SelectionGoal::None)
12299 });
12300 })
12301 }
12302
12303 pub fn delete_to_next_word_end(
12304 &mut self,
12305 action: &DeleteToNextWordEnd,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) {
12309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12310 self.transact(window, cx, |this, window, cx| {
12311 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12312 s.move_with(|map, selection| {
12313 if selection.is_empty() {
12314 let cursor = if action.ignore_newlines {
12315 movement::next_word_end(map, selection.head())
12316 } else {
12317 movement::next_word_end_or_newline(map, selection.head())
12318 };
12319 selection.set_head(cursor, SelectionGoal::None);
12320 }
12321 });
12322 });
12323 this.insert("", window, cx);
12324 });
12325 }
12326
12327 pub fn delete_to_next_subword_end(
12328 &mut self,
12329 _: &DeleteToNextSubwordEnd,
12330 window: &mut Window,
12331 cx: &mut Context<Self>,
12332 ) {
12333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12334 self.transact(window, cx, |this, window, cx| {
12335 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12336 s.move_with(|map, selection| {
12337 if selection.is_empty() {
12338 let cursor = movement::next_subword_end(map, selection.head());
12339 selection.set_head(cursor, SelectionGoal::None);
12340 }
12341 });
12342 });
12343 this.insert("", window, cx);
12344 });
12345 }
12346
12347 pub fn move_to_beginning_of_line(
12348 &mut self,
12349 action: &MoveToBeginningOfLine,
12350 window: &mut Window,
12351 cx: &mut Context<Self>,
12352 ) {
12353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12355 s.move_cursors_with(|map, head, _| {
12356 (
12357 movement::indented_line_beginning(
12358 map,
12359 head,
12360 action.stop_at_soft_wraps,
12361 action.stop_at_indent,
12362 ),
12363 SelectionGoal::None,
12364 )
12365 });
12366 })
12367 }
12368
12369 pub fn select_to_beginning_of_line(
12370 &mut self,
12371 action: &SelectToBeginningOfLine,
12372 window: &mut Window,
12373 cx: &mut Context<Self>,
12374 ) {
12375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12376 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12377 s.move_heads_with(|map, head, _| {
12378 (
12379 movement::indented_line_beginning(
12380 map,
12381 head,
12382 action.stop_at_soft_wraps,
12383 action.stop_at_indent,
12384 ),
12385 SelectionGoal::None,
12386 )
12387 });
12388 });
12389 }
12390
12391 pub fn delete_to_beginning_of_line(
12392 &mut self,
12393 action: &DeleteToBeginningOfLine,
12394 window: &mut Window,
12395 cx: &mut Context<Self>,
12396 ) {
12397 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12398 self.transact(window, cx, |this, window, cx| {
12399 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12400 s.move_with(|_, selection| {
12401 selection.reversed = true;
12402 });
12403 });
12404
12405 this.select_to_beginning_of_line(
12406 &SelectToBeginningOfLine {
12407 stop_at_soft_wraps: false,
12408 stop_at_indent: action.stop_at_indent,
12409 },
12410 window,
12411 cx,
12412 );
12413 this.backspace(&Backspace, window, cx);
12414 });
12415 }
12416
12417 pub fn move_to_end_of_line(
12418 &mut self,
12419 action: &MoveToEndOfLine,
12420 window: &mut Window,
12421 cx: &mut Context<Self>,
12422 ) {
12423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12425 s.move_cursors_with(|map, head, _| {
12426 (
12427 movement::line_end(map, head, action.stop_at_soft_wraps),
12428 SelectionGoal::None,
12429 )
12430 });
12431 })
12432 }
12433
12434 pub fn select_to_end_of_line(
12435 &mut self,
12436 action: &SelectToEndOfLine,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12441 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12442 s.move_heads_with(|map, head, _| {
12443 (
12444 movement::line_end(map, head, action.stop_at_soft_wraps),
12445 SelectionGoal::None,
12446 )
12447 });
12448 })
12449 }
12450
12451 pub fn delete_to_end_of_line(
12452 &mut self,
12453 _: &DeleteToEndOfLine,
12454 window: &mut Window,
12455 cx: &mut Context<Self>,
12456 ) {
12457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12458 self.transact(window, cx, |this, window, cx| {
12459 this.select_to_end_of_line(
12460 &SelectToEndOfLine {
12461 stop_at_soft_wraps: false,
12462 },
12463 window,
12464 cx,
12465 );
12466 this.delete(&Delete, window, cx);
12467 });
12468 }
12469
12470 pub fn cut_to_end_of_line(
12471 &mut self,
12472 _: &CutToEndOfLine,
12473 window: &mut Window,
12474 cx: &mut Context<Self>,
12475 ) {
12476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12477 self.transact(window, cx, |this, window, cx| {
12478 this.select_to_end_of_line(
12479 &SelectToEndOfLine {
12480 stop_at_soft_wraps: false,
12481 },
12482 window,
12483 cx,
12484 );
12485 this.cut(&Cut, window, cx);
12486 });
12487 }
12488
12489 pub fn move_to_start_of_paragraph(
12490 &mut self,
12491 _: &MoveToStartOfParagraph,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) {
12495 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12496 cx.propagate();
12497 return;
12498 }
12499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12500 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12501 s.move_with(|map, selection| {
12502 selection.collapse_to(
12503 movement::start_of_paragraph(map, selection.head(), 1),
12504 SelectionGoal::None,
12505 )
12506 });
12507 })
12508 }
12509
12510 pub fn move_to_end_of_paragraph(
12511 &mut self,
12512 _: &MoveToEndOfParagraph,
12513 window: &mut Window,
12514 cx: &mut Context<Self>,
12515 ) {
12516 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12517 cx.propagate();
12518 return;
12519 }
12520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12521 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12522 s.move_with(|map, selection| {
12523 selection.collapse_to(
12524 movement::end_of_paragraph(map, selection.head(), 1),
12525 SelectionGoal::None,
12526 )
12527 });
12528 })
12529 }
12530
12531 pub fn select_to_start_of_paragraph(
12532 &mut self,
12533 _: &SelectToStartOfParagraph,
12534 window: &mut Window,
12535 cx: &mut Context<Self>,
12536 ) {
12537 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12538 cx.propagate();
12539 return;
12540 }
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12543 s.move_heads_with(|map, head, _| {
12544 (
12545 movement::start_of_paragraph(map, head, 1),
12546 SelectionGoal::None,
12547 )
12548 });
12549 })
12550 }
12551
12552 pub fn select_to_end_of_paragraph(
12553 &mut self,
12554 _: &SelectToEndOfParagraph,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12559 cx.propagate();
12560 return;
12561 }
12562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12563 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12564 s.move_heads_with(|map, head, _| {
12565 (
12566 movement::end_of_paragraph(map, head, 1),
12567 SelectionGoal::None,
12568 )
12569 });
12570 })
12571 }
12572
12573 pub fn move_to_start_of_excerpt(
12574 &mut self,
12575 _: &MoveToStartOfExcerpt,
12576 window: &mut Window,
12577 cx: &mut Context<Self>,
12578 ) {
12579 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12580 cx.propagate();
12581 return;
12582 }
12583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12584 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12585 s.move_with(|map, selection| {
12586 selection.collapse_to(
12587 movement::start_of_excerpt(
12588 map,
12589 selection.head(),
12590 workspace::searchable::Direction::Prev,
12591 ),
12592 SelectionGoal::None,
12593 )
12594 });
12595 })
12596 }
12597
12598 pub fn move_to_start_of_next_excerpt(
12599 &mut self,
12600 _: &MoveToStartOfNextExcerpt,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12605 cx.propagate();
12606 return;
12607 }
12608
12609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12610 s.move_with(|map, selection| {
12611 selection.collapse_to(
12612 movement::start_of_excerpt(
12613 map,
12614 selection.head(),
12615 workspace::searchable::Direction::Next,
12616 ),
12617 SelectionGoal::None,
12618 )
12619 });
12620 })
12621 }
12622
12623 pub fn move_to_end_of_excerpt(
12624 &mut self,
12625 _: &MoveToEndOfExcerpt,
12626 window: &mut Window,
12627 cx: &mut Context<Self>,
12628 ) {
12629 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12630 cx.propagate();
12631 return;
12632 }
12633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12635 s.move_with(|map, selection| {
12636 selection.collapse_to(
12637 movement::end_of_excerpt(
12638 map,
12639 selection.head(),
12640 workspace::searchable::Direction::Next,
12641 ),
12642 SelectionGoal::None,
12643 )
12644 });
12645 })
12646 }
12647
12648 pub fn move_to_end_of_previous_excerpt(
12649 &mut self,
12650 _: &MoveToEndOfPreviousExcerpt,
12651 window: &mut Window,
12652 cx: &mut Context<Self>,
12653 ) {
12654 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12655 cx.propagate();
12656 return;
12657 }
12658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12660 s.move_with(|map, selection| {
12661 selection.collapse_to(
12662 movement::end_of_excerpt(
12663 map,
12664 selection.head(),
12665 workspace::searchable::Direction::Prev,
12666 ),
12667 SelectionGoal::None,
12668 )
12669 });
12670 })
12671 }
12672
12673 pub fn select_to_start_of_excerpt(
12674 &mut self,
12675 _: &SelectToStartOfExcerpt,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) {
12679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12680 cx.propagate();
12681 return;
12682 }
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12685 s.move_heads_with(|map, head, _| {
12686 (
12687 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12688 SelectionGoal::None,
12689 )
12690 });
12691 })
12692 }
12693
12694 pub fn select_to_start_of_next_excerpt(
12695 &mut self,
12696 _: &SelectToStartOfNextExcerpt,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) {
12700 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12701 cx.propagate();
12702 return;
12703 }
12704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12705 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12706 s.move_heads_with(|map, head, _| {
12707 (
12708 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12709 SelectionGoal::None,
12710 )
12711 });
12712 })
12713 }
12714
12715 pub fn select_to_end_of_excerpt(
12716 &mut self,
12717 _: &SelectToEndOfExcerpt,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12722 cx.propagate();
12723 return;
12724 }
12725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12727 s.move_heads_with(|map, head, _| {
12728 (
12729 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12730 SelectionGoal::None,
12731 )
12732 });
12733 })
12734 }
12735
12736 pub fn select_to_end_of_previous_excerpt(
12737 &mut self,
12738 _: &SelectToEndOfPreviousExcerpt,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12743 cx.propagate();
12744 return;
12745 }
12746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12748 s.move_heads_with(|map, head, _| {
12749 (
12750 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12751 SelectionGoal::None,
12752 )
12753 });
12754 })
12755 }
12756
12757 pub fn move_to_beginning(
12758 &mut self,
12759 _: &MoveToBeginning,
12760 window: &mut Window,
12761 cx: &mut Context<Self>,
12762 ) {
12763 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12764 cx.propagate();
12765 return;
12766 }
12767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12769 s.select_ranges(vec![0..0]);
12770 });
12771 }
12772
12773 pub fn select_to_beginning(
12774 &mut self,
12775 _: &SelectToBeginning,
12776 window: &mut Window,
12777 cx: &mut Context<Self>,
12778 ) {
12779 let mut selection = self.selections.last::<Point>(cx);
12780 selection.set_head(Point::zero(), SelectionGoal::None);
12781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12783 s.select(vec![selection]);
12784 });
12785 }
12786
12787 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12788 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12789 cx.propagate();
12790 return;
12791 }
12792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12793 let cursor = self.buffer.read(cx).read(cx).len();
12794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12795 s.select_ranges(vec![cursor..cursor])
12796 });
12797 }
12798
12799 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12800 self.nav_history = nav_history;
12801 }
12802
12803 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12804 self.nav_history.as_ref()
12805 }
12806
12807 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12808 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12809 }
12810
12811 fn push_to_nav_history(
12812 &mut self,
12813 cursor_anchor: Anchor,
12814 new_position: Option<Point>,
12815 is_deactivate: bool,
12816 cx: &mut Context<Self>,
12817 ) {
12818 if let Some(nav_history) = self.nav_history.as_mut() {
12819 let buffer = self.buffer.read(cx).read(cx);
12820 let cursor_position = cursor_anchor.to_point(&buffer);
12821 let scroll_state = self.scroll_manager.anchor();
12822 let scroll_top_row = scroll_state.top_row(&buffer);
12823 drop(buffer);
12824
12825 if let Some(new_position) = new_position {
12826 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12827 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12828 return;
12829 }
12830 }
12831
12832 nav_history.push(
12833 Some(NavigationData {
12834 cursor_anchor,
12835 cursor_position,
12836 scroll_anchor: scroll_state,
12837 scroll_top_row,
12838 }),
12839 cx,
12840 );
12841 cx.emit(EditorEvent::PushedToNavHistory {
12842 anchor: cursor_anchor,
12843 is_deactivate,
12844 })
12845 }
12846 }
12847
12848 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12850 let buffer = self.buffer.read(cx).snapshot(cx);
12851 let mut selection = self.selections.first::<usize>(cx);
12852 selection.set_head(buffer.len(), SelectionGoal::None);
12853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12854 s.select(vec![selection]);
12855 });
12856 }
12857
12858 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12860 let end = self.buffer.read(cx).read(cx).len();
12861 self.change_selections(None, window, cx, |s| {
12862 s.select_ranges(vec![0..end]);
12863 });
12864 }
12865
12866 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12869 let mut selections = self.selections.all::<Point>(cx);
12870 let max_point = display_map.buffer_snapshot.max_point();
12871 for selection in &mut selections {
12872 let rows = selection.spanned_rows(true, &display_map);
12873 selection.start = Point::new(rows.start.0, 0);
12874 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12875 selection.reversed = false;
12876 }
12877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12878 s.select(selections);
12879 });
12880 }
12881
12882 pub fn split_selection_into_lines(
12883 &mut self,
12884 _: &SplitSelectionIntoLines,
12885 window: &mut Window,
12886 cx: &mut Context<Self>,
12887 ) {
12888 let selections = self
12889 .selections
12890 .all::<Point>(cx)
12891 .into_iter()
12892 .map(|selection| selection.start..selection.end)
12893 .collect::<Vec<_>>();
12894 self.unfold_ranges(&selections, true, true, cx);
12895
12896 let mut new_selection_ranges = Vec::new();
12897 {
12898 let buffer = self.buffer.read(cx).read(cx);
12899 for selection in selections {
12900 for row in selection.start.row..selection.end.row {
12901 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12902 new_selection_ranges.push(cursor..cursor);
12903 }
12904
12905 let is_multiline_selection = selection.start.row != selection.end.row;
12906 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12907 // so this action feels more ergonomic when paired with other selection operations
12908 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12909 if !should_skip_last {
12910 new_selection_ranges.push(selection.end..selection.end);
12911 }
12912 }
12913 }
12914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12915 s.select_ranges(new_selection_ranges);
12916 });
12917 }
12918
12919 pub fn add_selection_above(
12920 &mut self,
12921 _: &AddSelectionAbove,
12922 window: &mut Window,
12923 cx: &mut Context<Self>,
12924 ) {
12925 self.add_selection(true, window, cx);
12926 }
12927
12928 pub fn add_selection_below(
12929 &mut self,
12930 _: &AddSelectionBelow,
12931 window: &mut Window,
12932 cx: &mut Context<Self>,
12933 ) {
12934 self.add_selection(false, window, cx);
12935 }
12936
12937 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12939
12940 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12941 let all_selections = self.selections.all::<Point>(cx);
12942 let text_layout_details = self.text_layout_details(window);
12943
12944 let (mut columnar_selections, new_selections_to_columnarize) = {
12945 if let Some(state) = self.add_selections_state.as_ref() {
12946 let columnar_selection_ids: HashSet<_> = state
12947 .groups
12948 .iter()
12949 .flat_map(|group| group.stack.iter())
12950 .copied()
12951 .collect();
12952
12953 all_selections
12954 .into_iter()
12955 .partition(|s| columnar_selection_ids.contains(&s.id))
12956 } else {
12957 (Vec::new(), all_selections)
12958 }
12959 };
12960
12961 let mut state = self
12962 .add_selections_state
12963 .take()
12964 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12965
12966 for selection in new_selections_to_columnarize {
12967 let range = selection.display_range(&display_map).sorted();
12968 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12969 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12970 let positions = start_x.min(end_x)..start_x.max(end_x);
12971 let mut stack = Vec::new();
12972 for row in range.start.row().0..=range.end.row().0 {
12973 if let Some(selection) = self.selections.build_columnar_selection(
12974 &display_map,
12975 DisplayRow(row),
12976 &positions,
12977 selection.reversed,
12978 &text_layout_details,
12979 ) {
12980 stack.push(selection.id);
12981 columnar_selections.push(selection);
12982 }
12983 }
12984 if !stack.is_empty() {
12985 if above {
12986 stack.reverse();
12987 }
12988 state.groups.push(AddSelectionsGroup { above, stack });
12989 }
12990 }
12991
12992 let mut final_selections = Vec::new();
12993 let end_row = if above {
12994 DisplayRow(0)
12995 } else {
12996 display_map.max_point().row()
12997 };
12998
12999 let mut last_added_item_per_group = HashMap::default();
13000 for group in state.groups.iter_mut() {
13001 if let Some(last_id) = group.stack.last() {
13002 last_added_item_per_group.insert(*last_id, group);
13003 }
13004 }
13005
13006 for selection in columnar_selections {
13007 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13008 if above == group.above {
13009 let range = selection.display_range(&display_map).sorted();
13010 debug_assert_eq!(range.start.row(), range.end.row());
13011 let mut row = range.start.row();
13012 let positions =
13013 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13014 px(start)..px(end)
13015 } else {
13016 let start_x =
13017 display_map.x_for_display_point(range.start, &text_layout_details);
13018 let end_x =
13019 display_map.x_for_display_point(range.end, &text_layout_details);
13020 start_x.min(end_x)..start_x.max(end_x)
13021 };
13022
13023 let mut maybe_new_selection = None;
13024 while row != end_row {
13025 if above {
13026 row.0 -= 1;
13027 } else {
13028 row.0 += 1;
13029 }
13030 if let Some(new_selection) = self.selections.build_columnar_selection(
13031 &display_map,
13032 row,
13033 &positions,
13034 selection.reversed,
13035 &text_layout_details,
13036 ) {
13037 maybe_new_selection = Some(new_selection);
13038 break;
13039 }
13040 }
13041
13042 if let Some(new_selection) = maybe_new_selection {
13043 group.stack.push(new_selection.id);
13044 if above {
13045 final_selections.push(new_selection);
13046 final_selections.push(selection);
13047 } else {
13048 final_selections.push(selection);
13049 final_selections.push(new_selection);
13050 }
13051 } else {
13052 final_selections.push(selection);
13053 }
13054 } else {
13055 group.stack.pop();
13056 }
13057 } else {
13058 final_selections.push(selection);
13059 }
13060 }
13061
13062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13063 s.select(final_selections);
13064 });
13065
13066 let final_selection_ids: HashSet<_> = self
13067 .selections
13068 .all::<Point>(cx)
13069 .iter()
13070 .map(|s| s.id)
13071 .collect();
13072 state.groups.retain_mut(|group| {
13073 // selections might get merged above so we remove invalid items from stacks
13074 group.stack.retain(|id| final_selection_ids.contains(id));
13075
13076 // single selection in stack can be treated as initial state
13077 group.stack.len() > 1
13078 });
13079
13080 if !state.groups.is_empty() {
13081 self.add_selections_state = Some(state);
13082 }
13083 }
13084
13085 fn select_match_ranges(
13086 &mut self,
13087 range: Range<usize>,
13088 reversed: bool,
13089 replace_newest: bool,
13090 auto_scroll: Option<Autoscroll>,
13091 window: &mut Window,
13092 cx: &mut Context<Editor>,
13093 ) {
13094 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13095 self.change_selections(auto_scroll, window, cx, |s| {
13096 if replace_newest {
13097 s.delete(s.newest_anchor().id);
13098 }
13099 if reversed {
13100 s.insert_range(range.end..range.start);
13101 } else {
13102 s.insert_range(range);
13103 }
13104 });
13105 }
13106
13107 pub fn select_next_match_internal(
13108 &mut self,
13109 display_map: &DisplaySnapshot,
13110 replace_newest: bool,
13111 autoscroll: Option<Autoscroll>,
13112 window: &mut Window,
13113 cx: &mut Context<Self>,
13114 ) -> Result<()> {
13115 let buffer = &display_map.buffer_snapshot;
13116 let mut selections = self.selections.all::<usize>(cx);
13117 if let Some(mut select_next_state) = self.select_next_state.take() {
13118 let query = &select_next_state.query;
13119 if !select_next_state.done {
13120 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13121 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13122 let mut next_selected_range = None;
13123
13124 let bytes_after_last_selection =
13125 buffer.bytes_in_range(last_selection.end..buffer.len());
13126 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13127 let query_matches = query
13128 .stream_find_iter(bytes_after_last_selection)
13129 .map(|result| (last_selection.end, result))
13130 .chain(
13131 query
13132 .stream_find_iter(bytes_before_first_selection)
13133 .map(|result| (0, result)),
13134 );
13135
13136 for (start_offset, query_match) in query_matches {
13137 let query_match = query_match.unwrap(); // can only fail due to I/O
13138 let offset_range =
13139 start_offset + query_match.start()..start_offset + query_match.end();
13140 let display_range = offset_range.start.to_display_point(display_map)
13141 ..offset_range.end.to_display_point(display_map);
13142
13143 if !select_next_state.wordwise
13144 || (!movement::is_inside_word(display_map, display_range.start)
13145 && !movement::is_inside_word(display_map, display_range.end))
13146 {
13147 // TODO: This is n^2, because we might check all the selections
13148 if !selections
13149 .iter()
13150 .any(|selection| selection.range().overlaps(&offset_range))
13151 {
13152 next_selected_range = Some(offset_range);
13153 break;
13154 }
13155 }
13156 }
13157
13158 if let Some(next_selected_range) = next_selected_range {
13159 self.select_match_ranges(
13160 next_selected_range,
13161 last_selection.reversed,
13162 replace_newest,
13163 autoscroll,
13164 window,
13165 cx,
13166 );
13167 } else {
13168 select_next_state.done = true;
13169 }
13170 }
13171
13172 self.select_next_state = Some(select_next_state);
13173 } else {
13174 let mut only_carets = true;
13175 let mut same_text_selected = true;
13176 let mut selected_text = None;
13177
13178 let mut selections_iter = selections.iter().peekable();
13179 while let Some(selection) = selections_iter.next() {
13180 if selection.start != selection.end {
13181 only_carets = false;
13182 }
13183
13184 if same_text_selected {
13185 if selected_text.is_none() {
13186 selected_text =
13187 Some(buffer.text_for_range(selection.range()).collect::<String>());
13188 }
13189
13190 if let Some(next_selection) = selections_iter.peek() {
13191 if next_selection.range().len() == selection.range().len() {
13192 let next_selected_text = buffer
13193 .text_for_range(next_selection.range())
13194 .collect::<String>();
13195 if Some(next_selected_text) != selected_text {
13196 same_text_selected = false;
13197 selected_text = None;
13198 }
13199 } else {
13200 same_text_selected = false;
13201 selected_text = None;
13202 }
13203 }
13204 }
13205 }
13206
13207 if only_carets {
13208 for selection in &mut selections {
13209 let word_range = movement::surrounding_word(
13210 display_map,
13211 selection.start.to_display_point(display_map),
13212 );
13213 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13214 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13215 selection.goal = SelectionGoal::None;
13216 selection.reversed = false;
13217 self.select_match_ranges(
13218 selection.start..selection.end,
13219 selection.reversed,
13220 replace_newest,
13221 autoscroll,
13222 window,
13223 cx,
13224 );
13225 }
13226
13227 if selections.len() == 1 {
13228 let selection = selections
13229 .last()
13230 .expect("ensured that there's only one selection");
13231 let query = buffer
13232 .text_for_range(selection.start..selection.end)
13233 .collect::<String>();
13234 let is_empty = query.is_empty();
13235 let select_state = SelectNextState {
13236 query: AhoCorasick::new(&[query])?,
13237 wordwise: true,
13238 done: is_empty,
13239 };
13240 self.select_next_state = Some(select_state);
13241 } else {
13242 self.select_next_state = None;
13243 }
13244 } else if let Some(selected_text) = selected_text {
13245 self.select_next_state = Some(SelectNextState {
13246 query: AhoCorasick::new(&[selected_text])?,
13247 wordwise: false,
13248 done: false,
13249 });
13250 self.select_next_match_internal(
13251 display_map,
13252 replace_newest,
13253 autoscroll,
13254 window,
13255 cx,
13256 )?;
13257 }
13258 }
13259 Ok(())
13260 }
13261
13262 pub fn select_all_matches(
13263 &mut self,
13264 _action: &SelectAllMatches,
13265 window: &mut Window,
13266 cx: &mut Context<Self>,
13267 ) -> Result<()> {
13268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13269
13270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13271
13272 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13273 let Some(select_next_state) = self.select_next_state.as_mut() else {
13274 return Ok(());
13275 };
13276 if select_next_state.done {
13277 return Ok(());
13278 }
13279
13280 let mut new_selections = Vec::new();
13281
13282 let reversed = self.selections.oldest::<usize>(cx).reversed;
13283 let buffer = &display_map.buffer_snapshot;
13284 let query_matches = select_next_state
13285 .query
13286 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13287
13288 for query_match in query_matches.into_iter() {
13289 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13290 let offset_range = if reversed {
13291 query_match.end()..query_match.start()
13292 } else {
13293 query_match.start()..query_match.end()
13294 };
13295 let display_range = offset_range.start.to_display_point(&display_map)
13296 ..offset_range.end.to_display_point(&display_map);
13297
13298 if !select_next_state.wordwise
13299 || (!movement::is_inside_word(&display_map, display_range.start)
13300 && !movement::is_inside_word(&display_map, display_range.end))
13301 {
13302 new_selections.push(offset_range.start..offset_range.end);
13303 }
13304 }
13305
13306 select_next_state.done = true;
13307 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13308 self.change_selections(None, window, cx, |selections| {
13309 selections.select_ranges(new_selections)
13310 });
13311
13312 Ok(())
13313 }
13314
13315 pub fn select_next(
13316 &mut self,
13317 action: &SelectNext,
13318 window: &mut Window,
13319 cx: &mut Context<Self>,
13320 ) -> Result<()> {
13321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13323 self.select_next_match_internal(
13324 &display_map,
13325 action.replace_newest,
13326 Some(Autoscroll::newest()),
13327 window,
13328 cx,
13329 )?;
13330 Ok(())
13331 }
13332
13333 pub fn select_previous(
13334 &mut self,
13335 action: &SelectPrevious,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) -> Result<()> {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13341 let buffer = &display_map.buffer_snapshot;
13342 let mut selections = self.selections.all::<usize>(cx);
13343 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13344 let query = &select_prev_state.query;
13345 if !select_prev_state.done {
13346 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13347 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13348 let mut next_selected_range = None;
13349 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13350 let bytes_before_last_selection =
13351 buffer.reversed_bytes_in_range(0..last_selection.start);
13352 let bytes_after_first_selection =
13353 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13354 let query_matches = query
13355 .stream_find_iter(bytes_before_last_selection)
13356 .map(|result| (last_selection.start, result))
13357 .chain(
13358 query
13359 .stream_find_iter(bytes_after_first_selection)
13360 .map(|result| (buffer.len(), result)),
13361 );
13362 for (end_offset, query_match) in query_matches {
13363 let query_match = query_match.unwrap(); // can only fail due to I/O
13364 let offset_range =
13365 end_offset - query_match.end()..end_offset - query_match.start();
13366 let display_range = offset_range.start.to_display_point(&display_map)
13367 ..offset_range.end.to_display_point(&display_map);
13368
13369 if !select_prev_state.wordwise
13370 || (!movement::is_inside_word(&display_map, display_range.start)
13371 && !movement::is_inside_word(&display_map, display_range.end))
13372 {
13373 next_selected_range = Some(offset_range);
13374 break;
13375 }
13376 }
13377
13378 if let Some(next_selected_range) = next_selected_range {
13379 self.select_match_ranges(
13380 next_selected_range,
13381 last_selection.reversed,
13382 action.replace_newest,
13383 Some(Autoscroll::newest()),
13384 window,
13385 cx,
13386 );
13387 } else {
13388 select_prev_state.done = true;
13389 }
13390 }
13391
13392 self.select_prev_state = Some(select_prev_state);
13393 } else {
13394 let mut only_carets = true;
13395 let mut same_text_selected = true;
13396 let mut selected_text = None;
13397
13398 let mut selections_iter = selections.iter().peekable();
13399 while let Some(selection) = selections_iter.next() {
13400 if selection.start != selection.end {
13401 only_carets = false;
13402 }
13403
13404 if same_text_selected {
13405 if selected_text.is_none() {
13406 selected_text =
13407 Some(buffer.text_for_range(selection.range()).collect::<String>());
13408 }
13409
13410 if let Some(next_selection) = selections_iter.peek() {
13411 if next_selection.range().len() == selection.range().len() {
13412 let next_selected_text = buffer
13413 .text_for_range(next_selection.range())
13414 .collect::<String>();
13415 if Some(next_selected_text) != selected_text {
13416 same_text_selected = false;
13417 selected_text = None;
13418 }
13419 } else {
13420 same_text_selected = false;
13421 selected_text = None;
13422 }
13423 }
13424 }
13425 }
13426
13427 if only_carets {
13428 for selection in &mut selections {
13429 let word_range = movement::surrounding_word(
13430 &display_map,
13431 selection.start.to_display_point(&display_map),
13432 );
13433 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13434 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13435 selection.goal = SelectionGoal::None;
13436 selection.reversed = false;
13437 self.select_match_ranges(
13438 selection.start..selection.end,
13439 selection.reversed,
13440 action.replace_newest,
13441 Some(Autoscroll::newest()),
13442 window,
13443 cx,
13444 );
13445 }
13446 if selections.len() == 1 {
13447 let selection = selections
13448 .last()
13449 .expect("ensured that there's only one selection");
13450 let query = buffer
13451 .text_for_range(selection.start..selection.end)
13452 .collect::<String>();
13453 let is_empty = query.is_empty();
13454 let select_state = SelectNextState {
13455 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13456 wordwise: true,
13457 done: is_empty,
13458 };
13459 self.select_prev_state = Some(select_state);
13460 } else {
13461 self.select_prev_state = None;
13462 }
13463 } else if let Some(selected_text) = selected_text {
13464 self.select_prev_state = Some(SelectNextState {
13465 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13466 wordwise: false,
13467 done: false,
13468 });
13469 self.select_previous(action, window, cx)?;
13470 }
13471 }
13472 Ok(())
13473 }
13474
13475 pub fn find_next_match(
13476 &mut self,
13477 _: &FindNextMatch,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) -> Result<()> {
13481 let selections = self.selections.disjoint_anchors();
13482 match selections.first() {
13483 Some(first) if selections.len() >= 2 => {
13484 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13485 s.select_ranges([first.range()]);
13486 });
13487 }
13488 _ => self.select_next(
13489 &SelectNext {
13490 replace_newest: true,
13491 },
13492 window,
13493 cx,
13494 )?,
13495 }
13496 Ok(())
13497 }
13498
13499 pub fn find_previous_match(
13500 &mut self,
13501 _: &FindPreviousMatch,
13502 window: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) -> Result<()> {
13505 let selections = self.selections.disjoint_anchors();
13506 match selections.last() {
13507 Some(last) if selections.len() >= 2 => {
13508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13509 s.select_ranges([last.range()]);
13510 });
13511 }
13512 _ => self.select_previous(
13513 &SelectPrevious {
13514 replace_newest: true,
13515 },
13516 window,
13517 cx,
13518 )?,
13519 }
13520 Ok(())
13521 }
13522
13523 pub fn toggle_comments(
13524 &mut self,
13525 action: &ToggleComments,
13526 window: &mut Window,
13527 cx: &mut Context<Self>,
13528 ) {
13529 if self.read_only(cx) {
13530 return;
13531 }
13532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13533 let text_layout_details = &self.text_layout_details(window);
13534 self.transact(window, cx, |this, window, cx| {
13535 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13536 let mut edits = Vec::new();
13537 let mut selection_edit_ranges = Vec::new();
13538 let mut last_toggled_row = None;
13539 let snapshot = this.buffer.read(cx).read(cx);
13540 let empty_str: Arc<str> = Arc::default();
13541 let mut suffixes_inserted = Vec::new();
13542 let ignore_indent = action.ignore_indent;
13543
13544 fn comment_prefix_range(
13545 snapshot: &MultiBufferSnapshot,
13546 row: MultiBufferRow,
13547 comment_prefix: &str,
13548 comment_prefix_whitespace: &str,
13549 ignore_indent: bool,
13550 ) -> Range<Point> {
13551 let indent_size = if ignore_indent {
13552 0
13553 } else {
13554 snapshot.indent_size_for_line(row).len
13555 };
13556
13557 let start = Point::new(row.0, indent_size);
13558
13559 let mut line_bytes = snapshot
13560 .bytes_in_range(start..snapshot.max_point())
13561 .flatten()
13562 .copied();
13563
13564 // If this line currently begins with the line comment prefix, then record
13565 // the range containing the prefix.
13566 if line_bytes
13567 .by_ref()
13568 .take(comment_prefix.len())
13569 .eq(comment_prefix.bytes())
13570 {
13571 // Include any whitespace that matches the comment prefix.
13572 let matching_whitespace_len = line_bytes
13573 .zip(comment_prefix_whitespace.bytes())
13574 .take_while(|(a, b)| a == b)
13575 .count() as u32;
13576 let end = Point::new(
13577 start.row,
13578 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13579 );
13580 start..end
13581 } else {
13582 start..start
13583 }
13584 }
13585
13586 fn comment_suffix_range(
13587 snapshot: &MultiBufferSnapshot,
13588 row: MultiBufferRow,
13589 comment_suffix: &str,
13590 comment_suffix_has_leading_space: bool,
13591 ) -> Range<Point> {
13592 let end = Point::new(row.0, snapshot.line_len(row));
13593 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13594
13595 let mut line_end_bytes = snapshot
13596 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13597 .flatten()
13598 .copied();
13599
13600 let leading_space_len = if suffix_start_column > 0
13601 && line_end_bytes.next() == Some(b' ')
13602 && comment_suffix_has_leading_space
13603 {
13604 1
13605 } else {
13606 0
13607 };
13608
13609 // If this line currently begins with the line comment prefix, then record
13610 // the range containing the prefix.
13611 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13612 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13613 start..end
13614 } else {
13615 end..end
13616 }
13617 }
13618
13619 // TODO: Handle selections that cross excerpts
13620 for selection in &mut selections {
13621 let start_column = snapshot
13622 .indent_size_for_line(MultiBufferRow(selection.start.row))
13623 .len;
13624 let language = if let Some(language) =
13625 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13626 {
13627 language
13628 } else {
13629 continue;
13630 };
13631
13632 selection_edit_ranges.clear();
13633
13634 // If multiple selections contain a given row, avoid processing that
13635 // row more than once.
13636 let mut start_row = MultiBufferRow(selection.start.row);
13637 if last_toggled_row == Some(start_row) {
13638 start_row = start_row.next_row();
13639 }
13640 let end_row =
13641 if selection.end.row > selection.start.row && selection.end.column == 0 {
13642 MultiBufferRow(selection.end.row - 1)
13643 } else {
13644 MultiBufferRow(selection.end.row)
13645 };
13646 last_toggled_row = Some(end_row);
13647
13648 if start_row > end_row {
13649 continue;
13650 }
13651
13652 // If the language has line comments, toggle those.
13653 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13654
13655 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13656 if ignore_indent {
13657 full_comment_prefixes = full_comment_prefixes
13658 .into_iter()
13659 .map(|s| Arc::from(s.trim_end()))
13660 .collect();
13661 }
13662
13663 if !full_comment_prefixes.is_empty() {
13664 let first_prefix = full_comment_prefixes
13665 .first()
13666 .expect("prefixes is non-empty");
13667 let prefix_trimmed_lengths = full_comment_prefixes
13668 .iter()
13669 .map(|p| p.trim_end_matches(' ').len())
13670 .collect::<SmallVec<[usize; 4]>>();
13671
13672 let mut all_selection_lines_are_comments = true;
13673
13674 for row in start_row.0..=end_row.0 {
13675 let row = MultiBufferRow(row);
13676 if start_row < end_row && snapshot.is_line_blank(row) {
13677 continue;
13678 }
13679
13680 let prefix_range = full_comment_prefixes
13681 .iter()
13682 .zip(prefix_trimmed_lengths.iter().copied())
13683 .map(|(prefix, trimmed_prefix_len)| {
13684 comment_prefix_range(
13685 snapshot.deref(),
13686 row,
13687 &prefix[..trimmed_prefix_len],
13688 &prefix[trimmed_prefix_len..],
13689 ignore_indent,
13690 )
13691 })
13692 .max_by_key(|range| range.end.column - range.start.column)
13693 .expect("prefixes is non-empty");
13694
13695 if prefix_range.is_empty() {
13696 all_selection_lines_are_comments = false;
13697 }
13698
13699 selection_edit_ranges.push(prefix_range);
13700 }
13701
13702 if all_selection_lines_are_comments {
13703 edits.extend(
13704 selection_edit_ranges
13705 .iter()
13706 .cloned()
13707 .map(|range| (range, empty_str.clone())),
13708 );
13709 } else {
13710 let min_column = selection_edit_ranges
13711 .iter()
13712 .map(|range| range.start.column)
13713 .min()
13714 .unwrap_or(0);
13715 edits.extend(selection_edit_ranges.iter().map(|range| {
13716 let position = Point::new(range.start.row, min_column);
13717 (position..position, first_prefix.clone())
13718 }));
13719 }
13720 } else if let Some((full_comment_prefix, comment_suffix)) =
13721 language.block_comment_delimiters()
13722 {
13723 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13724 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13725 let prefix_range = comment_prefix_range(
13726 snapshot.deref(),
13727 start_row,
13728 comment_prefix,
13729 comment_prefix_whitespace,
13730 ignore_indent,
13731 );
13732 let suffix_range = comment_suffix_range(
13733 snapshot.deref(),
13734 end_row,
13735 comment_suffix.trim_start_matches(' '),
13736 comment_suffix.starts_with(' '),
13737 );
13738
13739 if prefix_range.is_empty() || suffix_range.is_empty() {
13740 edits.push((
13741 prefix_range.start..prefix_range.start,
13742 full_comment_prefix.clone(),
13743 ));
13744 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13745 suffixes_inserted.push((end_row, comment_suffix.len()));
13746 } else {
13747 edits.push((prefix_range, empty_str.clone()));
13748 edits.push((suffix_range, empty_str.clone()));
13749 }
13750 } else {
13751 continue;
13752 }
13753 }
13754
13755 drop(snapshot);
13756 this.buffer.update(cx, |buffer, cx| {
13757 buffer.edit(edits, None, cx);
13758 });
13759
13760 // Adjust selections so that they end before any comment suffixes that
13761 // were inserted.
13762 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13763 let mut selections = this.selections.all::<Point>(cx);
13764 let snapshot = this.buffer.read(cx).read(cx);
13765 for selection in &mut selections {
13766 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13767 match row.cmp(&MultiBufferRow(selection.end.row)) {
13768 Ordering::Less => {
13769 suffixes_inserted.next();
13770 continue;
13771 }
13772 Ordering::Greater => break,
13773 Ordering::Equal => {
13774 if selection.end.column == snapshot.line_len(row) {
13775 if selection.is_empty() {
13776 selection.start.column -= suffix_len as u32;
13777 }
13778 selection.end.column -= suffix_len as u32;
13779 }
13780 break;
13781 }
13782 }
13783 }
13784 }
13785
13786 drop(snapshot);
13787 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13788 s.select(selections)
13789 });
13790
13791 let selections = this.selections.all::<Point>(cx);
13792 let selections_on_single_row = selections.windows(2).all(|selections| {
13793 selections[0].start.row == selections[1].start.row
13794 && selections[0].end.row == selections[1].end.row
13795 && selections[0].start.row == selections[0].end.row
13796 });
13797 let selections_selecting = selections
13798 .iter()
13799 .any(|selection| selection.start != selection.end);
13800 let advance_downwards = action.advance_downwards
13801 && selections_on_single_row
13802 && !selections_selecting
13803 && !matches!(this.mode, EditorMode::SingleLine { .. });
13804
13805 if advance_downwards {
13806 let snapshot = this.buffer.read(cx).snapshot(cx);
13807
13808 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13809 s.move_cursors_with(|display_snapshot, display_point, _| {
13810 let mut point = display_point.to_point(display_snapshot);
13811 point.row += 1;
13812 point = snapshot.clip_point(point, Bias::Left);
13813 let display_point = point.to_display_point(display_snapshot);
13814 let goal = SelectionGoal::HorizontalPosition(
13815 display_snapshot
13816 .x_for_display_point(display_point, text_layout_details)
13817 .into(),
13818 );
13819 (display_point, goal)
13820 })
13821 });
13822 }
13823 });
13824 }
13825
13826 pub fn select_enclosing_symbol(
13827 &mut self,
13828 _: &SelectEnclosingSymbol,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13833
13834 let buffer = self.buffer.read(cx).snapshot(cx);
13835 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13836
13837 fn update_selection(
13838 selection: &Selection<usize>,
13839 buffer_snap: &MultiBufferSnapshot,
13840 ) -> Option<Selection<usize>> {
13841 let cursor = selection.head();
13842 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13843 for symbol in symbols.iter().rev() {
13844 let start = symbol.range.start.to_offset(buffer_snap);
13845 let end = symbol.range.end.to_offset(buffer_snap);
13846 let new_range = start..end;
13847 if start < selection.start || end > selection.end {
13848 return Some(Selection {
13849 id: selection.id,
13850 start: new_range.start,
13851 end: new_range.end,
13852 goal: SelectionGoal::None,
13853 reversed: selection.reversed,
13854 });
13855 }
13856 }
13857 None
13858 }
13859
13860 let mut selected_larger_symbol = false;
13861 let new_selections = old_selections
13862 .iter()
13863 .map(|selection| match update_selection(selection, &buffer) {
13864 Some(new_selection) => {
13865 if new_selection.range() != selection.range() {
13866 selected_larger_symbol = true;
13867 }
13868 new_selection
13869 }
13870 None => selection.clone(),
13871 })
13872 .collect::<Vec<_>>();
13873
13874 if selected_larger_symbol {
13875 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13876 s.select(new_selections);
13877 });
13878 }
13879 }
13880
13881 pub fn select_larger_syntax_node(
13882 &mut self,
13883 _: &SelectLargerSyntaxNode,
13884 window: &mut Window,
13885 cx: &mut Context<Self>,
13886 ) {
13887 let Some(visible_row_count) = self.visible_row_count() else {
13888 return;
13889 };
13890 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13891 if old_selections.is_empty() {
13892 return;
13893 }
13894
13895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13896
13897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13898 let buffer = self.buffer.read(cx).snapshot(cx);
13899
13900 let mut selected_larger_node = false;
13901 let mut new_selections = old_selections
13902 .iter()
13903 .map(|selection| {
13904 let old_range = selection.start..selection.end;
13905
13906 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13907 // manually select word at selection
13908 if ["string_content", "inline"].contains(&node.kind()) {
13909 let word_range = {
13910 let display_point = buffer
13911 .offset_to_point(old_range.start)
13912 .to_display_point(&display_map);
13913 let Range { start, end } =
13914 movement::surrounding_word(&display_map, display_point);
13915 start.to_point(&display_map).to_offset(&buffer)
13916 ..end.to_point(&display_map).to_offset(&buffer)
13917 };
13918 // ignore if word is already selected
13919 if !word_range.is_empty() && old_range != word_range {
13920 let last_word_range = {
13921 let display_point = buffer
13922 .offset_to_point(old_range.end)
13923 .to_display_point(&display_map);
13924 let Range { start, end } =
13925 movement::surrounding_word(&display_map, display_point);
13926 start.to_point(&display_map).to_offset(&buffer)
13927 ..end.to_point(&display_map).to_offset(&buffer)
13928 };
13929 // only select word if start and end point belongs to same word
13930 if word_range == last_word_range {
13931 selected_larger_node = true;
13932 return Selection {
13933 id: selection.id,
13934 start: word_range.start,
13935 end: word_range.end,
13936 goal: SelectionGoal::None,
13937 reversed: selection.reversed,
13938 };
13939 }
13940 }
13941 }
13942 }
13943
13944 let mut new_range = old_range.clone();
13945 while let Some((_node, containing_range)) =
13946 buffer.syntax_ancestor(new_range.clone())
13947 {
13948 new_range = match containing_range {
13949 MultiOrSingleBufferOffsetRange::Single(_) => break,
13950 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13951 };
13952 if !display_map.intersects_fold(new_range.start)
13953 && !display_map.intersects_fold(new_range.end)
13954 {
13955 break;
13956 }
13957 }
13958
13959 selected_larger_node |= new_range != old_range;
13960 Selection {
13961 id: selection.id,
13962 start: new_range.start,
13963 end: new_range.end,
13964 goal: SelectionGoal::None,
13965 reversed: selection.reversed,
13966 }
13967 })
13968 .collect::<Vec<_>>();
13969
13970 if !selected_larger_node {
13971 return; // don't put this call in the history
13972 }
13973
13974 // scroll based on transformation done to the last selection created by the user
13975 let (last_old, last_new) = old_selections
13976 .last()
13977 .zip(new_selections.last().cloned())
13978 .expect("old_selections isn't empty");
13979
13980 // revert selection
13981 let is_selection_reversed = {
13982 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13983 new_selections.last_mut().expect("checked above").reversed =
13984 should_newest_selection_be_reversed;
13985 should_newest_selection_be_reversed
13986 };
13987
13988 if selected_larger_node {
13989 self.select_syntax_node_history.disable_clearing = true;
13990 self.change_selections(None, window, cx, |s| {
13991 s.select(new_selections.clone());
13992 });
13993 self.select_syntax_node_history.disable_clearing = false;
13994 }
13995
13996 let start_row = last_new.start.to_display_point(&display_map).row().0;
13997 let end_row = last_new.end.to_display_point(&display_map).row().0;
13998 let selection_height = end_row - start_row + 1;
13999 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14000
14001 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14002 let scroll_behavior = if fits_on_the_screen {
14003 self.request_autoscroll(Autoscroll::fit(), cx);
14004 SelectSyntaxNodeScrollBehavior::FitSelection
14005 } else if is_selection_reversed {
14006 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14007 SelectSyntaxNodeScrollBehavior::CursorTop
14008 } else {
14009 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14010 SelectSyntaxNodeScrollBehavior::CursorBottom
14011 };
14012
14013 self.select_syntax_node_history.push((
14014 old_selections,
14015 scroll_behavior,
14016 is_selection_reversed,
14017 ));
14018 }
14019
14020 pub fn select_smaller_syntax_node(
14021 &mut self,
14022 _: &SelectSmallerSyntaxNode,
14023 window: &mut Window,
14024 cx: &mut Context<Self>,
14025 ) {
14026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14027
14028 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14029 self.select_syntax_node_history.pop()
14030 {
14031 if let Some(selection) = selections.last_mut() {
14032 selection.reversed = is_selection_reversed;
14033 }
14034
14035 self.select_syntax_node_history.disable_clearing = true;
14036 self.change_selections(None, window, cx, |s| {
14037 s.select(selections.to_vec());
14038 });
14039 self.select_syntax_node_history.disable_clearing = false;
14040
14041 match scroll_behavior {
14042 SelectSyntaxNodeScrollBehavior::CursorTop => {
14043 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14044 }
14045 SelectSyntaxNodeScrollBehavior::FitSelection => {
14046 self.request_autoscroll(Autoscroll::fit(), cx);
14047 }
14048 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14049 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14050 }
14051 }
14052 }
14053 }
14054
14055 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14056 if !EditorSettings::get_global(cx).gutter.runnables {
14057 self.clear_tasks();
14058 return Task::ready(());
14059 }
14060 let project = self.project.as_ref().map(Entity::downgrade);
14061 let task_sources = self.lsp_task_sources(cx);
14062 let multi_buffer = self.buffer.downgrade();
14063 cx.spawn_in(window, async move |editor, cx| {
14064 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14065 let Some(project) = project.and_then(|p| p.upgrade()) else {
14066 return;
14067 };
14068 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14069 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14070 }) else {
14071 return;
14072 };
14073
14074 let hide_runnables = project
14075 .update(cx, |project, cx| {
14076 // Do not display any test indicators in non-dev server remote projects.
14077 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14078 })
14079 .unwrap_or(true);
14080 if hide_runnables {
14081 return;
14082 }
14083 let new_rows =
14084 cx.background_spawn({
14085 let snapshot = display_snapshot.clone();
14086 async move {
14087 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14088 }
14089 })
14090 .await;
14091 let Ok(lsp_tasks) =
14092 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14093 else {
14094 return;
14095 };
14096 let lsp_tasks = lsp_tasks.await;
14097
14098 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14099 lsp_tasks
14100 .into_iter()
14101 .flat_map(|(kind, tasks)| {
14102 tasks.into_iter().filter_map(move |(location, task)| {
14103 Some((kind.clone(), location?, task))
14104 })
14105 })
14106 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14107 let buffer = location.target.buffer;
14108 let buffer_snapshot = buffer.read(cx).snapshot();
14109 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14110 |(excerpt_id, snapshot, _)| {
14111 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14112 display_snapshot
14113 .buffer_snapshot
14114 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14115 } else {
14116 None
14117 }
14118 },
14119 );
14120 if let Some(offset) = offset {
14121 let task_buffer_range =
14122 location.target.range.to_point(&buffer_snapshot);
14123 let context_buffer_range =
14124 task_buffer_range.to_offset(&buffer_snapshot);
14125 let context_range = BufferOffset(context_buffer_range.start)
14126 ..BufferOffset(context_buffer_range.end);
14127
14128 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14129 .or_insert_with(|| RunnableTasks {
14130 templates: Vec::new(),
14131 offset,
14132 column: task_buffer_range.start.column,
14133 extra_variables: HashMap::default(),
14134 context_range,
14135 })
14136 .templates
14137 .push((kind, task.original_task().clone()));
14138 }
14139
14140 acc
14141 })
14142 }) else {
14143 return;
14144 };
14145
14146 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14147 buffer.language_settings(cx).tasks.prefer_lsp
14148 }) else {
14149 return;
14150 };
14151
14152 let rows = Self::runnable_rows(
14153 project,
14154 display_snapshot,
14155 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14156 new_rows,
14157 cx.clone(),
14158 )
14159 .await;
14160 editor
14161 .update(cx, |editor, _| {
14162 editor.clear_tasks();
14163 for (key, mut value) in rows {
14164 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14165 value.templates.extend(lsp_tasks.templates);
14166 }
14167
14168 editor.insert_tasks(key, value);
14169 }
14170 for (key, value) in lsp_tasks_by_rows {
14171 editor.insert_tasks(key, value);
14172 }
14173 })
14174 .ok();
14175 })
14176 }
14177 fn fetch_runnable_ranges(
14178 snapshot: &DisplaySnapshot,
14179 range: Range<Anchor>,
14180 ) -> Vec<language::RunnableRange> {
14181 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14182 }
14183
14184 fn runnable_rows(
14185 project: Entity<Project>,
14186 snapshot: DisplaySnapshot,
14187 prefer_lsp: bool,
14188 runnable_ranges: Vec<RunnableRange>,
14189 cx: AsyncWindowContext,
14190 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14191 cx.spawn(async move |cx| {
14192 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14193 for mut runnable in runnable_ranges {
14194 let Some(tasks) = cx
14195 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14196 .ok()
14197 else {
14198 continue;
14199 };
14200 let mut tasks = tasks.await;
14201
14202 if prefer_lsp {
14203 tasks.retain(|(task_kind, _)| {
14204 !matches!(task_kind, TaskSourceKind::Language { .. })
14205 });
14206 }
14207 if tasks.is_empty() {
14208 continue;
14209 }
14210
14211 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14212 let Some(row) = snapshot
14213 .buffer_snapshot
14214 .buffer_line_for_row(MultiBufferRow(point.row))
14215 .map(|(_, range)| range.start.row)
14216 else {
14217 continue;
14218 };
14219
14220 let context_range =
14221 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14222 runnable_rows.push((
14223 (runnable.buffer_id, row),
14224 RunnableTasks {
14225 templates: tasks,
14226 offset: snapshot
14227 .buffer_snapshot
14228 .anchor_before(runnable.run_range.start),
14229 context_range,
14230 column: point.column,
14231 extra_variables: runnable.extra_captures,
14232 },
14233 ));
14234 }
14235 runnable_rows
14236 })
14237 }
14238
14239 fn templates_with_tags(
14240 project: &Entity<Project>,
14241 runnable: &mut Runnable,
14242 cx: &mut App,
14243 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14244 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14245 let (worktree_id, file) = project
14246 .buffer_for_id(runnable.buffer, cx)
14247 .and_then(|buffer| buffer.read(cx).file())
14248 .map(|file| (file.worktree_id(cx), file.clone()))
14249 .unzip();
14250
14251 (
14252 project.task_store().read(cx).task_inventory().cloned(),
14253 worktree_id,
14254 file,
14255 )
14256 });
14257
14258 let tags = mem::take(&mut runnable.tags);
14259 let language = runnable.language.clone();
14260 cx.spawn(async move |cx| {
14261 let mut templates_with_tags = Vec::new();
14262 if let Some(inventory) = inventory {
14263 for RunnableTag(tag) in tags {
14264 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14265 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14266 }) else {
14267 return templates_with_tags;
14268 };
14269 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14270 move |(_, template)| {
14271 template.tags.iter().any(|source_tag| source_tag == &tag)
14272 },
14273 ));
14274 }
14275 }
14276 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14277
14278 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14279 // Strongest source wins; if we have worktree tag binding, prefer that to
14280 // global and language bindings;
14281 // if we have a global binding, prefer that to language binding.
14282 let first_mismatch = templates_with_tags
14283 .iter()
14284 .position(|(tag_source, _)| tag_source != leading_tag_source);
14285 if let Some(index) = first_mismatch {
14286 templates_with_tags.truncate(index);
14287 }
14288 }
14289
14290 templates_with_tags
14291 })
14292 }
14293
14294 pub fn move_to_enclosing_bracket(
14295 &mut self,
14296 _: &MoveToEnclosingBracket,
14297 window: &mut Window,
14298 cx: &mut Context<Self>,
14299 ) {
14300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14301 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14302 s.move_offsets_with(|snapshot, selection| {
14303 let Some(enclosing_bracket_ranges) =
14304 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14305 else {
14306 return;
14307 };
14308
14309 let mut best_length = usize::MAX;
14310 let mut best_inside = false;
14311 let mut best_in_bracket_range = false;
14312 let mut best_destination = None;
14313 for (open, close) in enclosing_bracket_ranges {
14314 let close = close.to_inclusive();
14315 let length = close.end() - open.start;
14316 let inside = selection.start >= open.end && selection.end <= *close.start();
14317 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14318 || close.contains(&selection.head());
14319
14320 // If best is next to a bracket and current isn't, skip
14321 if !in_bracket_range && best_in_bracket_range {
14322 continue;
14323 }
14324
14325 // Prefer smaller lengths unless best is inside and current isn't
14326 if length > best_length && (best_inside || !inside) {
14327 continue;
14328 }
14329
14330 best_length = length;
14331 best_inside = inside;
14332 best_in_bracket_range = in_bracket_range;
14333 best_destination = Some(
14334 if close.contains(&selection.start) && close.contains(&selection.end) {
14335 if inside { open.end } else { open.start }
14336 } else if inside {
14337 *close.start()
14338 } else {
14339 *close.end()
14340 },
14341 );
14342 }
14343
14344 if let Some(destination) = best_destination {
14345 selection.collapse_to(destination, SelectionGoal::None);
14346 }
14347 })
14348 });
14349 }
14350
14351 pub fn undo_selection(
14352 &mut self,
14353 _: &UndoSelection,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14359 self.selection_history.mode = SelectionHistoryMode::Undoing;
14360 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14361 this.end_selection(window, cx);
14362 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14363 s.select_anchors(entry.selections.to_vec())
14364 });
14365 });
14366 self.selection_history.mode = SelectionHistoryMode::Normal;
14367
14368 self.select_next_state = entry.select_next_state;
14369 self.select_prev_state = entry.select_prev_state;
14370 self.add_selections_state = entry.add_selections_state;
14371 }
14372 }
14373
14374 pub fn redo_selection(
14375 &mut self,
14376 _: &RedoSelection,
14377 window: &mut Window,
14378 cx: &mut Context<Self>,
14379 ) {
14380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14381 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14382 self.selection_history.mode = SelectionHistoryMode::Redoing;
14383 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14384 this.end_selection(window, cx);
14385 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14386 s.select_anchors(entry.selections.to_vec())
14387 });
14388 });
14389 self.selection_history.mode = SelectionHistoryMode::Normal;
14390
14391 self.select_next_state = entry.select_next_state;
14392 self.select_prev_state = entry.select_prev_state;
14393 self.add_selections_state = entry.add_selections_state;
14394 }
14395 }
14396
14397 pub fn expand_excerpts(
14398 &mut self,
14399 action: &ExpandExcerpts,
14400 _: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) {
14403 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14404 }
14405
14406 pub fn expand_excerpts_down(
14407 &mut self,
14408 action: &ExpandExcerptsDown,
14409 _: &mut Window,
14410 cx: &mut Context<Self>,
14411 ) {
14412 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14413 }
14414
14415 pub fn expand_excerpts_up(
14416 &mut self,
14417 action: &ExpandExcerptsUp,
14418 _: &mut Window,
14419 cx: &mut Context<Self>,
14420 ) {
14421 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14422 }
14423
14424 pub fn expand_excerpts_for_direction(
14425 &mut self,
14426 lines: u32,
14427 direction: ExpandExcerptDirection,
14428
14429 cx: &mut Context<Self>,
14430 ) {
14431 let selections = self.selections.disjoint_anchors();
14432
14433 let lines = if lines == 0 {
14434 EditorSettings::get_global(cx).expand_excerpt_lines
14435 } else {
14436 lines
14437 };
14438
14439 self.buffer.update(cx, |buffer, cx| {
14440 let snapshot = buffer.snapshot(cx);
14441 let mut excerpt_ids = selections
14442 .iter()
14443 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14444 .collect::<Vec<_>>();
14445 excerpt_ids.sort();
14446 excerpt_ids.dedup();
14447 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14448 })
14449 }
14450
14451 pub fn expand_excerpt(
14452 &mut self,
14453 excerpt: ExcerptId,
14454 direction: ExpandExcerptDirection,
14455 window: &mut Window,
14456 cx: &mut Context<Self>,
14457 ) {
14458 let current_scroll_position = self.scroll_position(cx);
14459 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14460 let mut should_scroll_up = false;
14461
14462 if direction == ExpandExcerptDirection::Down {
14463 let multi_buffer = self.buffer.read(cx);
14464 let snapshot = multi_buffer.snapshot(cx);
14465 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14466 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14467 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14468 let buffer_snapshot = buffer.read(cx).snapshot();
14469 let excerpt_end_row =
14470 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14471 let last_row = buffer_snapshot.max_point().row;
14472 let lines_below = last_row.saturating_sub(excerpt_end_row);
14473 should_scroll_up = lines_below >= lines_to_expand;
14474 }
14475 }
14476 }
14477 }
14478
14479 self.buffer.update(cx, |buffer, cx| {
14480 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14481 });
14482
14483 if should_scroll_up {
14484 let new_scroll_position =
14485 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14486 self.set_scroll_position(new_scroll_position, window, cx);
14487 }
14488 }
14489
14490 pub fn go_to_singleton_buffer_point(
14491 &mut self,
14492 point: Point,
14493 window: &mut Window,
14494 cx: &mut Context<Self>,
14495 ) {
14496 self.go_to_singleton_buffer_range(point..point, window, cx);
14497 }
14498
14499 pub fn go_to_singleton_buffer_range(
14500 &mut self,
14501 range: Range<Point>,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 let multibuffer = self.buffer().read(cx);
14506 let Some(buffer) = multibuffer.as_singleton() else {
14507 return;
14508 };
14509 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14510 return;
14511 };
14512 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14513 return;
14514 };
14515 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14516 s.select_anchor_ranges([start..end])
14517 });
14518 }
14519
14520 pub fn go_to_diagnostic(
14521 &mut self,
14522 _: &GoToDiagnostic,
14523 window: &mut Window,
14524 cx: &mut Context<Self>,
14525 ) {
14526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14527 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14528 }
14529
14530 pub fn go_to_prev_diagnostic(
14531 &mut self,
14532 _: &GoToPreviousDiagnostic,
14533 window: &mut Window,
14534 cx: &mut Context<Self>,
14535 ) {
14536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14537 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14538 }
14539
14540 pub fn go_to_diagnostic_impl(
14541 &mut self,
14542 direction: Direction,
14543 window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) {
14546 let buffer = self.buffer.read(cx).snapshot(cx);
14547 let selection = self.selections.newest::<usize>(cx);
14548
14549 let mut active_group_id = None;
14550 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14551 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14552 active_group_id = Some(active_group.group_id);
14553 }
14554 }
14555
14556 fn filtered(
14557 snapshot: EditorSnapshot,
14558 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14559 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14560 diagnostics
14561 .filter(|entry| entry.range.start != entry.range.end)
14562 .filter(|entry| !entry.diagnostic.is_unnecessary)
14563 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14564 }
14565
14566 let snapshot = self.snapshot(window, cx);
14567 let before = filtered(
14568 snapshot.clone(),
14569 buffer
14570 .diagnostics_in_range(0..selection.start)
14571 .filter(|entry| entry.range.start <= selection.start),
14572 );
14573 let after = filtered(
14574 snapshot,
14575 buffer
14576 .diagnostics_in_range(selection.start..buffer.len())
14577 .filter(|entry| entry.range.start >= selection.start),
14578 );
14579
14580 let mut found: Option<DiagnosticEntry<usize>> = None;
14581 if direction == Direction::Prev {
14582 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14583 {
14584 for diagnostic in prev_diagnostics.into_iter().rev() {
14585 if diagnostic.range.start != selection.start
14586 || active_group_id
14587 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14588 {
14589 found = Some(diagnostic);
14590 break 'outer;
14591 }
14592 }
14593 }
14594 } else {
14595 for diagnostic in after.chain(before) {
14596 if diagnostic.range.start != selection.start
14597 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14598 {
14599 found = Some(diagnostic);
14600 break;
14601 }
14602 }
14603 }
14604 let Some(next_diagnostic) = found else {
14605 return;
14606 };
14607
14608 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14609 return;
14610 };
14611 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14612 s.select_ranges(vec![
14613 next_diagnostic.range.start..next_diagnostic.range.start,
14614 ])
14615 });
14616 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14617 self.refresh_inline_completion(false, true, window, cx);
14618 }
14619
14620 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622 let snapshot = self.snapshot(window, cx);
14623 let selection = self.selections.newest::<Point>(cx);
14624 self.go_to_hunk_before_or_after_position(
14625 &snapshot,
14626 selection.head(),
14627 Direction::Next,
14628 window,
14629 cx,
14630 );
14631 }
14632
14633 pub fn go_to_hunk_before_or_after_position(
14634 &mut self,
14635 snapshot: &EditorSnapshot,
14636 position: Point,
14637 direction: Direction,
14638 window: &mut Window,
14639 cx: &mut Context<Editor>,
14640 ) {
14641 let row = if direction == Direction::Next {
14642 self.hunk_after_position(snapshot, position)
14643 .map(|hunk| hunk.row_range.start)
14644 } else {
14645 self.hunk_before_position(snapshot, position)
14646 };
14647
14648 if let Some(row) = row {
14649 let destination = Point::new(row.0, 0);
14650 let autoscroll = Autoscroll::center();
14651
14652 self.unfold_ranges(&[destination..destination], false, false, cx);
14653 self.change_selections(Some(autoscroll), window, cx, |s| {
14654 s.select_ranges([destination..destination]);
14655 });
14656 }
14657 }
14658
14659 fn hunk_after_position(
14660 &mut self,
14661 snapshot: &EditorSnapshot,
14662 position: Point,
14663 ) -> Option<MultiBufferDiffHunk> {
14664 snapshot
14665 .buffer_snapshot
14666 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14667 .find(|hunk| hunk.row_range.start.0 > position.row)
14668 .or_else(|| {
14669 snapshot
14670 .buffer_snapshot
14671 .diff_hunks_in_range(Point::zero()..position)
14672 .find(|hunk| hunk.row_range.end.0 < position.row)
14673 })
14674 }
14675
14676 fn go_to_prev_hunk(
14677 &mut self,
14678 _: &GoToPreviousHunk,
14679 window: &mut Window,
14680 cx: &mut Context<Self>,
14681 ) {
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 let snapshot = self.snapshot(window, cx);
14684 let selection = self.selections.newest::<Point>(cx);
14685 self.go_to_hunk_before_or_after_position(
14686 &snapshot,
14687 selection.head(),
14688 Direction::Prev,
14689 window,
14690 cx,
14691 );
14692 }
14693
14694 fn hunk_before_position(
14695 &mut self,
14696 snapshot: &EditorSnapshot,
14697 position: Point,
14698 ) -> Option<MultiBufferRow> {
14699 snapshot
14700 .buffer_snapshot
14701 .diff_hunk_before(position)
14702 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14703 }
14704
14705 fn go_to_next_change(
14706 &mut self,
14707 _: &GoToNextChange,
14708 window: &mut Window,
14709 cx: &mut Context<Self>,
14710 ) {
14711 if let Some(selections) = self
14712 .change_list
14713 .next_change(1, Direction::Next)
14714 .map(|s| s.to_vec())
14715 {
14716 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14717 let map = s.display_map();
14718 s.select_display_ranges(selections.iter().map(|a| {
14719 let point = a.to_display_point(&map);
14720 point..point
14721 }))
14722 })
14723 }
14724 }
14725
14726 fn go_to_previous_change(
14727 &mut self,
14728 _: &GoToPreviousChange,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) {
14732 if let Some(selections) = self
14733 .change_list
14734 .next_change(1, Direction::Prev)
14735 .map(|s| s.to_vec())
14736 {
14737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14738 let map = s.display_map();
14739 s.select_display_ranges(selections.iter().map(|a| {
14740 let point = a.to_display_point(&map);
14741 point..point
14742 }))
14743 })
14744 }
14745 }
14746
14747 fn go_to_line<T: 'static>(
14748 &mut self,
14749 position: Anchor,
14750 highlight_color: Option<Hsla>,
14751 window: &mut Window,
14752 cx: &mut Context<Self>,
14753 ) {
14754 let snapshot = self.snapshot(window, cx).display_snapshot;
14755 let position = position.to_point(&snapshot.buffer_snapshot);
14756 let start = snapshot
14757 .buffer_snapshot
14758 .clip_point(Point::new(position.row, 0), Bias::Left);
14759 let end = start + Point::new(1, 0);
14760 let start = snapshot.buffer_snapshot.anchor_before(start);
14761 let end = snapshot.buffer_snapshot.anchor_before(end);
14762
14763 self.highlight_rows::<T>(
14764 start..end,
14765 highlight_color
14766 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14767 Default::default(),
14768 cx,
14769 );
14770
14771 if self.buffer.read(cx).is_singleton() {
14772 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14773 }
14774 }
14775
14776 pub fn go_to_definition(
14777 &mut self,
14778 _: &GoToDefinition,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) -> Task<Result<Navigated>> {
14782 let definition =
14783 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14784 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14785 cx.spawn_in(window, async move |editor, cx| {
14786 if definition.await? == Navigated::Yes {
14787 return Ok(Navigated::Yes);
14788 }
14789 match fallback_strategy {
14790 GoToDefinitionFallback::None => Ok(Navigated::No),
14791 GoToDefinitionFallback::FindAllReferences => {
14792 match editor.update_in(cx, |editor, window, cx| {
14793 editor.find_all_references(&FindAllReferences, window, cx)
14794 })? {
14795 Some(references) => references.await,
14796 None => Ok(Navigated::No),
14797 }
14798 }
14799 }
14800 })
14801 }
14802
14803 pub fn go_to_declaration(
14804 &mut self,
14805 _: &GoToDeclaration,
14806 window: &mut Window,
14807 cx: &mut Context<Self>,
14808 ) -> Task<Result<Navigated>> {
14809 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14810 }
14811
14812 pub fn go_to_declaration_split(
14813 &mut self,
14814 _: &GoToDeclaration,
14815 window: &mut Window,
14816 cx: &mut Context<Self>,
14817 ) -> Task<Result<Navigated>> {
14818 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14819 }
14820
14821 pub fn go_to_implementation(
14822 &mut self,
14823 _: &GoToImplementation,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) -> Task<Result<Navigated>> {
14827 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14828 }
14829
14830 pub fn go_to_implementation_split(
14831 &mut self,
14832 _: &GoToImplementationSplit,
14833 window: &mut Window,
14834 cx: &mut Context<Self>,
14835 ) -> Task<Result<Navigated>> {
14836 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14837 }
14838
14839 pub fn go_to_type_definition(
14840 &mut self,
14841 _: &GoToTypeDefinition,
14842 window: &mut Window,
14843 cx: &mut Context<Self>,
14844 ) -> Task<Result<Navigated>> {
14845 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14846 }
14847
14848 pub fn go_to_definition_split(
14849 &mut self,
14850 _: &GoToDefinitionSplit,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) -> Task<Result<Navigated>> {
14854 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14855 }
14856
14857 pub fn go_to_type_definition_split(
14858 &mut self,
14859 _: &GoToTypeDefinitionSplit,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) -> Task<Result<Navigated>> {
14863 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14864 }
14865
14866 fn go_to_definition_of_kind(
14867 &mut self,
14868 kind: GotoDefinitionKind,
14869 split: bool,
14870 window: &mut Window,
14871 cx: &mut Context<Self>,
14872 ) -> Task<Result<Navigated>> {
14873 let Some(provider) = self.semantics_provider.clone() else {
14874 return Task::ready(Ok(Navigated::No));
14875 };
14876 let head = self.selections.newest::<usize>(cx).head();
14877 let buffer = self.buffer.read(cx);
14878 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14879 text_anchor
14880 } else {
14881 return Task::ready(Ok(Navigated::No));
14882 };
14883
14884 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14885 return Task::ready(Ok(Navigated::No));
14886 };
14887
14888 cx.spawn_in(window, async move |editor, cx| {
14889 let definitions = definitions.await?;
14890 let navigated = editor
14891 .update_in(cx, |editor, window, cx| {
14892 editor.navigate_to_hover_links(
14893 Some(kind),
14894 definitions
14895 .into_iter()
14896 .filter(|location| {
14897 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14898 })
14899 .map(HoverLink::Text)
14900 .collect::<Vec<_>>(),
14901 split,
14902 window,
14903 cx,
14904 )
14905 })?
14906 .await?;
14907 anyhow::Ok(navigated)
14908 })
14909 }
14910
14911 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14912 let selection = self.selections.newest_anchor();
14913 let head = selection.head();
14914 let tail = selection.tail();
14915
14916 let Some((buffer, start_position)) =
14917 self.buffer.read(cx).text_anchor_for_position(head, cx)
14918 else {
14919 return;
14920 };
14921
14922 let end_position = if head != tail {
14923 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14924 return;
14925 };
14926 Some(pos)
14927 } else {
14928 None
14929 };
14930
14931 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14932 let url = if let Some(end_pos) = end_position {
14933 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14934 } else {
14935 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14936 };
14937
14938 if let Some(url) = url {
14939 editor.update(cx, |_, cx| {
14940 cx.open_url(&url);
14941 })
14942 } else {
14943 Ok(())
14944 }
14945 });
14946
14947 url_finder.detach();
14948 }
14949
14950 pub fn open_selected_filename(
14951 &mut self,
14952 _: &OpenSelectedFilename,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 let Some(workspace) = self.workspace() else {
14957 return;
14958 };
14959
14960 let position = self.selections.newest_anchor().head();
14961
14962 let Some((buffer, buffer_position)) =
14963 self.buffer.read(cx).text_anchor_for_position(position, cx)
14964 else {
14965 return;
14966 };
14967
14968 let project = self.project.clone();
14969
14970 cx.spawn_in(window, async move |_, cx| {
14971 let result = find_file(&buffer, project, buffer_position, cx).await;
14972
14973 if let Some((_, path)) = result {
14974 workspace
14975 .update_in(cx, |workspace, window, cx| {
14976 workspace.open_resolved_path(path, window, cx)
14977 })?
14978 .await?;
14979 }
14980 anyhow::Ok(())
14981 })
14982 .detach();
14983 }
14984
14985 pub(crate) fn navigate_to_hover_links(
14986 &mut self,
14987 kind: Option<GotoDefinitionKind>,
14988 mut definitions: Vec<HoverLink>,
14989 split: bool,
14990 window: &mut Window,
14991 cx: &mut Context<Editor>,
14992 ) -> Task<Result<Navigated>> {
14993 // If there is one definition, just open it directly
14994 if definitions.len() == 1 {
14995 let definition = definitions.pop().unwrap();
14996
14997 enum TargetTaskResult {
14998 Location(Option<Location>),
14999 AlreadyNavigated,
15000 }
15001
15002 let target_task = match definition {
15003 HoverLink::Text(link) => {
15004 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15005 }
15006 HoverLink::InlayHint(lsp_location, server_id) => {
15007 let computation =
15008 self.compute_target_location(lsp_location, server_id, window, cx);
15009 cx.background_spawn(async move {
15010 let location = computation.await?;
15011 Ok(TargetTaskResult::Location(location))
15012 })
15013 }
15014 HoverLink::Url(url) => {
15015 cx.open_url(&url);
15016 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15017 }
15018 HoverLink::File(path) => {
15019 if let Some(workspace) = self.workspace() {
15020 cx.spawn_in(window, async move |_, cx| {
15021 workspace
15022 .update_in(cx, |workspace, window, cx| {
15023 workspace.open_resolved_path(path, window, cx)
15024 })?
15025 .await
15026 .map(|_| TargetTaskResult::AlreadyNavigated)
15027 })
15028 } else {
15029 Task::ready(Ok(TargetTaskResult::Location(None)))
15030 }
15031 }
15032 };
15033 cx.spawn_in(window, async move |editor, cx| {
15034 let target = match target_task.await.context("target resolution task")? {
15035 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15036 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15037 TargetTaskResult::Location(Some(target)) => target,
15038 };
15039
15040 editor.update_in(cx, |editor, window, cx| {
15041 let Some(workspace) = editor.workspace() else {
15042 return Navigated::No;
15043 };
15044 let pane = workspace.read(cx).active_pane().clone();
15045
15046 let range = target.range.to_point(target.buffer.read(cx));
15047 let range = editor.range_for_match(&range);
15048 let range = collapse_multiline_range(range);
15049
15050 if !split
15051 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15052 {
15053 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15054 } else {
15055 window.defer(cx, move |window, cx| {
15056 let target_editor: Entity<Self> =
15057 workspace.update(cx, |workspace, cx| {
15058 let pane = if split {
15059 workspace.adjacent_pane(window, cx)
15060 } else {
15061 workspace.active_pane().clone()
15062 };
15063
15064 workspace.open_project_item(
15065 pane,
15066 target.buffer.clone(),
15067 true,
15068 true,
15069 window,
15070 cx,
15071 )
15072 });
15073 target_editor.update(cx, |target_editor, cx| {
15074 // When selecting a definition in a different buffer, disable the nav history
15075 // to avoid creating a history entry at the previous cursor location.
15076 pane.update(cx, |pane, _| pane.disable_history());
15077 target_editor.go_to_singleton_buffer_range(range, window, cx);
15078 pane.update(cx, |pane, _| pane.enable_history());
15079 });
15080 });
15081 }
15082 Navigated::Yes
15083 })
15084 })
15085 } else if !definitions.is_empty() {
15086 cx.spawn_in(window, async move |editor, cx| {
15087 let (title, location_tasks, workspace) = editor
15088 .update_in(cx, |editor, window, cx| {
15089 let tab_kind = match kind {
15090 Some(GotoDefinitionKind::Implementation) => "Implementations",
15091 _ => "Definitions",
15092 };
15093 let title = definitions
15094 .iter()
15095 .find_map(|definition| match definition {
15096 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15097 let buffer = origin.buffer.read(cx);
15098 format!(
15099 "{} for {}",
15100 tab_kind,
15101 buffer
15102 .text_for_range(origin.range.clone())
15103 .collect::<String>()
15104 )
15105 }),
15106 HoverLink::InlayHint(_, _) => None,
15107 HoverLink::Url(_) => None,
15108 HoverLink::File(_) => None,
15109 })
15110 .unwrap_or(tab_kind.to_string());
15111 let location_tasks = definitions
15112 .into_iter()
15113 .map(|definition| match definition {
15114 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15115 HoverLink::InlayHint(lsp_location, server_id) => editor
15116 .compute_target_location(lsp_location, server_id, window, cx),
15117 HoverLink::Url(_) => Task::ready(Ok(None)),
15118 HoverLink::File(_) => Task::ready(Ok(None)),
15119 })
15120 .collect::<Vec<_>>();
15121 (title, location_tasks, editor.workspace().clone())
15122 })
15123 .context("location tasks preparation")?;
15124
15125 let locations = future::join_all(location_tasks)
15126 .await
15127 .into_iter()
15128 .filter_map(|location| location.transpose())
15129 .collect::<Result<_>>()
15130 .context("location tasks")?;
15131
15132 let Some(workspace) = workspace else {
15133 return Ok(Navigated::No);
15134 };
15135 let opened = workspace
15136 .update_in(cx, |workspace, window, cx| {
15137 Self::open_locations_in_multibuffer(
15138 workspace,
15139 locations,
15140 title,
15141 split,
15142 MultibufferSelectionMode::First,
15143 window,
15144 cx,
15145 )
15146 })
15147 .ok();
15148
15149 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15150 })
15151 } else {
15152 Task::ready(Ok(Navigated::No))
15153 }
15154 }
15155
15156 fn compute_target_location(
15157 &self,
15158 lsp_location: lsp::Location,
15159 server_id: LanguageServerId,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) -> Task<anyhow::Result<Option<Location>>> {
15163 let Some(project) = self.project.clone() else {
15164 return Task::ready(Ok(None));
15165 };
15166
15167 cx.spawn_in(window, async move |editor, cx| {
15168 let location_task = editor.update(cx, |_, cx| {
15169 project.update(cx, |project, cx| {
15170 let language_server_name = project
15171 .language_server_statuses(cx)
15172 .find(|(id, _)| server_id == *id)
15173 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15174 language_server_name.map(|language_server_name| {
15175 project.open_local_buffer_via_lsp(
15176 lsp_location.uri.clone(),
15177 server_id,
15178 language_server_name,
15179 cx,
15180 )
15181 })
15182 })
15183 })?;
15184 let location = match location_task {
15185 Some(task) => Some({
15186 let target_buffer_handle = task.await.context("open local buffer")?;
15187 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15188 let target_start = target_buffer
15189 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15190 let target_end = target_buffer
15191 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15192 target_buffer.anchor_after(target_start)
15193 ..target_buffer.anchor_before(target_end)
15194 })?;
15195 Location {
15196 buffer: target_buffer_handle,
15197 range,
15198 }
15199 }),
15200 None => None,
15201 };
15202 Ok(location)
15203 })
15204 }
15205
15206 pub fn find_all_references(
15207 &mut self,
15208 _: &FindAllReferences,
15209 window: &mut Window,
15210 cx: &mut Context<Self>,
15211 ) -> Option<Task<Result<Navigated>>> {
15212 let selection = self.selections.newest::<usize>(cx);
15213 let multi_buffer = self.buffer.read(cx);
15214 let head = selection.head();
15215
15216 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15217 let head_anchor = multi_buffer_snapshot.anchor_at(
15218 head,
15219 if head < selection.tail() {
15220 Bias::Right
15221 } else {
15222 Bias::Left
15223 },
15224 );
15225
15226 match self
15227 .find_all_references_task_sources
15228 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15229 {
15230 Ok(_) => {
15231 log::info!(
15232 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15233 );
15234 return None;
15235 }
15236 Err(i) => {
15237 self.find_all_references_task_sources.insert(i, head_anchor);
15238 }
15239 }
15240
15241 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15242 let workspace = self.workspace()?;
15243 let project = workspace.read(cx).project().clone();
15244 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15245 Some(cx.spawn_in(window, async move |editor, cx| {
15246 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15247 if let Ok(i) = editor
15248 .find_all_references_task_sources
15249 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15250 {
15251 editor.find_all_references_task_sources.remove(i);
15252 }
15253 });
15254
15255 let locations = references.await?;
15256 if locations.is_empty() {
15257 return anyhow::Ok(Navigated::No);
15258 }
15259
15260 workspace.update_in(cx, |workspace, window, cx| {
15261 let title = locations
15262 .first()
15263 .as_ref()
15264 .map(|location| {
15265 let buffer = location.buffer.read(cx);
15266 format!(
15267 "References to `{}`",
15268 buffer
15269 .text_for_range(location.range.clone())
15270 .collect::<String>()
15271 )
15272 })
15273 .unwrap();
15274 Self::open_locations_in_multibuffer(
15275 workspace,
15276 locations,
15277 title,
15278 false,
15279 MultibufferSelectionMode::First,
15280 window,
15281 cx,
15282 );
15283 Navigated::Yes
15284 })
15285 }))
15286 }
15287
15288 /// Opens a multibuffer with the given project locations in it
15289 pub fn open_locations_in_multibuffer(
15290 workspace: &mut Workspace,
15291 mut locations: Vec<Location>,
15292 title: String,
15293 split: bool,
15294 multibuffer_selection_mode: MultibufferSelectionMode,
15295 window: &mut Window,
15296 cx: &mut Context<Workspace>,
15297 ) {
15298 // If there are multiple definitions, open them in a multibuffer
15299 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15300 let mut locations = locations.into_iter().peekable();
15301 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15302 let capability = workspace.project().read(cx).capability();
15303
15304 let excerpt_buffer = cx.new(|cx| {
15305 let mut multibuffer = MultiBuffer::new(capability);
15306 while let Some(location) = locations.next() {
15307 let buffer = location.buffer.read(cx);
15308 let mut ranges_for_buffer = Vec::new();
15309 let range = location.range.to_point(buffer);
15310 ranges_for_buffer.push(range.clone());
15311
15312 while let Some(next_location) = locations.peek() {
15313 if next_location.buffer == location.buffer {
15314 ranges_for_buffer.push(next_location.range.to_point(buffer));
15315 locations.next();
15316 } else {
15317 break;
15318 }
15319 }
15320
15321 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15322 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15323 PathKey::for_buffer(&location.buffer, cx),
15324 location.buffer.clone(),
15325 ranges_for_buffer,
15326 DEFAULT_MULTIBUFFER_CONTEXT,
15327 cx,
15328 );
15329 ranges.extend(new_ranges)
15330 }
15331
15332 multibuffer.with_title(title)
15333 });
15334
15335 let editor = cx.new(|cx| {
15336 Editor::for_multibuffer(
15337 excerpt_buffer,
15338 Some(workspace.project().clone()),
15339 window,
15340 cx,
15341 )
15342 });
15343 editor.update(cx, |editor, cx| {
15344 match multibuffer_selection_mode {
15345 MultibufferSelectionMode::First => {
15346 if let Some(first_range) = ranges.first() {
15347 editor.change_selections(None, window, cx, |selections| {
15348 selections.clear_disjoint();
15349 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15350 });
15351 }
15352 editor.highlight_background::<Self>(
15353 &ranges,
15354 |theme| theme.editor_highlighted_line_background,
15355 cx,
15356 );
15357 }
15358 MultibufferSelectionMode::All => {
15359 editor.change_selections(None, window, cx, |selections| {
15360 selections.clear_disjoint();
15361 selections.select_anchor_ranges(ranges);
15362 });
15363 }
15364 }
15365 editor.register_buffers_with_language_servers(cx);
15366 });
15367
15368 let item = Box::new(editor);
15369 let item_id = item.item_id();
15370
15371 if split {
15372 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15373 } else {
15374 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15375 let (preview_item_id, preview_item_idx) =
15376 workspace.active_pane().read_with(cx, |pane, _| {
15377 (pane.preview_item_id(), pane.preview_item_idx())
15378 });
15379
15380 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15381
15382 if let Some(preview_item_id) = preview_item_id {
15383 workspace.active_pane().update(cx, |pane, cx| {
15384 pane.remove_item(preview_item_id, false, false, window, cx);
15385 });
15386 }
15387 } else {
15388 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15389 }
15390 }
15391 workspace.active_pane().update(cx, |pane, cx| {
15392 pane.set_preview_item_id(Some(item_id), cx);
15393 });
15394 }
15395
15396 pub fn rename(
15397 &mut self,
15398 _: &Rename,
15399 window: &mut Window,
15400 cx: &mut Context<Self>,
15401 ) -> Option<Task<Result<()>>> {
15402 use language::ToOffset as _;
15403
15404 let provider = self.semantics_provider.clone()?;
15405 let selection = self.selections.newest_anchor().clone();
15406 let (cursor_buffer, cursor_buffer_position) = self
15407 .buffer
15408 .read(cx)
15409 .text_anchor_for_position(selection.head(), cx)?;
15410 let (tail_buffer, cursor_buffer_position_end) = self
15411 .buffer
15412 .read(cx)
15413 .text_anchor_for_position(selection.tail(), cx)?;
15414 if tail_buffer != cursor_buffer {
15415 return None;
15416 }
15417
15418 let snapshot = cursor_buffer.read(cx).snapshot();
15419 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15420 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15421 let prepare_rename = provider
15422 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15423 .unwrap_or_else(|| Task::ready(Ok(None)));
15424 drop(snapshot);
15425
15426 Some(cx.spawn_in(window, async move |this, cx| {
15427 let rename_range = if let Some(range) = prepare_rename.await? {
15428 Some(range)
15429 } else {
15430 this.update(cx, |this, cx| {
15431 let buffer = this.buffer.read(cx).snapshot(cx);
15432 let mut buffer_highlights = this
15433 .document_highlights_for_position(selection.head(), &buffer)
15434 .filter(|highlight| {
15435 highlight.start.excerpt_id == selection.head().excerpt_id
15436 && highlight.end.excerpt_id == selection.head().excerpt_id
15437 });
15438 buffer_highlights
15439 .next()
15440 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15441 })?
15442 };
15443 if let Some(rename_range) = rename_range {
15444 this.update_in(cx, |this, window, cx| {
15445 let snapshot = cursor_buffer.read(cx).snapshot();
15446 let rename_buffer_range = rename_range.to_offset(&snapshot);
15447 let cursor_offset_in_rename_range =
15448 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15449 let cursor_offset_in_rename_range_end =
15450 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15451
15452 this.take_rename(false, window, cx);
15453 let buffer = this.buffer.read(cx).read(cx);
15454 let cursor_offset = selection.head().to_offset(&buffer);
15455 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15456 let rename_end = rename_start + rename_buffer_range.len();
15457 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15458 let mut old_highlight_id = None;
15459 let old_name: Arc<str> = buffer
15460 .chunks(rename_start..rename_end, true)
15461 .map(|chunk| {
15462 if old_highlight_id.is_none() {
15463 old_highlight_id = chunk.syntax_highlight_id;
15464 }
15465 chunk.text
15466 })
15467 .collect::<String>()
15468 .into();
15469
15470 drop(buffer);
15471
15472 // Position the selection in the rename editor so that it matches the current selection.
15473 this.show_local_selections = false;
15474 let rename_editor = cx.new(|cx| {
15475 let mut editor = Editor::single_line(window, cx);
15476 editor.buffer.update(cx, |buffer, cx| {
15477 buffer.edit([(0..0, old_name.clone())], None, cx)
15478 });
15479 let rename_selection_range = match cursor_offset_in_rename_range
15480 .cmp(&cursor_offset_in_rename_range_end)
15481 {
15482 Ordering::Equal => {
15483 editor.select_all(&SelectAll, window, cx);
15484 return editor;
15485 }
15486 Ordering::Less => {
15487 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15488 }
15489 Ordering::Greater => {
15490 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15491 }
15492 };
15493 if rename_selection_range.end > old_name.len() {
15494 editor.select_all(&SelectAll, window, cx);
15495 } else {
15496 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15497 s.select_ranges([rename_selection_range]);
15498 });
15499 }
15500 editor
15501 });
15502 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15503 if e == &EditorEvent::Focused {
15504 cx.emit(EditorEvent::FocusedIn)
15505 }
15506 })
15507 .detach();
15508
15509 let write_highlights =
15510 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15511 let read_highlights =
15512 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15513 let ranges = write_highlights
15514 .iter()
15515 .flat_map(|(_, ranges)| ranges.iter())
15516 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15517 .cloned()
15518 .collect();
15519
15520 this.highlight_text::<Rename>(
15521 ranges,
15522 HighlightStyle {
15523 fade_out: Some(0.6),
15524 ..Default::default()
15525 },
15526 cx,
15527 );
15528 let rename_focus_handle = rename_editor.focus_handle(cx);
15529 window.focus(&rename_focus_handle);
15530 let block_id = this.insert_blocks(
15531 [BlockProperties {
15532 style: BlockStyle::Flex,
15533 placement: BlockPlacement::Below(range.start),
15534 height: Some(1),
15535 render: Arc::new({
15536 let rename_editor = rename_editor.clone();
15537 move |cx: &mut BlockContext| {
15538 let mut text_style = cx.editor_style.text.clone();
15539 if let Some(highlight_style) = old_highlight_id
15540 .and_then(|h| h.style(&cx.editor_style.syntax))
15541 {
15542 text_style = text_style.highlight(highlight_style);
15543 }
15544 div()
15545 .block_mouse_except_scroll()
15546 .pl(cx.anchor_x)
15547 .child(EditorElement::new(
15548 &rename_editor,
15549 EditorStyle {
15550 background: cx.theme().system().transparent,
15551 local_player: cx.editor_style.local_player,
15552 text: text_style,
15553 scrollbar_width: cx.editor_style.scrollbar_width,
15554 syntax: cx.editor_style.syntax.clone(),
15555 status: cx.editor_style.status.clone(),
15556 inlay_hints_style: HighlightStyle {
15557 font_weight: Some(FontWeight::BOLD),
15558 ..make_inlay_hints_style(cx.app)
15559 },
15560 inline_completion_styles: make_suggestion_styles(
15561 cx.app,
15562 ),
15563 ..EditorStyle::default()
15564 },
15565 ))
15566 .into_any_element()
15567 }
15568 }),
15569 priority: 0,
15570 render_in_minimap: true,
15571 }],
15572 Some(Autoscroll::fit()),
15573 cx,
15574 )[0];
15575 this.pending_rename = Some(RenameState {
15576 range,
15577 old_name,
15578 editor: rename_editor,
15579 block_id,
15580 });
15581 })?;
15582 }
15583
15584 Ok(())
15585 }))
15586 }
15587
15588 pub fn confirm_rename(
15589 &mut self,
15590 _: &ConfirmRename,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Option<Task<Result<()>>> {
15594 let rename = self.take_rename(false, window, cx)?;
15595 let workspace = self.workspace()?.downgrade();
15596 let (buffer, start) = self
15597 .buffer
15598 .read(cx)
15599 .text_anchor_for_position(rename.range.start, cx)?;
15600 let (end_buffer, _) = self
15601 .buffer
15602 .read(cx)
15603 .text_anchor_for_position(rename.range.end, cx)?;
15604 if buffer != end_buffer {
15605 return None;
15606 }
15607
15608 let old_name = rename.old_name;
15609 let new_name = rename.editor.read(cx).text(cx);
15610
15611 let rename = self.semantics_provider.as_ref()?.perform_rename(
15612 &buffer,
15613 start,
15614 new_name.clone(),
15615 cx,
15616 )?;
15617
15618 Some(cx.spawn_in(window, async move |editor, cx| {
15619 let project_transaction = rename.await?;
15620 Self::open_project_transaction(
15621 &editor,
15622 workspace,
15623 project_transaction,
15624 format!("Rename: {} → {}", old_name, new_name),
15625 cx,
15626 )
15627 .await?;
15628
15629 editor.update(cx, |editor, cx| {
15630 editor.refresh_document_highlights(cx);
15631 })?;
15632 Ok(())
15633 }))
15634 }
15635
15636 fn take_rename(
15637 &mut self,
15638 moving_cursor: bool,
15639 window: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) -> Option<RenameState> {
15642 let rename = self.pending_rename.take()?;
15643 if rename.editor.focus_handle(cx).is_focused(window) {
15644 window.focus(&self.focus_handle);
15645 }
15646
15647 self.remove_blocks(
15648 [rename.block_id].into_iter().collect(),
15649 Some(Autoscroll::fit()),
15650 cx,
15651 );
15652 self.clear_highlights::<Rename>(cx);
15653 self.show_local_selections = true;
15654
15655 if moving_cursor {
15656 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15657 editor.selections.newest::<usize>(cx).head()
15658 });
15659
15660 // Update the selection to match the position of the selection inside
15661 // the rename editor.
15662 let snapshot = self.buffer.read(cx).read(cx);
15663 let rename_range = rename.range.to_offset(&snapshot);
15664 let cursor_in_editor = snapshot
15665 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15666 .min(rename_range.end);
15667 drop(snapshot);
15668
15669 self.change_selections(None, window, cx, |s| {
15670 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15671 });
15672 } else {
15673 self.refresh_document_highlights(cx);
15674 }
15675
15676 Some(rename)
15677 }
15678
15679 pub fn pending_rename(&self) -> Option<&RenameState> {
15680 self.pending_rename.as_ref()
15681 }
15682
15683 fn format(
15684 &mut self,
15685 _: &Format,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) -> Option<Task<Result<()>>> {
15689 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15690
15691 let project = match &self.project {
15692 Some(project) => project.clone(),
15693 None => return None,
15694 };
15695
15696 Some(self.perform_format(
15697 project,
15698 FormatTrigger::Manual,
15699 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15700 window,
15701 cx,
15702 ))
15703 }
15704
15705 fn format_selections(
15706 &mut self,
15707 _: &FormatSelections,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Option<Task<Result<()>>> {
15711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15712
15713 let project = match &self.project {
15714 Some(project) => project.clone(),
15715 None => return None,
15716 };
15717
15718 let ranges = self
15719 .selections
15720 .all_adjusted(cx)
15721 .into_iter()
15722 .map(|selection| selection.range())
15723 .collect_vec();
15724
15725 Some(self.perform_format(
15726 project,
15727 FormatTrigger::Manual,
15728 FormatTarget::Ranges(ranges),
15729 window,
15730 cx,
15731 ))
15732 }
15733
15734 fn perform_format(
15735 &mut self,
15736 project: Entity<Project>,
15737 trigger: FormatTrigger,
15738 target: FormatTarget,
15739 window: &mut Window,
15740 cx: &mut Context<Self>,
15741 ) -> Task<Result<()>> {
15742 let buffer = self.buffer.clone();
15743 let (buffers, target) = match target {
15744 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15745 FormatTarget::Ranges(selection_ranges) => {
15746 let multi_buffer = buffer.read(cx);
15747 let snapshot = multi_buffer.read(cx);
15748 let mut buffers = HashSet::default();
15749 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15750 BTreeMap::new();
15751 for selection_range in selection_ranges {
15752 for (buffer, buffer_range, _) in
15753 snapshot.range_to_buffer_ranges(selection_range)
15754 {
15755 let buffer_id = buffer.remote_id();
15756 let start = buffer.anchor_before(buffer_range.start);
15757 let end = buffer.anchor_after(buffer_range.end);
15758 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15759 buffer_id_to_ranges
15760 .entry(buffer_id)
15761 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15762 .or_insert_with(|| vec![start..end]);
15763 }
15764 }
15765 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15766 }
15767 };
15768
15769 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15770 let selections_prev = transaction_id_prev
15771 .and_then(|transaction_id_prev| {
15772 // default to selections as they were after the last edit, if we have them,
15773 // instead of how they are now.
15774 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15775 // will take you back to where you made the last edit, instead of staying where you scrolled
15776 self.selection_history
15777 .transaction(transaction_id_prev)
15778 .map(|t| t.0.clone())
15779 })
15780 .unwrap_or_else(|| {
15781 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15782 self.selections.disjoint_anchors()
15783 });
15784
15785 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15786 let format = project.update(cx, |project, cx| {
15787 project.format(buffers, target, true, trigger, cx)
15788 });
15789
15790 cx.spawn_in(window, async move |editor, cx| {
15791 let transaction = futures::select_biased! {
15792 transaction = format.log_err().fuse() => transaction,
15793 () = timeout => {
15794 log::warn!("timed out waiting for formatting");
15795 None
15796 }
15797 };
15798
15799 buffer
15800 .update(cx, |buffer, cx| {
15801 if let Some(transaction) = transaction {
15802 if !buffer.is_singleton() {
15803 buffer.push_transaction(&transaction.0, cx);
15804 }
15805 }
15806 cx.notify();
15807 })
15808 .ok();
15809
15810 if let Some(transaction_id_now) =
15811 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15812 {
15813 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15814 if has_new_transaction {
15815 _ = editor.update(cx, |editor, _| {
15816 editor
15817 .selection_history
15818 .insert_transaction(transaction_id_now, selections_prev);
15819 });
15820 }
15821 }
15822
15823 Ok(())
15824 })
15825 }
15826
15827 fn organize_imports(
15828 &mut self,
15829 _: &OrganizeImports,
15830 window: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) -> Option<Task<Result<()>>> {
15833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15834 let project = match &self.project {
15835 Some(project) => project.clone(),
15836 None => return None,
15837 };
15838 Some(self.perform_code_action_kind(
15839 project,
15840 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15841 window,
15842 cx,
15843 ))
15844 }
15845
15846 fn perform_code_action_kind(
15847 &mut self,
15848 project: Entity<Project>,
15849 kind: CodeActionKind,
15850 window: &mut Window,
15851 cx: &mut Context<Self>,
15852 ) -> Task<Result<()>> {
15853 let buffer = self.buffer.clone();
15854 let buffers = buffer.read(cx).all_buffers();
15855 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15856 let apply_action = project.update(cx, |project, cx| {
15857 project.apply_code_action_kind(buffers, kind, true, cx)
15858 });
15859 cx.spawn_in(window, async move |_, cx| {
15860 let transaction = futures::select_biased! {
15861 () = timeout => {
15862 log::warn!("timed out waiting for executing code action");
15863 None
15864 }
15865 transaction = apply_action.log_err().fuse() => transaction,
15866 };
15867 buffer
15868 .update(cx, |buffer, cx| {
15869 // check if we need this
15870 if let Some(transaction) = transaction {
15871 if !buffer.is_singleton() {
15872 buffer.push_transaction(&transaction.0, cx);
15873 }
15874 }
15875 cx.notify();
15876 })
15877 .ok();
15878 Ok(())
15879 })
15880 }
15881
15882 fn restart_language_server(
15883 &mut self,
15884 _: &RestartLanguageServer,
15885 _: &mut Window,
15886 cx: &mut Context<Self>,
15887 ) {
15888 if let Some(project) = self.project.clone() {
15889 self.buffer.update(cx, |multi_buffer, cx| {
15890 project.update(cx, |project, cx| {
15891 project.restart_language_servers_for_buffers(
15892 multi_buffer.all_buffers().into_iter().collect(),
15893 cx,
15894 );
15895 });
15896 })
15897 }
15898 }
15899
15900 fn stop_language_server(
15901 &mut self,
15902 _: &StopLanguageServer,
15903 _: &mut Window,
15904 cx: &mut Context<Self>,
15905 ) {
15906 if let Some(project) = self.project.clone() {
15907 self.buffer.update(cx, |multi_buffer, cx| {
15908 project.update(cx, |project, cx| {
15909 project.stop_language_servers_for_buffers(
15910 multi_buffer.all_buffers().into_iter().collect(),
15911 cx,
15912 );
15913 cx.emit(project::Event::RefreshInlayHints);
15914 });
15915 });
15916 }
15917 }
15918
15919 fn cancel_language_server_work(
15920 workspace: &mut Workspace,
15921 _: &actions::CancelLanguageServerWork,
15922 _: &mut Window,
15923 cx: &mut Context<Workspace>,
15924 ) {
15925 let project = workspace.project();
15926 let buffers = workspace
15927 .active_item(cx)
15928 .and_then(|item| item.act_as::<Editor>(cx))
15929 .map_or(HashSet::default(), |editor| {
15930 editor.read(cx).buffer.read(cx).all_buffers()
15931 });
15932 project.update(cx, |project, cx| {
15933 project.cancel_language_server_work_for_buffers(buffers, cx);
15934 });
15935 }
15936
15937 fn show_character_palette(
15938 &mut self,
15939 _: &ShowCharacterPalette,
15940 window: &mut Window,
15941 _: &mut Context<Self>,
15942 ) {
15943 window.show_character_palette();
15944 }
15945
15946 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15947 if self.mode.is_minimap() {
15948 return;
15949 }
15950
15951 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15952 let buffer = self.buffer.read(cx).snapshot(cx);
15953 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15954 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15955 let is_valid = buffer
15956 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15957 .any(|entry| {
15958 entry.diagnostic.is_primary
15959 && !entry.range.is_empty()
15960 && entry.range.start == primary_range_start
15961 && entry.diagnostic.message == active_diagnostics.active_message
15962 });
15963
15964 if !is_valid {
15965 self.dismiss_diagnostics(cx);
15966 }
15967 }
15968 }
15969
15970 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15971 match &self.active_diagnostics {
15972 ActiveDiagnostic::Group(group) => Some(group),
15973 _ => None,
15974 }
15975 }
15976
15977 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15978 self.dismiss_diagnostics(cx);
15979 self.active_diagnostics = ActiveDiagnostic::All;
15980 }
15981
15982 fn activate_diagnostics(
15983 &mut self,
15984 buffer_id: BufferId,
15985 diagnostic: DiagnosticEntry<usize>,
15986 window: &mut Window,
15987 cx: &mut Context<Self>,
15988 ) {
15989 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15990 return;
15991 }
15992 self.dismiss_diagnostics(cx);
15993 let snapshot = self.snapshot(window, cx);
15994 let buffer = self.buffer.read(cx).snapshot(cx);
15995 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15996 return;
15997 };
15998
15999 let diagnostic_group = buffer
16000 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16001 .collect::<Vec<_>>();
16002
16003 let blocks =
16004 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16005
16006 let blocks = self.display_map.update(cx, |display_map, cx| {
16007 display_map.insert_blocks(blocks, cx).into_iter().collect()
16008 });
16009 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16010 active_range: buffer.anchor_before(diagnostic.range.start)
16011 ..buffer.anchor_after(diagnostic.range.end),
16012 active_message: diagnostic.diagnostic.message.clone(),
16013 group_id: diagnostic.diagnostic.group_id,
16014 blocks,
16015 });
16016 cx.notify();
16017 }
16018
16019 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16020 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16021 return;
16022 };
16023
16024 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16025 if let ActiveDiagnostic::Group(group) = prev {
16026 self.display_map.update(cx, |display_map, cx| {
16027 display_map.remove_blocks(group.blocks, cx);
16028 });
16029 cx.notify();
16030 }
16031 }
16032
16033 /// Disable inline diagnostics rendering for this editor.
16034 pub fn disable_inline_diagnostics(&mut self) {
16035 self.inline_diagnostics_enabled = false;
16036 self.inline_diagnostics_update = Task::ready(());
16037 self.inline_diagnostics.clear();
16038 }
16039
16040 pub fn diagnostics_enabled(&self) -> bool {
16041 self.mode.is_full()
16042 }
16043
16044 pub fn inline_diagnostics_enabled(&self) -> bool {
16045 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16046 }
16047
16048 pub fn show_inline_diagnostics(&self) -> bool {
16049 self.show_inline_diagnostics
16050 }
16051
16052 pub fn toggle_inline_diagnostics(
16053 &mut self,
16054 _: &ToggleInlineDiagnostics,
16055 window: &mut Window,
16056 cx: &mut Context<Editor>,
16057 ) {
16058 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16059 self.refresh_inline_diagnostics(false, window, cx);
16060 }
16061
16062 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16063 self.diagnostics_max_severity = severity;
16064 self.display_map.update(cx, |display_map, _| {
16065 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16066 });
16067 }
16068
16069 pub fn toggle_diagnostics(
16070 &mut self,
16071 _: &ToggleDiagnostics,
16072 window: &mut Window,
16073 cx: &mut Context<Editor>,
16074 ) {
16075 if !self.diagnostics_enabled() {
16076 return;
16077 }
16078
16079 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16080 EditorSettings::get_global(cx)
16081 .diagnostics_max_severity
16082 .filter(|severity| severity != &DiagnosticSeverity::Off)
16083 .unwrap_or(DiagnosticSeverity::Hint)
16084 } else {
16085 DiagnosticSeverity::Off
16086 };
16087 self.set_max_diagnostics_severity(new_severity, cx);
16088 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16089 self.active_diagnostics = ActiveDiagnostic::None;
16090 self.inline_diagnostics_update = Task::ready(());
16091 self.inline_diagnostics.clear();
16092 } else {
16093 self.refresh_inline_diagnostics(false, window, cx);
16094 }
16095
16096 cx.notify();
16097 }
16098
16099 pub fn toggle_minimap(
16100 &mut self,
16101 _: &ToggleMinimap,
16102 window: &mut Window,
16103 cx: &mut Context<Editor>,
16104 ) {
16105 if self.supports_minimap(cx) {
16106 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16107 }
16108 }
16109
16110 fn refresh_inline_diagnostics(
16111 &mut self,
16112 debounce: bool,
16113 window: &mut Window,
16114 cx: &mut Context<Self>,
16115 ) {
16116 let max_severity = ProjectSettings::get_global(cx)
16117 .diagnostics
16118 .inline
16119 .max_severity
16120 .unwrap_or(self.diagnostics_max_severity);
16121
16122 if !self.inline_diagnostics_enabled()
16123 || !self.show_inline_diagnostics
16124 || max_severity == DiagnosticSeverity::Off
16125 {
16126 self.inline_diagnostics_update = Task::ready(());
16127 self.inline_diagnostics.clear();
16128 return;
16129 }
16130
16131 let debounce_ms = ProjectSettings::get_global(cx)
16132 .diagnostics
16133 .inline
16134 .update_debounce_ms;
16135 let debounce = if debounce && debounce_ms > 0 {
16136 Some(Duration::from_millis(debounce_ms))
16137 } else {
16138 None
16139 };
16140 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16141 if let Some(debounce) = debounce {
16142 cx.background_executor().timer(debounce).await;
16143 }
16144 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16145 editor
16146 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16147 .ok()
16148 }) else {
16149 return;
16150 };
16151
16152 let new_inline_diagnostics = cx
16153 .background_spawn(async move {
16154 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16155 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16156 let message = diagnostic_entry
16157 .diagnostic
16158 .message
16159 .split_once('\n')
16160 .map(|(line, _)| line)
16161 .map(SharedString::new)
16162 .unwrap_or_else(|| {
16163 SharedString::from(diagnostic_entry.diagnostic.message)
16164 });
16165 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16166 let (Ok(i) | Err(i)) = inline_diagnostics
16167 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16168 inline_diagnostics.insert(
16169 i,
16170 (
16171 start_anchor,
16172 InlineDiagnostic {
16173 message,
16174 group_id: diagnostic_entry.diagnostic.group_id,
16175 start: diagnostic_entry.range.start.to_point(&snapshot),
16176 is_primary: diagnostic_entry.diagnostic.is_primary,
16177 severity: diagnostic_entry.diagnostic.severity,
16178 },
16179 ),
16180 );
16181 }
16182 inline_diagnostics
16183 })
16184 .await;
16185
16186 editor
16187 .update(cx, |editor, cx| {
16188 editor.inline_diagnostics = new_inline_diagnostics;
16189 cx.notify();
16190 })
16191 .ok();
16192 });
16193 }
16194
16195 fn pull_diagnostics(
16196 &mut self,
16197 buffer_id: Option<BufferId>,
16198 window: &Window,
16199 cx: &mut Context<Self>,
16200 ) -> Option<()> {
16201 if !self.mode().is_full() {
16202 return None;
16203 }
16204 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16205 .diagnostics
16206 .lsp_pull_diagnostics;
16207 if !pull_diagnostics_settings.enabled {
16208 return None;
16209 }
16210 let project = self.project.as_ref()?.downgrade();
16211 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16212 let mut buffers = self.buffer.read(cx).all_buffers();
16213 if let Some(buffer_id) = buffer_id {
16214 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16215 }
16216
16217 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16218 cx.background_executor().timer(debounce).await;
16219
16220 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16221 buffers
16222 .into_iter()
16223 .flat_map(|buffer| {
16224 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16225 })
16226 .collect::<FuturesUnordered<_>>()
16227 }) else {
16228 return;
16229 };
16230
16231 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16232 match pull_task {
16233 Ok(()) => {
16234 if editor
16235 .update_in(cx, |editor, window, cx| {
16236 editor.update_diagnostics_state(window, cx);
16237 })
16238 .is_err()
16239 {
16240 return;
16241 }
16242 }
16243 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16244 }
16245 }
16246 });
16247
16248 Some(())
16249 }
16250
16251 pub fn set_selections_from_remote(
16252 &mut self,
16253 selections: Vec<Selection<Anchor>>,
16254 pending_selection: Option<Selection<Anchor>>,
16255 window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 let old_cursor_position = self.selections.newest_anchor().head();
16259 self.selections.change_with(cx, |s| {
16260 s.select_anchors(selections);
16261 if let Some(pending_selection) = pending_selection {
16262 s.set_pending(pending_selection, SelectMode::Character);
16263 } else {
16264 s.clear_pending();
16265 }
16266 });
16267 self.selections_did_change(
16268 false,
16269 &old_cursor_position,
16270 SelectionEffects::default(),
16271 window,
16272 cx,
16273 );
16274 }
16275
16276 pub fn transact(
16277 &mut self,
16278 window: &mut Window,
16279 cx: &mut Context<Self>,
16280 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16281 ) -> Option<TransactionId> {
16282 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16283 this.start_transaction_at(Instant::now(), window, cx);
16284 update(this, window, cx);
16285 this.end_transaction_at(Instant::now(), cx)
16286 })
16287 }
16288
16289 pub fn start_transaction_at(
16290 &mut self,
16291 now: Instant,
16292 window: &mut Window,
16293 cx: &mut Context<Self>,
16294 ) {
16295 self.end_selection(window, cx);
16296 if let Some(tx_id) = self
16297 .buffer
16298 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16299 {
16300 self.selection_history
16301 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16302 cx.emit(EditorEvent::TransactionBegun {
16303 transaction_id: tx_id,
16304 })
16305 }
16306 }
16307
16308 pub fn end_transaction_at(
16309 &mut self,
16310 now: Instant,
16311 cx: &mut Context<Self>,
16312 ) -> Option<TransactionId> {
16313 if let Some(transaction_id) = self
16314 .buffer
16315 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16316 {
16317 if let Some((_, end_selections)) =
16318 self.selection_history.transaction_mut(transaction_id)
16319 {
16320 *end_selections = Some(self.selections.disjoint_anchors());
16321 } else {
16322 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16323 }
16324
16325 cx.emit(EditorEvent::Edited { transaction_id });
16326 Some(transaction_id)
16327 } else {
16328 None
16329 }
16330 }
16331
16332 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16333 if self.selection_mark_mode {
16334 self.change_selections(None, window, cx, |s| {
16335 s.move_with(|_, sel| {
16336 sel.collapse_to(sel.head(), SelectionGoal::None);
16337 });
16338 })
16339 }
16340 self.selection_mark_mode = true;
16341 cx.notify();
16342 }
16343
16344 pub fn swap_selection_ends(
16345 &mut self,
16346 _: &actions::SwapSelectionEnds,
16347 window: &mut Window,
16348 cx: &mut Context<Self>,
16349 ) {
16350 self.change_selections(None, window, cx, |s| {
16351 s.move_with(|_, sel| {
16352 if sel.start != sel.end {
16353 sel.reversed = !sel.reversed
16354 }
16355 });
16356 });
16357 self.request_autoscroll(Autoscroll::newest(), cx);
16358 cx.notify();
16359 }
16360
16361 pub fn toggle_fold(
16362 &mut self,
16363 _: &actions::ToggleFold,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) {
16367 if self.is_singleton(cx) {
16368 let selection = self.selections.newest::<Point>(cx);
16369
16370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16371 let range = if selection.is_empty() {
16372 let point = selection.head().to_display_point(&display_map);
16373 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16374 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16375 .to_point(&display_map);
16376 start..end
16377 } else {
16378 selection.range()
16379 };
16380 if display_map.folds_in_range(range).next().is_some() {
16381 self.unfold_lines(&Default::default(), window, cx)
16382 } else {
16383 self.fold(&Default::default(), window, cx)
16384 }
16385 } else {
16386 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16387 let buffer_ids: HashSet<_> = self
16388 .selections
16389 .disjoint_anchor_ranges()
16390 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16391 .collect();
16392
16393 let should_unfold = buffer_ids
16394 .iter()
16395 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16396
16397 for buffer_id in buffer_ids {
16398 if should_unfold {
16399 self.unfold_buffer(buffer_id, cx);
16400 } else {
16401 self.fold_buffer(buffer_id, cx);
16402 }
16403 }
16404 }
16405 }
16406
16407 pub fn toggle_fold_recursive(
16408 &mut self,
16409 _: &actions::ToggleFoldRecursive,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) {
16413 let selection = self.selections.newest::<Point>(cx);
16414
16415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16416 let range = if selection.is_empty() {
16417 let point = selection.head().to_display_point(&display_map);
16418 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16419 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16420 .to_point(&display_map);
16421 start..end
16422 } else {
16423 selection.range()
16424 };
16425 if display_map.folds_in_range(range).next().is_some() {
16426 self.unfold_recursive(&Default::default(), window, cx)
16427 } else {
16428 self.fold_recursive(&Default::default(), window, cx)
16429 }
16430 }
16431
16432 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16433 if self.is_singleton(cx) {
16434 let mut to_fold = Vec::new();
16435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16436 let selections = self.selections.all_adjusted(cx);
16437
16438 for selection in selections {
16439 let range = selection.range().sorted();
16440 let buffer_start_row = range.start.row;
16441
16442 if range.start.row != range.end.row {
16443 let mut found = false;
16444 let mut row = range.start.row;
16445 while row <= range.end.row {
16446 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16447 {
16448 found = true;
16449 row = crease.range().end.row + 1;
16450 to_fold.push(crease);
16451 } else {
16452 row += 1
16453 }
16454 }
16455 if found {
16456 continue;
16457 }
16458 }
16459
16460 for row in (0..=range.start.row).rev() {
16461 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16462 if crease.range().end.row >= buffer_start_row {
16463 to_fold.push(crease);
16464 if row <= range.start.row {
16465 break;
16466 }
16467 }
16468 }
16469 }
16470 }
16471
16472 self.fold_creases(to_fold, true, window, cx);
16473 } else {
16474 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16475 let buffer_ids = self
16476 .selections
16477 .disjoint_anchor_ranges()
16478 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16479 .collect::<HashSet<_>>();
16480 for buffer_id in buffer_ids {
16481 self.fold_buffer(buffer_id, cx);
16482 }
16483 }
16484 }
16485
16486 fn fold_at_level(
16487 &mut self,
16488 fold_at: &FoldAtLevel,
16489 window: &mut Window,
16490 cx: &mut Context<Self>,
16491 ) {
16492 if !self.buffer.read(cx).is_singleton() {
16493 return;
16494 }
16495
16496 let fold_at_level = fold_at.0;
16497 let snapshot = self.buffer.read(cx).snapshot(cx);
16498 let mut to_fold = Vec::new();
16499 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16500
16501 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16502 while start_row < end_row {
16503 match self
16504 .snapshot(window, cx)
16505 .crease_for_buffer_row(MultiBufferRow(start_row))
16506 {
16507 Some(crease) => {
16508 let nested_start_row = crease.range().start.row + 1;
16509 let nested_end_row = crease.range().end.row;
16510
16511 if current_level < fold_at_level {
16512 stack.push((nested_start_row, nested_end_row, current_level + 1));
16513 } else if current_level == fold_at_level {
16514 to_fold.push(crease);
16515 }
16516
16517 start_row = nested_end_row + 1;
16518 }
16519 None => start_row += 1,
16520 }
16521 }
16522 }
16523
16524 self.fold_creases(to_fold, true, window, cx);
16525 }
16526
16527 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16528 if self.buffer.read(cx).is_singleton() {
16529 let mut fold_ranges = Vec::new();
16530 let snapshot = self.buffer.read(cx).snapshot(cx);
16531
16532 for row in 0..snapshot.max_row().0 {
16533 if let Some(foldable_range) = self
16534 .snapshot(window, cx)
16535 .crease_for_buffer_row(MultiBufferRow(row))
16536 {
16537 fold_ranges.push(foldable_range);
16538 }
16539 }
16540
16541 self.fold_creases(fold_ranges, true, window, cx);
16542 } else {
16543 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16544 editor
16545 .update_in(cx, |editor, _, cx| {
16546 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16547 editor.fold_buffer(buffer_id, cx);
16548 }
16549 })
16550 .ok();
16551 });
16552 }
16553 }
16554
16555 pub fn fold_function_bodies(
16556 &mut self,
16557 _: &actions::FoldFunctionBodies,
16558 window: &mut Window,
16559 cx: &mut Context<Self>,
16560 ) {
16561 let snapshot = self.buffer.read(cx).snapshot(cx);
16562
16563 let ranges = snapshot
16564 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16565 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16566 .collect::<Vec<_>>();
16567
16568 let creases = ranges
16569 .into_iter()
16570 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16571 .collect();
16572
16573 self.fold_creases(creases, true, window, cx);
16574 }
16575
16576 pub fn fold_recursive(
16577 &mut self,
16578 _: &actions::FoldRecursive,
16579 window: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 let mut to_fold = Vec::new();
16583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16584 let selections = self.selections.all_adjusted(cx);
16585
16586 for selection in selections {
16587 let range = selection.range().sorted();
16588 let buffer_start_row = range.start.row;
16589
16590 if range.start.row != range.end.row {
16591 let mut found = false;
16592 for row in range.start.row..=range.end.row {
16593 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16594 found = true;
16595 to_fold.push(crease);
16596 }
16597 }
16598 if found {
16599 continue;
16600 }
16601 }
16602
16603 for row in (0..=range.start.row).rev() {
16604 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16605 if crease.range().end.row >= buffer_start_row {
16606 to_fold.push(crease);
16607 } else {
16608 break;
16609 }
16610 }
16611 }
16612 }
16613
16614 self.fold_creases(to_fold, true, window, cx);
16615 }
16616
16617 pub fn fold_at(
16618 &mut self,
16619 buffer_row: MultiBufferRow,
16620 window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) {
16623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16624
16625 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16626 let autoscroll = self
16627 .selections
16628 .all::<Point>(cx)
16629 .iter()
16630 .any(|selection| crease.range().overlaps(&selection.range()));
16631
16632 self.fold_creases(vec![crease], autoscroll, window, cx);
16633 }
16634 }
16635
16636 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16637 if self.is_singleton(cx) {
16638 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16639 let buffer = &display_map.buffer_snapshot;
16640 let selections = self.selections.all::<Point>(cx);
16641 let ranges = selections
16642 .iter()
16643 .map(|s| {
16644 let range = s.display_range(&display_map).sorted();
16645 let mut start = range.start.to_point(&display_map);
16646 let mut end = range.end.to_point(&display_map);
16647 start.column = 0;
16648 end.column = buffer.line_len(MultiBufferRow(end.row));
16649 start..end
16650 })
16651 .collect::<Vec<_>>();
16652
16653 self.unfold_ranges(&ranges, true, true, cx);
16654 } else {
16655 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16656 let buffer_ids = self
16657 .selections
16658 .disjoint_anchor_ranges()
16659 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16660 .collect::<HashSet<_>>();
16661 for buffer_id in buffer_ids {
16662 self.unfold_buffer(buffer_id, cx);
16663 }
16664 }
16665 }
16666
16667 pub fn unfold_recursive(
16668 &mut self,
16669 _: &UnfoldRecursive,
16670 _window: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) {
16673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16674 let selections = self.selections.all::<Point>(cx);
16675 let ranges = selections
16676 .iter()
16677 .map(|s| {
16678 let mut range = s.display_range(&display_map).sorted();
16679 *range.start.column_mut() = 0;
16680 *range.end.column_mut() = display_map.line_len(range.end.row());
16681 let start = range.start.to_point(&display_map);
16682 let end = range.end.to_point(&display_map);
16683 start..end
16684 })
16685 .collect::<Vec<_>>();
16686
16687 self.unfold_ranges(&ranges, true, true, cx);
16688 }
16689
16690 pub fn unfold_at(
16691 &mut self,
16692 buffer_row: MultiBufferRow,
16693 _window: &mut Window,
16694 cx: &mut Context<Self>,
16695 ) {
16696 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16697
16698 let intersection_range = Point::new(buffer_row.0, 0)
16699 ..Point::new(
16700 buffer_row.0,
16701 display_map.buffer_snapshot.line_len(buffer_row),
16702 );
16703
16704 let autoscroll = self
16705 .selections
16706 .all::<Point>(cx)
16707 .iter()
16708 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16709
16710 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16711 }
16712
16713 pub fn unfold_all(
16714 &mut self,
16715 _: &actions::UnfoldAll,
16716 _window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) {
16719 if self.buffer.read(cx).is_singleton() {
16720 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16721 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16722 } else {
16723 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16724 editor
16725 .update(cx, |editor, cx| {
16726 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16727 editor.unfold_buffer(buffer_id, cx);
16728 }
16729 })
16730 .ok();
16731 });
16732 }
16733 }
16734
16735 pub fn fold_selected_ranges(
16736 &mut self,
16737 _: &FoldSelectedRanges,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 let selections = self.selections.all_adjusted(cx);
16742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16743 let ranges = selections
16744 .into_iter()
16745 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16746 .collect::<Vec<_>>();
16747 self.fold_creases(ranges, true, window, cx);
16748 }
16749
16750 pub fn fold_ranges<T: ToOffset + Clone>(
16751 &mut self,
16752 ranges: Vec<Range<T>>,
16753 auto_scroll: bool,
16754 window: &mut Window,
16755 cx: &mut Context<Self>,
16756 ) {
16757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16758 let ranges = ranges
16759 .into_iter()
16760 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16761 .collect::<Vec<_>>();
16762 self.fold_creases(ranges, auto_scroll, window, cx);
16763 }
16764
16765 pub fn fold_creases<T: ToOffset + Clone>(
16766 &mut self,
16767 creases: Vec<Crease<T>>,
16768 auto_scroll: bool,
16769 _window: &mut Window,
16770 cx: &mut Context<Self>,
16771 ) {
16772 if creases.is_empty() {
16773 return;
16774 }
16775
16776 let mut buffers_affected = HashSet::default();
16777 let multi_buffer = self.buffer().read(cx);
16778 for crease in &creases {
16779 if let Some((_, buffer, _)) =
16780 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16781 {
16782 buffers_affected.insert(buffer.read(cx).remote_id());
16783 };
16784 }
16785
16786 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16787
16788 if auto_scroll {
16789 self.request_autoscroll(Autoscroll::fit(), cx);
16790 }
16791
16792 cx.notify();
16793
16794 self.scrollbar_marker_state.dirty = true;
16795 self.folds_did_change(cx);
16796 }
16797
16798 /// Removes any folds whose ranges intersect any of the given ranges.
16799 pub fn unfold_ranges<T: ToOffset + Clone>(
16800 &mut self,
16801 ranges: &[Range<T>],
16802 inclusive: bool,
16803 auto_scroll: bool,
16804 cx: &mut Context<Self>,
16805 ) {
16806 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16807 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16808 });
16809 self.folds_did_change(cx);
16810 }
16811
16812 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16813 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16814 return;
16815 }
16816 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16817 self.display_map.update(cx, |display_map, cx| {
16818 display_map.fold_buffers([buffer_id], cx)
16819 });
16820 cx.emit(EditorEvent::BufferFoldToggled {
16821 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16822 folded: true,
16823 });
16824 cx.notify();
16825 }
16826
16827 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16828 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16829 return;
16830 }
16831 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16832 self.display_map.update(cx, |display_map, cx| {
16833 display_map.unfold_buffers([buffer_id], cx);
16834 });
16835 cx.emit(EditorEvent::BufferFoldToggled {
16836 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16837 folded: false,
16838 });
16839 cx.notify();
16840 }
16841
16842 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16843 self.display_map.read(cx).is_buffer_folded(buffer)
16844 }
16845
16846 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16847 self.display_map.read(cx).folded_buffers()
16848 }
16849
16850 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16851 self.display_map.update(cx, |display_map, cx| {
16852 display_map.disable_header_for_buffer(buffer_id, cx);
16853 });
16854 cx.notify();
16855 }
16856
16857 /// Removes any folds with the given ranges.
16858 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16859 &mut self,
16860 ranges: &[Range<T>],
16861 type_id: TypeId,
16862 auto_scroll: bool,
16863 cx: &mut Context<Self>,
16864 ) {
16865 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16866 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16867 });
16868 self.folds_did_change(cx);
16869 }
16870
16871 fn remove_folds_with<T: ToOffset + Clone>(
16872 &mut self,
16873 ranges: &[Range<T>],
16874 auto_scroll: bool,
16875 cx: &mut Context<Self>,
16876 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16877 ) {
16878 if ranges.is_empty() {
16879 return;
16880 }
16881
16882 let mut buffers_affected = HashSet::default();
16883 let multi_buffer = self.buffer().read(cx);
16884 for range in ranges {
16885 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16886 buffers_affected.insert(buffer.read(cx).remote_id());
16887 };
16888 }
16889
16890 self.display_map.update(cx, update);
16891
16892 if auto_scroll {
16893 self.request_autoscroll(Autoscroll::fit(), cx);
16894 }
16895
16896 cx.notify();
16897 self.scrollbar_marker_state.dirty = true;
16898 self.active_indent_guides_state.dirty = true;
16899 }
16900
16901 pub fn update_fold_widths(
16902 &mut self,
16903 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16904 cx: &mut Context<Self>,
16905 ) -> bool {
16906 self.display_map
16907 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16908 }
16909
16910 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16911 self.display_map.read(cx).fold_placeholder.clone()
16912 }
16913
16914 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16915 self.buffer.update(cx, |buffer, cx| {
16916 buffer.set_all_diff_hunks_expanded(cx);
16917 });
16918 }
16919
16920 pub fn expand_all_diff_hunks(
16921 &mut self,
16922 _: &ExpandAllDiffHunks,
16923 _window: &mut Window,
16924 cx: &mut Context<Self>,
16925 ) {
16926 self.buffer.update(cx, |buffer, cx| {
16927 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16928 });
16929 }
16930
16931 pub fn toggle_selected_diff_hunks(
16932 &mut self,
16933 _: &ToggleSelectedDiffHunks,
16934 _window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16938 self.toggle_diff_hunks_in_ranges(ranges, cx);
16939 }
16940
16941 pub fn diff_hunks_in_ranges<'a>(
16942 &'a self,
16943 ranges: &'a [Range<Anchor>],
16944 buffer: &'a MultiBufferSnapshot,
16945 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16946 ranges.iter().flat_map(move |range| {
16947 let end_excerpt_id = range.end.excerpt_id;
16948 let range = range.to_point(buffer);
16949 let mut peek_end = range.end;
16950 if range.end.row < buffer.max_row().0 {
16951 peek_end = Point::new(range.end.row + 1, 0);
16952 }
16953 buffer
16954 .diff_hunks_in_range(range.start..peek_end)
16955 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16956 })
16957 }
16958
16959 pub fn has_stageable_diff_hunks_in_ranges(
16960 &self,
16961 ranges: &[Range<Anchor>],
16962 snapshot: &MultiBufferSnapshot,
16963 ) -> bool {
16964 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16965 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16966 }
16967
16968 pub fn toggle_staged_selected_diff_hunks(
16969 &mut self,
16970 _: &::git::ToggleStaged,
16971 _: &mut Window,
16972 cx: &mut Context<Self>,
16973 ) {
16974 let snapshot = self.buffer.read(cx).snapshot(cx);
16975 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16976 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16977 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16978 }
16979
16980 pub fn set_render_diff_hunk_controls(
16981 &mut self,
16982 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16983 cx: &mut Context<Self>,
16984 ) {
16985 self.render_diff_hunk_controls = render_diff_hunk_controls;
16986 cx.notify();
16987 }
16988
16989 pub fn stage_and_next(
16990 &mut self,
16991 _: &::git::StageAndNext,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 self.do_stage_or_unstage_and_next(true, window, cx);
16996 }
16997
16998 pub fn unstage_and_next(
16999 &mut self,
17000 _: &::git::UnstageAndNext,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) {
17004 self.do_stage_or_unstage_and_next(false, window, cx);
17005 }
17006
17007 pub fn stage_or_unstage_diff_hunks(
17008 &mut self,
17009 stage: bool,
17010 ranges: Vec<Range<Anchor>>,
17011 cx: &mut Context<Self>,
17012 ) {
17013 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17014 cx.spawn(async move |this, cx| {
17015 task.await?;
17016 this.update(cx, |this, cx| {
17017 let snapshot = this.buffer.read(cx).snapshot(cx);
17018 let chunk_by = this
17019 .diff_hunks_in_ranges(&ranges, &snapshot)
17020 .chunk_by(|hunk| hunk.buffer_id);
17021 for (buffer_id, hunks) in &chunk_by {
17022 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17023 }
17024 })
17025 })
17026 .detach_and_log_err(cx);
17027 }
17028
17029 fn save_buffers_for_ranges_if_needed(
17030 &mut self,
17031 ranges: &[Range<Anchor>],
17032 cx: &mut Context<Editor>,
17033 ) -> Task<Result<()>> {
17034 let multibuffer = self.buffer.read(cx);
17035 let snapshot = multibuffer.read(cx);
17036 let buffer_ids: HashSet<_> = ranges
17037 .iter()
17038 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17039 .collect();
17040 drop(snapshot);
17041
17042 let mut buffers = HashSet::default();
17043 for buffer_id in buffer_ids {
17044 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17045 let buffer = buffer_entity.read(cx);
17046 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17047 {
17048 buffers.insert(buffer_entity);
17049 }
17050 }
17051 }
17052
17053 if let Some(project) = &self.project {
17054 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17055 } else {
17056 Task::ready(Ok(()))
17057 }
17058 }
17059
17060 fn do_stage_or_unstage_and_next(
17061 &mut self,
17062 stage: bool,
17063 window: &mut Window,
17064 cx: &mut Context<Self>,
17065 ) {
17066 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17067
17068 if ranges.iter().any(|range| range.start != range.end) {
17069 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17070 return;
17071 }
17072
17073 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17074 let snapshot = self.snapshot(window, cx);
17075 let position = self.selections.newest::<Point>(cx).head();
17076 let mut row = snapshot
17077 .buffer_snapshot
17078 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17079 .find(|hunk| hunk.row_range.start.0 > position.row)
17080 .map(|hunk| hunk.row_range.start);
17081
17082 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17083 // Outside of the project diff editor, wrap around to the beginning.
17084 if !all_diff_hunks_expanded {
17085 row = row.or_else(|| {
17086 snapshot
17087 .buffer_snapshot
17088 .diff_hunks_in_range(Point::zero()..position)
17089 .find(|hunk| hunk.row_range.end.0 < position.row)
17090 .map(|hunk| hunk.row_range.start)
17091 });
17092 }
17093
17094 if let Some(row) = row {
17095 let destination = Point::new(row.0, 0);
17096 let autoscroll = Autoscroll::center();
17097
17098 self.unfold_ranges(&[destination..destination], false, false, cx);
17099 self.change_selections(Some(autoscroll), window, cx, |s| {
17100 s.select_ranges([destination..destination]);
17101 });
17102 }
17103 }
17104
17105 fn do_stage_or_unstage(
17106 &self,
17107 stage: bool,
17108 buffer_id: BufferId,
17109 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17110 cx: &mut App,
17111 ) -> Option<()> {
17112 let project = self.project.as_ref()?;
17113 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17114 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17115 let buffer_snapshot = buffer.read(cx).snapshot();
17116 let file_exists = buffer_snapshot
17117 .file()
17118 .is_some_and(|file| file.disk_state().exists());
17119 diff.update(cx, |diff, cx| {
17120 diff.stage_or_unstage_hunks(
17121 stage,
17122 &hunks
17123 .map(|hunk| buffer_diff::DiffHunk {
17124 buffer_range: hunk.buffer_range,
17125 diff_base_byte_range: hunk.diff_base_byte_range,
17126 secondary_status: hunk.secondary_status,
17127 range: Point::zero()..Point::zero(), // unused
17128 })
17129 .collect::<Vec<_>>(),
17130 &buffer_snapshot,
17131 file_exists,
17132 cx,
17133 )
17134 });
17135 None
17136 }
17137
17138 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17139 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17140 self.buffer
17141 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17142 }
17143
17144 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17145 self.buffer.update(cx, |buffer, cx| {
17146 let ranges = vec![Anchor::min()..Anchor::max()];
17147 if !buffer.all_diff_hunks_expanded()
17148 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17149 {
17150 buffer.collapse_diff_hunks(ranges, cx);
17151 true
17152 } else {
17153 false
17154 }
17155 })
17156 }
17157
17158 fn toggle_diff_hunks_in_ranges(
17159 &mut self,
17160 ranges: Vec<Range<Anchor>>,
17161 cx: &mut Context<Editor>,
17162 ) {
17163 self.buffer.update(cx, |buffer, cx| {
17164 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17165 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17166 })
17167 }
17168
17169 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17170 self.buffer.update(cx, |buffer, cx| {
17171 let snapshot = buffer.snapshot(cx);
17172 let excerpt_id = range.end.excerpt_id;
17173 let point_range = range.to_point(&snapshot);
17174 let expand = !buffer.single_hunk_is_expanded(range, cx);
17175 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17176 })
17177 }
17178
17179 pub(crate) fn apply_all_diff_hunks(
17180 &mut self,
17181 _: &ApplyAllDiffHunks,
17182 window: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) {
17185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17186
17187 let buffers = self.buffer.read(cx).all_buffers();
17188 for branch_buffer in buffers {
17189 branch_buffer.update(cx, |branch_buffer, cx| {
17190 branch_buffer.merge_into_base(Vec::new(), cx);
17191 });
17192 }
17193
17194 if let Some(project) = self.project.clone() {
17195 self.save(
17196 SaveOptions {
17197 format: true,
17198 autosave: false,
17199 },
17200 project,
17201 window,
17202 cx,
17203 )
17204 .detach_and_log_err(cx);
17205 }
17206 }
17207
17208 pub(crate) fn apply_selected_diff_hunks(
17209 &mut self,
17210 _: &ApplyDiffHunk,
17211 window: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17215 let snapshot = self.snapshot(window, cx);
17216 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17217 let mut ranges_by_buffer = HashMap::default();
17218 self.transact(window, cx, |editor, _window, cx| {
17219 for hunk in hunks {
17220 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17221 ranges_by_buffer
17222 .entry(buffer.clone())
17223 .or_insert_with(Vec::new)
17224 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17225 }
17226 }
17227
17228 for (buffer, ranges) in ranges_by_buffer {
17229 buffer.update(cx, |buffer, cx| {
17230 buffer.merge_into_base(ranges, cx);
17231 });
17232 }
17233 });
17234
17235 if let Some(project) = self.project.clone() {
17236 self.save(
17237 SaveOptions {
17238 format: true,
17239 autosave: false,
17240 },
17241 project,
17242 window,
17243 cx,
17244 )
17245 .detach_and_log_err(cx);
17246 }
17247 }
17248
17249 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17250 if hovered != self.gutter_hovered {
17251 self.gutter_hovered = hovered;
17252 cx.notify();
17253 }
17254 }
17255
17256 pub fn insert_blocks(
17257 &mut self,
17258 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17259 autoscroll: Option<Autoscroll>,
17260 cx: &mut Context<Self>,
17261 ) -> Vec<CustomBlockId> {
17262 let blocks = self
17263 .display_map
17264 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17265 if let Some(autoscroll) = autoscroll {
17266 self.request_autoscroll(autoscroll, cx);
17267 }
17268 cx.notify();
17269 blocks
17270 }
17271
17272 pub fn resize_blocks(
17273 &mut self,
17274 heights: HashMap<CustomBlockId, u32>,
17275 autoscroll: Option<Autoscroll>,
17276 cx: &mut Context<Self>,
17277 ) {
17278 self.display_map
17279 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17280 if let Some(autoscroll) = autoscroll {
17281 self.request_autoscroll(autoscroll, cx);
17282 }
17283 cx.notify();
17284 }
17285
17286 pub fn replace_blocks(
17287 &mut self,
17288 renderers: HashMap<CustomBlockId, RenderBlock>,
17289 autoscroll: Option<Autoscroll>,
17290 cx: &mut Context<Self>,
17291 ) {
17292 self.display_map
17293 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17294 if let Some(autoscroll) = autoscroll {
17295 self.request_autoscroll(autoscroll, cx);
17296 }
17297 cx.notify();
17298 }
17299
17300 pub fn remove_blocks(
17301 &mut self,
17302 block_ids: HashSet<CustomBlockId>,
17303 autoscroll: Option<Autoscroll>,
17304 cx: &mut Context<Self>,
17305 ) {
17306 self.display_map.update(cx, |display_map, cx| {
17307 display_map.remove_blocks(block_ids, cx)
17308 });
17309 if let Some(autoscroll) = autoscroll {
17310 self.request_autoscroll(autoscroll, cx);
17311 }
17312 cx.notify();
17313 }
17314
17315 pub fn row_for_block(
17316 &self,
17317 block_id: CustomBlockId,
17318 cx: &mut Context<Self>,
17319 ) -> Option<DisplayRow> {
17320 self.display_map
17321 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17322 }
17323
17324 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17325 self.focused_block = Some(focused_block);
17326 }
17327
17328 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17329 self.focused_block.take()
17330 }
17331
17332 pub fn insert_creases(
17333 &mut self,
17334 creases: impl IntoIterator<Item = Crease<Anchor>>,
17335 cx: &mut Context<Self>,
17336 ) -> Vec<CreaseId> {
17337 self.display_map
17338 .update(cx, |map, cx| map.insert_creases(creases, cx))
17339 }
17340
17341 pub fn remove_creases(
17342 &mut self,
17343 ids: impl IntoIterator<Item = CreaseId>,
17344 cx: &mut Context<Self>,
17345 ) -> Vec<(CreaseId, Range<Anchor>)> {
17346 self.display_map
17347 .update(cx, |map, cx| map.remove_creases(ids, cx))
17348 }
17349
17350 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17351 self.display_map
17352 .update(cx, |map, cx| map.snapshot(cx))
17353 .longest_row()
17354 }
17355
17356 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17357 self.display_map
17358 .update(cx, |map, cx| map.snapshot(cx))
17359 .max_point()
17360 }
17361
17362 pub fn text(&self, cx: &App) -> String {
17363 self.buffer.read(cx).read(cx).text()
17364 }
17365
17366 pub fn is_empty(&self, cx: &App) -> bool {
17367 self.buffer.read(cx).read(cx).is_empty()
17368 }
17369
17370 pub fn text_option(&self, cx: &App) -> Option<String> {
17371 let text = self.text(cx);
17372 let text = text.trim();
17373
17374 if text.is_empty() {
17375 return None;
17376 }
17377
17378 Some(text.to_string())
17379 }
17380
17381 pub fn set_text(
17382 &mut self,
17383 text: impl Into<Arc<str>>,
17384 window: &mut Window,
17385 cx: &mut Context<Self>,
17386 ) {
17387 self.transact(window, cx, |this, _, cx| {
17388 this.buffer
17389 .read(cx)
17390 .as_singleton()
17391 .expect("you can only call set_text on editors for singleton buffers")
17392 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17393 });
17394 }
17395
17396 pub fn display_text(&self, cx: &mut App) -> String {
17397 self.display_map
17398 .update(cx, |map, cx| map.snapshot(cx))
17399 .text()
17400 }
17401
17402 fn create_minimap(
17403 &self,
17404 minimap_settings: MinimapSettings,
17405 window: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) -> Option<Entity<Self>> {
17408 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17409 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17410 }
17411
17412 fn initialize_new_minimap(
17413 &self,
17414 minimap_settings: MinimapSettings,
17415 window: &mut Window,
17416 cx: &mut Context<Self>,
17417 ) -> Entity<Self> {
17418 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17419
17420 let mut minimap = Editor::new_internal(
17421 EditorMode::Minimap {
17422 parent: cx.weak_entity(),
17423 },
17424 self.buffer.clone(),
17425 self.project.clone(),
17426 Some(self.display_map.clone()),
17427 window,
17428 cx,
17429 );
17430 minimap.scroll_manager.clone_state(&self.scroll_manager);
17431 minimap.set_text_style_refinement(TextStyleRefinement {
17432 font_size: Some(MINIMAP_FONT_SIZE),
17433 font_weight: Some(MINIMAP_FONT_WEIGHT),
17434 ..Default::default()
17435 });
17436 minimap.update_minimap_configuration(minimap_settings, cx);
17437 cx.new(|_| minimap)
17438 }
17439
17440 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17441 let current_line_highlight = minimap_settings
17442 .current_line_highlight
17443 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17444 self.set_current_line_highlight(Some(current_line_highlight));
17445 }
17446
17447 pub fn minimap(&self) -> Option<&Entity<Self>> {
17448 self.minimap
17449 .as_ref()
17450 .filter(|_| self.minimap_visibility.visible())
17451 }
17452
17453 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17454 let mut wrap_guides = smallvec![];
17455
17456 if self.show_wrap_guides == Some(false) {
17457 return wrap_guides;
17458 }
17459
17460 let settings = self.buffer.read(cx).language_settings(cx);
17461 if settings.show_wrap_guides {
17462 match self.soft_wrap_mode(cx) {
17463 SoftWrap::Column(soft_wrap) => {
17464 wrap_guides.push((soft_wrap as usize, true));
17465 }
17466 SoftWrap::Bounded(soft_wrap) => {
17467 wrap_guides.push((soft_wrap as usize, true));
17468 }
17469 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17470 }
17471 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17472 }
17473
17474 wrap_guides
17475 }
17476
17477 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17478 let settings = self.buffer.read(cx).language_settings(cx);
17479 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17480 match mode {
17481 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17482 SoftWrap::None
17483 }
17484 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17485 language_settings::SoftWrap::PreferredLineLength => {
17486 SoftWrap::Column(settings.preferred_line_length)
17487 }
17488 language_settings::SoftWrap::Bounded => {
17489 SoftWrap::Bounded(settings.preferred_line_length)
17490 }
17491 }
17492 }
17493
17494 pub fn set_soft_wrap_mode(
17495 &mut self,
17496 mode: language_settings::SoftWrap,
17497
17498 cx: &mut Context<Self>,
17499 ) {
17500 self.soft_wrap_mode_override = Some(mode);
17501 cx.notify();
17502 }
17503
17504 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17505 self.hard_wrap = hard_wrap;
17506 cx.notify();
17507 }
17508
17509 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17510 self.text_style_refinement = Some(style);
17511 }
17512
17513 /// called by the Element so we know what style we were most recently rendered with.
17514 pub(crate) fn set_style(
17515 &mut self,
17516 style: EditorStyle,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 // We intentionally do not inform the display map about the minimap style
17521 // so that wrapping is not recalculated and stays consistent for the editor
17522 // and its linked minimap.
17523 if !self.mode.is_minimap() {
17524 let rem_size = window.rem_size();
17525 self.display_map.update(cx, |map, cx| {
17526 map.set_font(
17527 style.text.font(),
17528 style.text.font_size.to_pixels(rem_size),
17529 cx,
17530 )
17531 });
17532 }
17533 self.style = Some(style);
17534 }
17535
17536 pub fn style(&self) -> Option<&EditorStyle> {
17537 self.style.as_ref()
17538 }
17539
17540 // Called by the element. This method is not designed to be called outside of the editor
17541 // element's layout code because it does not notify when rewrapping is computed synchronously.
17542 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17543 self.display_map
17544 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17545 }
17546
17547 pub fn set_soft_wrap(&mut self) {
17548 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17549 }
17550
17551 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17552 if self.soft_wrap_mode_override.is_some() {
17553 self.soft_wrap_mode_override.take();
17554 } else {
17555 let soft_wrap = match self.soft_wrap_mode(cx) {
17556 SoftWrap::GitDiff => return,
17557 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17558 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17559 language_settings::SoftWrap::None
17560 }
17561 };
17562 self.soft_wrap_mode_override = Some(soft_wrap);
17563 }
17564 cx.notify();
17565 }
17566
17567 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17568 let Some(workspace) = self.workspace() else {
17569 return;
17570 };
17571 let fs = workspace.read(cx).app_state().fs.clone();
17572 let current_show = TabBarSettings::get_global(cx).show;
17573 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17574 setting.show = Some(!current_show);
17575 });
17576 }
17577
17578 pub fn toggle_indent_guides(
17579 &mut self,
17580 _: &ToggleIndentGuides,
17581 _: &mut Window,
17582 cx: &mut Context<Self>,
17583 ) {
17584 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17585 self.buffer
17586 .read(cx)
17587 .language_settings(cx)
17588 .indent_guides
17589 .enabled
17590 });
17591 self.show_indent_guides = Some(!currently_enabled);
17592 cx.notify();
17593 }
17594
17595 fn should_show_indent_guides(&self) -> Option<bool> {
17596 self.show_indent_guides
17597 }
17598
17599 pub fn toggle_line_numbers(
17600 &mut self,
17601 _: &ToggleLineNumbers,
17602 _: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) {
17605 let mut editor_settings = EditorSettings::get_global(cx).clone();
17606 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17607 EditorSettings::override_global(editor_settings, cx);
17608 }
17609
17610 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17611 if let Some(show_line_numbers) = self.show_line_numbers {
17612 return show_line_numbers;
17613 }
17614 EditorSettings::get_global(cx).gutter.line_numbers
17615 }
17616
17617 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17618 self.use_relative_line_numbers
17619 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17620 }
17621
17622 pub fn toggle_relative_line_numbers(
17623 &mut self,
17624 _: &ToggleRelativeLineNumbers,
17625 _: &mut Window,
17626 cx: &mut Context<Self>,
17627 ) {
17628 let is_relative = self.should_use_relative_line_numbers(cx);
17629 self.set_relative_line_number(Some(!is_relative), cx)
17630 }
17631
17632 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17633 self.use_relative_line_numbers = is_relative;
17634 cx.notify();
17635 }
17636
17637 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17638 self.show_gutter = show_gutter;
17639 cx.notify();
17640 }
17641
17642 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17643 self.show_scrollbars = ScrollbarAxes {
17644 horizontal: show,
17645 vertical: show,
17646 };
17647 cx.notify();
17648 }
17649
17650 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17651 self.show_scrollbars.vertical = show;
17652 cx.notify();
17653 }
17654
17655 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17656 self.show_scrollbars.horizontal = show;
17657 cx.notify();
17658 }
17659
17660 pub fn set_minimap_visibility(
17661 &mut self,
17662 minimap_visibility: MinimapVisibility,
17663 window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) {
17666 if self.minimap_visibility != minimap_visibility {
17667 if minimap_visibility.visible() && self.minimap.is_none() {
17668 let minimap_settings = EditorSettings::get_global(cx).minimap;
17669 self.minimap =
17670 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17671 }
17672 self.minimap_visibility = minimap_visibility;
17673 cx.notify();
17674 }
17675 }
17676
17677 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17678 self.set_show_scrollbars(false, cx);
17679 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17680 }
17681
17682 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17683 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17684 }
17685
17686 /// Normally the text in full mode and auto height editors is padded on the
17687 /// left side by roughly half a character width for improved hit testing.
17688 ///
17689 /// Use this method to disable this for cases where this is not wanted (e.g.
17690 /// if you want to align the editor text with some other text above or below)
17691 /// or if you want to add this padding to single-line editors.
17692 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17693 self.offset_content = offset_content;
17694 cx.notify();
17695 }
17696
17697 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17698 self.show_line_numbers = Some(show_line_numbers);
17699 cx.notify();
17700 }
17701
17702 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17703 self.disable_expand_excerpt_buttons = true;
17704 cx.notify();
17705 }
17706
17707 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17708 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17709 cx.notify();
17710 }
17711
17712 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17713 self.show_code_actions = Some(show_code_actions);
17714 cx.notify();
17715 }
17716
17717 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17718 self.show_runnables = Some(show_runnables);
17719 cx.notify();
17720 }
17721
17722 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17723 self.show_breakpoints = Some(show_breakpoints);
17724 cx.notify();
17725 }
17726
17727 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17728 if self.display_map.read(cx).masked != masked {
17729 self.display_map.update(cx, |map, _| map.masked = masked);
17730 }
17731 cx.notify()
17732 }
17733
17734 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17735 self.show_wrap_guides = Some(show_wrap_guides);
17736 cx.notify();
17737 }
17738
17739 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17740 self.show_indent_guides = Some(show_indent_guides);
17741 cx.notify();
17742 }
17743
17744 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17745 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17746 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17747 if let Some(dir) = file.abs_path(cx).parent() {
17748 return Some(dir.to_owned());
17749 }
17750 }
17751
17752 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17753 return Some(project_path.path.to_path_buf());
17754 }
17755 }
17756
17757 None
17758 }
17759
17760 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17761 self.active_excerpt(cx)?
17762 .1
17763 .read(cx)
17764 .file()
17765 .and_then(|f| f.as_local())
17766 }
17767
17768 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17769 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17770 let buffer = buffer.read(cx);
17771 if let Some(project_path) = buffer.project_path(cx) {
17772 let project = self.project.as_ref()?.read(cx);
17773 project.absolute_path(&project_path, cx)
17774 } else {
17775 buffer
17776 .file()
17777 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17778 }
17779 })
17780 }
17781
17782 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17783 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17784 let project_path = buffer.read(cx).project_path(cx)?;
17785 let project = self.project.as_ref()?.read(cx);
17786 let entry = project.entry_for_path(&project_path, cx)?;
17787 let path = entry.path.to_path_buf();
17788 Some(path)
17789 })
17790 }
17791
17792 pub fn reveal_in_finder(
17793 &mut self,
17794 _: &RevealInFileManager,
17795 _window: &mut Window,
17796 cx: &mut Context<Self>,
17797 ) {
17798 if let Some(target) = self.target_file(cx) {
17799 cx.reveal_path(&target.abs_path(cx));
17800 }
17801 }
17802
17803 pub fn copy_path(
17804 &mut self,
17805 _: &zed_actions::workspace::CopyPath,
17806 _window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 if let Some(path) = self.target_file_abs_path(cx) {
17810 if let Some(path) = path.to_str() {
17811 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17812 }
17813 }
17814 }
17815
17816 pub fn copy_relative_path(
17817 &mut self,
17818 _: &zed_actions::workspace::CopyRelativePath,
17819 _window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 if let Some(path) = self.target_file_path(cx) {
17823 if let Some(path) = path.to_str() {
17824 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17825 }
17826 }
17827 }
17828
17829 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17830 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17831 buffer.read(cx).project_path(cx)
17832 } else {
17833 None
17834 }
17835 }
17836
17837 // Returns true if the editor handled a go-to-line request
17838 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17839 maybe!({
17840 let breakpoint_store = self.breakpoint_store.as_ref()?;
17841
17842 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17843 else {
17844 self.clear_row_highlights::<ActiveDebugLine>();
17845 return None;
17846 };
17847
17848 let position = active_stack_frame.position;
17849 let buffer_id = position.buffer_id?;
17850 let snapshot = self
17851 .project
17852 .as_ref()?
17853 .read(cx)
17854 .buffer_for_id(buffer_id, cx)?
17855 .read(cx)
17856 .snapshot();
17857
17858 let mut handled = false;
17859 for (id, ExcerptRange { context, .. }) in
17860 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17861 {
17862 if context.start.cmp(&position, &snapshot).is_ge()
17863 || context.end.cmp(&position, &snapshot).is_lt()
17864 {
17865 continue;
17866 }
17867 let snapshot = self.buffer.read(cx).snapshot(cx);
17868 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17869
17870 handled = true;
17871 self.clear_row_highlights::<ActiveDebugLine>();
17872
17873 self.go_to_line::<ActiveDebugLine>(
17874 multibuffer_anchor,
17875 Some(cx.theme().colors().editor_debugger_active_line_background),
17876 window,
17877 cx,
17878 );
17879
17880 cx.notify();
17881 }
17882
17883 handled.then_some(())
17884 })
17885 .is_some()
17886 }
17887
17888 pub fn copy_file_name_without_extension(
17889 &mut self,
17890 _: &CopyFileNameWithoutExtension,
17891 _: &mut Window,
17892 cx: &mut Context<Self>,
17893 ) {
17894 if let Some(file) = self.target_file(cx) {
17895 if let Some(file_stem) = file.path().file_stem() {
17896 if let Some(name) = file_stem.to_str() {
17897 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17898 }
17899 }
17900 }
17901 }
17902
17903 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17904 if let Some(file) = self.target_file(cx) {
17905 if let Some(file_name) = file.path().file_name() {
17906 if let Some(name) = file_name.to_str() {
17907 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17908 }
17909 }
17910 }
17911 }
17912
17913 pub fn toggle_git_blame(
17914 &mut self,
17915 _: &::git::Blame,
17916 window: &mut Window,
17917 cx: &mut Context<Self>,
17918 ) {
17919 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17920
17921 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17922 self.start_git_blame(true, window, cx);
17923 }
17924
17925 cx.notify();
17926 }
17927
17928 pub fn toggle_git_blame_inline(
17929 &mut self,
17930 _: &ToggleGitBlameInline,
17931 window: &mut Window,
17932 cx: &mut Context<Self>,
17933 ) {
17934 self.toggle_git_blame_inline_internal(true, window, cx);
17935 cx.notify();
17936 }
17937
17938 pub fn open_git_blame_commit(
17939 &mut self,
17940 _: &OpenGitBlameCommit,
17941 window: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) {
17944 self.open_git_blame_commit_internal(window, cx);
17945 }
17946
17947 fn open_git_blame_commit_internal(
17948 &mut self,
17949 window: &mut Window,
17950 cx: &mut Context<Self>,
17951 ) -> Option<()> {
17952 let blame = self.blame.as_ref()?;
17953 let snapshot = self.snapshot(window, cx);
17954 let cursor = self.selections.newest::<Point>(cx).head();
17955 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17956 let blame_entry = blame
17957 .update(cx, |blame, cx| {
17958 blame
17959 .blame_for_rows(
17960 &[RowInfo {
17961 buffer_id: Some(buffer.remote_id()),
17962 buffer_row: Some(point.row),
17963 ..Default::default()
17964 }],
17965 cx,
17966 )
17967 .next()
17968 })
17969 .flatten()?;
17970 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17971 let repo = blame.read(cx).repository(cx)?;
17972 let workspace = self.workspace()?.downgrade();
17973 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17974 None
17975 }
17976
17977 pub fn git_blame_inline_enabled(&self) -> bool {
17978 self.git_blame_inline_enabled
17979 }
17980
17981 pub fn toggle_selection_menu(
17982 &mut self,
17983 _: &ToggleSelectionMenu,
17984 _: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 self.show_selection_menu = self
17988 .show_selection_menu
17989 .map(|show_selections_menu| !show_selections_menu)
17990 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17991
17992 cx.notify();
17993 }
17994
17995 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17996 self.show_selection_menu
17997 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17998 }
17999
18000 fn start_git_blame(
18001 &mut self,
18002 user_triggered: bool,
18003 window: &mut Window,
18004 cx: &mut Context<Self>,
18005 ) {
18006 if let Some(project) = self.project.as_ref() {
18007 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18008 return;
18009 };
18010
18011 if buffer.read(cx).file().is_none() {
18012 return;
18013 }
18014
18015 let focused = self.focus_handle(cx).contains_focused(window, cx);
18016
18017 let project = project.clone();
18018 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18019 self.blame_subscription =
18020 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18021 self.blame = Some(blame);
18022 }
18023 }
18024
18025 fn toggle_git_blame_inline_internal(
18026 &mut self,
18027 user_triggered: bool,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) {
18031 if self.git_blame_inline_enabled {
18032 self.git_blame_inline_enabled = false;
18033 self.show_git_blame_inline = false;
18034 self.show_git_blame_inline_delay_task.take();
18035 } else {
18036 self.git_blame_inline_enabled = true;
18037 self.start_git_blame_inline(user_triggered, window, cx);
18038 }
18039
18040 cx.notify();
18041 }
18042
18043 fn start_git_blame_inline(
18044 &mut self,
18045 user_triggered: bool,
18046 window: &mut Window,
18047 cx: &mut Context<Self>,
18048 ) {
18049 self.start_git_blame(user_triggered, window, cx);
18050
18051 if ProjectSettings::get_global(cx)
18052 .git
18053 .inline_blame_delay()
18054 .is_some()
18055 {
18056 self.start_inline_blame_timer(window, cx);
18057 } else {
18058 self.show_git_blame_inline = true
18059 }
18060 }
18061
18062 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18063 self.blame.as_ref()
18064 }
18065
18066 pub fn show_git_blame_gutter(&self) -> bool {
18067 self.show_git_blame_gutter
18068 }
18069
18070 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18071 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18072 }
18073
18074 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18075 self.show_git_blame_inline
18076 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18077 && !self.newest_selection_head_on_empty_line(cx)
18078 && self.has_blame_entries(cx)
18079 }
18080
18081 fn has_blame_entries(&self, cx: &App) -> bool {
18082 self.blame()
18083 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18084 }
18085
18086 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18087 let cursor_anchor = self.selections.newest_anchor().head();
18088
18089 let snapshot = self.buffer.read(cx).snapshot(cx);
18090 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18091
18092 snapshot.line_len(buffer_row) == 0
18093 }
18094
18095 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18096 let buffer_and_selection = maybe!({
18097 let selection = self.selections.newest::<Point>(cx);
18098 let selection_range = selection.range();
18099
18100 let multi_buffer = self.buffer().read(cx);
18101 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18102 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18103
18104 let (buffer, range, _) = if selection.reversed {
18105 buffer_ranges.first()
18106 } else {
18107 buffer_ranges.last()
18108 }?;
18109
18110 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18111 ..text::ToPoint::to_point(&range.end, &buffer).row;
18112 Some((
18113 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18114 selection,
18115 ))
18116 });
18117
18118 let Some((buffer, selection)) = buffer_and_selection else {
18119 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18120 };
18121
18122 let Some(project) = self.project.as_ref() else {
18123 return Task::ready(Err(anyhow!("editor does not have project")));
18124 };
18125
18126 project.update(cx, |project, cx| {
18127 project.get_permalink_to_line(&buffer, selection, cx)
18128 })
18129 }
18130
18131 pub fn copy_permalink_to_line(
18132 &mut self,
18133 _: &CopyPermalinkToLine,
18134 window: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) {
18137 let permalink_task = self.get_permalink_to_line(cx);
18138 let workspace = self.workspace();
18139
18140 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18141 Ok(permalink) => {
18142 cx.update(|_, cx| {
18143 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18144 })
18145 .ok();
18146 }
18147 Err(err) => {
18148 let message = format!("Failed to copy permalink: {err}");
18149
18150 anyhow::Result::<()>::Err(err).log_err();
18151
18152 if let Some(workspace) = workspace {
18153 workspace
18154 .update_in(cx, |workspace, _, cx| {
18155 struct CopyPermalinkToLine;
18156
18157 workspace.show_toast(
18158 Toast::new(
18159 NotificationId::unique::<CopyPermalinkToLine>(),
18160 message,
18161 ),
18162 cx,
18163 )
18164 })
18165 .ok();
18166 }
18167 }
18168 })
18169 .detach();
18170 }
18171
18172 pub fn copy_file_location(
18173 &mut self,
18174 _: &CopyFileLocation,
18175 _: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) {
18178 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18179 if let Some(file) = self.target_file(cx) {
18180 if let Some(path) = file.path().to_str() {
18181 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18182 }
18183 }
18184 }
18185
18186 pub fn open_permalink_to_line(
18187 &mut self,
18188 _: &OpenPermalinkToLine,
18189 window: &mut Window,
18190 cx: &mut Context<Self>,
18191 ) {
18192 let permalink_task = self.get_permalink_to_line(cx);
18193 let workspace = self.workspace();
18194
18195 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18196 Ok(permalink) => {
18197 cx.update(|_, cx| {
18198 cx.open_url(permalink.as_ref());
18199 })
18200 .ok();
18201 }
18202 Err(err) => {
18203 let message = format!("Failed to open permalink: {err}");
18204
18205 anyhow::Result::<()>::Err(err).log_err();
18206
18207 if let Some(workspace) = workspace {
18208 workspace
18209 .update(cx, |workspace, cx| {
18210 struct OpenPermalinkToLine;
18211
18212 workspace.show_toast(
18213 Toast::new(
18214 NotificationId::unique::<OpenPermalinkToLine>(),
18215 message,
18216 ),
18217 cx,
18218 )
18219 })
18220 .ok();
18221 }
18222 }
18223 })
18224 .detach();
18225 }
18226
18227 pub fn insert_uuid_v4(
18228 &mut self,
18229 _: &InsertUuidV4,
18230 window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 self.insert_uuid(UuidVersion::V4, window, cx);
18234 }
18235
18236 pub fn insert_uuid_v7(
18237 &mut self,
18238 _: &InsertUuidV7,
18239 window: &mut Window,
18240 cx: &mut Context<Self>,
18241 ) {
18242 self.insert_uuid(UuidVersion::V7, window, cx);
18243 }
18244
18245 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18246 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18247 self.transact(window, cx, |this, window, cx| {
18248 let edits = this
18249 .selections
18250 .all::<Point>(cx)
18251 .into_iter()
18252 .map(|selection| {
18253 let uuid = match version {
18254 UuidVersion::V4 => uuid::Uuid::new_v4(),
18255 UuidVersion::V7 => uuid::Uuid::now_v7(),
18256 };
18257
18258 (selection.range(), uuid.to_string())
18259 });
18260 this.edit(edits, cx);
18261 this.refresh_inline_completion(true, false, window, cx);
18262 });
18263 }
18264
18265 pub fn open_selections_in_multibuffer(
18266 &mut self,
18267 _: &OpenSelectionsInMultibuffer,
18268 window: &mut Window,
18269 cx: &mut Context<Self>,
18270 ) {
18271 let multibuffer = self.buffer.read(cx);
18272
18273 let Some(buffer) = multibuffer.as_singleton() else {
18274 return;
18275 };
18276
18277 let Some(workspace) = self.workspace() else {
18278 return;
18279 };
18280
18281 let locations = self
18282 .selections
18283 .disjoint_anchors()
18284 .iter()
18285 .map(|range| Location {
18286 buffer: buffer.clone(),
18287 range: range.start.text_anchor..range.end.text_anchor,
18288 })
18289 .collect::<Vec<_>>();
18290
18291 let title = multibuffer.title(cx).to_string();
18292
18293 cx.spawn_in(window, async move |_, cx| {
18294 workspace.update_in(cx, |workspace, window, cx| {
18295 Self::open_locations_in_multibuffer(
18296 workspace,
18297 locations,
18298 format!("Selections for '{title}'"),
18299 false,
18300 MultibufferSelectionMode::All,
18301 window,
18302 cx,
18303 );
18304 })
18305 })
18306 .detach();
18307 }
18308
18309 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18310 /// last highlight added will be used.
18311 ///
18312 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18313 pub fn highlight_rows<T: 'static>(
18314 &mut self,
18315 range: Range<Anchor>,
18316 color: Hsla,
18317 options: RowHighlightOptions,
18318 cx: &mut Context<Self>,
18319 ) {
18320 let snapshot = self.buffer().read(cx).snapshot(cx);
18321 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18322 let ix = row_highlights.binary_search_by(|highlight| {
18323 Ordering::Equal
18324 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18325 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18326 });
18327
18328 if let Err(mut ix) = ix {
18329 let index = post_inc(&mut self.highlight_order);
18330
18331 // If this range intersects with the preceding highlight, then merge it with
18332 // the preceding highlight. Otherwise insert a new highlight.
18333 let mut merged = false;
18334 if ix > 0 {
18335 let prev_highlight = &mut row_highlights[ix - 1];
18336 if prev_highlight
18337 .range
18338 .end
18339 .cmp(&range.start, &snapshot)
18340 .is_ge()
18341 {
18342 ix -= 1;
18343 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18344 prev_highlight.range.end = range.end;
18345 }
18346 merged = true;
18347 prev_highlight.index = index;
18348 prev_highlight.color = color;
18349 prev_highlight.options = options;
18350 }
18351 }
18352
18353 if !merged {
18354 row_highlights.insert(
18355 ix,
18356 RowHighlight {
18357 range: range.clone(),
18358 index,
18359 color,
18360 options,
18361 type_id: TypeId::of::<T>(),
18362 },
18363 );
18364 }
18365
18366 // If any of the following highlights intersect with this one, merge them.
18367 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18368 let highlight = &row_highlights[ix];
18369 if next_highlight
18370 .range
18371 .start
18372 .cmp(&highlight.range.end, &snapshot)
18373 .is_le()
18374 {
18375 if next_highlight
18376 .range
18377 .end
18378 .cmp(&highlight.range.end, &snapshot)
18379 .is_gt()
18380 {
18381 row_highlights[ix].range.end = next_highlight.range.end;
18382 }
18383 row_highlights.remove(ix + 1);
18384 } else {
18385 break;
18386 }
18387 }
18388 }
18389 }
18390
18391 /// Remove any highlighted row ranges of the given type that intersect the
18392 /// given ranges.
18393 pub fn remove_highlighted_rows<T: 'static>(
18394 &mut self,
18395 ranges_to_remove: Vec<Range<Anchor>>,
18396 cx: &mut Context<Self>,
18397 ) {
18398 let snapshot = self.buffer().read(cx).snapshot(cx);
18399 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18400 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18401 row_highlights.retain(|highlight| {
18402 while let Some(range_to_remove) = ranges_to_remove.peek() {
18403 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18404 Ordering::Less | Ordering::Equal => {
18405 ranges_to_remove.next();
18406 }
18407 Ordering::Greater => {
18408 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18409 Ordering::Less | Ordering::Equal => {
18410 return false;
18411 }
18412 Ordering::Greater => break,
18413 }
18414 }
18415 }
18416 }
18417
18418 true
18419 })
18420 }
18421
18422 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18423 pub fn clear_row_highlights<T: 'static>(&mut self) {
18424 self.highlighted_rows.remove(&TypeId::of::<T>());
18425 }
18426
18427 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18428 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18429 self.highlighted_rows
18430 .get(&TypeId::of::<T>())
18431 .map_or(&[] as &[_], |vec| vec.as_slice())
18432 .iter()
18433 .map(|highlight| (highlight.range.clone(), highlight.color))
18434 }
18435
18436 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18437 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18438 /// Allows to ignore certain kinds of highlights.
18439 pub fn highlighted_display_rows(
18440 &self,
18441 window: &mut Window,
18442 cx: &mut App,
18443 ) -> BTreeMap<DisplayRow, LineHighlight> {
18444 let snapshot = self.snapshot(window, cx);
18445 let mut used_highlight_orders = HashMap::default();
18446 self.highlighted_rows
18447 .iter()
18448 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18449 .fold(
18450 BTreeMap::<DisplayRow, LineHighlight>::new(),
18451 |mut unique_rows, highlight| {
18452 let start = highlight.range.start.to_display_point(&snapshot);
18453 let end = highlight.range.end.to_display_point(&snapshot);
18454 let start_row = start.row().0;
18455 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18456 && end.column() == 0
18457 {
18458 end.row().0.saturating_sub(1)
18459 } else {
18460 end.row().0
18461 };
18462 for row in start_row..=end_row {
18463 let used_index =
18464 used_highlight_orders.entry(row).or_insert(highlight.index);
18465 if highlight.index >= *used_index {
18466 *used_index = highlight.index;
18467 unique_rows.insert(
18468 DisplayRow(row),
18469 LineHighlight {
18470 include_gutter: highlight.options.include_gutter,
18471 border: None,
18472 background: highlight.color.into(),
18473 type_id: Some(highlight.type_id),
18474 },
18475 );
18476 }
18477 }
18478 unique_rows
18479 },
18480 )
18481 }
18482
18483 pub fn highlighted_display_row_for_autoscroll(
18484 &self,
18485 snapshot: &DisplaySnapshot,
18486 ) -> Option<DisplayRow> {
18487 self.highlighted_rows
18488 .values()
18489 .flat_map(|highlighted_rows| highlighted_rows.iter())
18490 .filter_map(|highlight| {
18491 if highlight.options.autoscroll {
18492 Some(highlight.range.start.to_display_point(snapshot).row())
18493 } else {
18494 None
18495 }
18496 })
18497 .min()
18498 }
18499
18500 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18501 self.highlight_background::<SearchWithinRange>(
18502 ranges,
18503 |colors| colors.editor_document_highlight_read_background,
18504 cx,
18505 )
18506 }
18507
18508 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18509 self.breadcrumb_header = Some(new_header);
18510 }
18511
18512 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18513 self.clear_background_highlights::<SearchWithinRange>(cx);
18514 }
18515
18516 pub fn highlight_background<T: 'static>(
18517 &mut self,
18518 ranges: &[Range<Anchor>],
18519 color_fetcher: fn(&ThemeColors) -> Hsla,
18520 cx: &mut Context<Self>,
18521 ) {
18522 self.background_highlights
18523 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18524 self.scrollbar_marker_state.dirty = true;
18525 cx.notify();
18526 }
18527
18528 pub fn clear_background_highlights<T: 'static>(
18529 &mut self,
18530 cx: &mut Context<Self>,
18531 ) -> Option<BackgroundHighlight> {
18532 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18533 if !text_highlights.1.is_empty() {
18534 self.scrollbar_marker_state.dirty = true;
18535 cx.notify();
18536 }
18537 Some(text_highlights)
18538 }
18539
18540 pub fn highlight_gutter<T: 'static>(
18541 &mut self,
18542 ranges: impl Into<Vec<Range<Anchor>>>,
18543 color_fetcher: fn(&App) -> Hsla,
18544 cx: &mut Context<Self>,
18545 ) {
18546 self.gutter_highlights
18547 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18548 cx.notify();
18549 }
18550
18551 pub fn clear_gutter_highlights<T: 'static>(
18552 &mut self,
18553 cx: &mut Context<Self>,
18554 ) -> Option<GutterHighlight> {
18555 cx.notify();
18556 self.gutter_highlights.remove(&TypeId::of::<T>())
18557 }
18558
18559 pub fn insert_gutter_highlight<T: 'static>(
18560 &mut self,
18561 range: Range<Anchor>,
18562 color_fetcher: fn(&App) -> Hsla,
18563 cx: &mut Context<Self>,
18564 ) {
18565 let snapshot = self.buffer().read(cx).snapshot(cx);
18566 let mut highlights = self
18567 .gutter_highlights
18568 .remove(&TypeId::of::<T>())
18569 .map(|(_, highlights)| highlights)
18570 .unwrap_or_default();
18571 let ix = highlights.binary_search_by(|highlight| {
18572 Ordering::Equal
18573 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18574 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18575 });
18576 if let Err(ix) = ix {
18577 highlights.insert(ix, range);
18578 }
18579 self.gutter_highlights
18580 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18581 }
18582
18583 pub fn remove_gutter_highlights<T: 'static>(
18584 &mut self,
18585 ranges_to_remove: Vec<Range<Anchor>>,
18586 cx: &mut Context<Self>,
18587 ) {
18588 let snapshot = self.buffer().read(cx).snapshot(cx);
18589 let Some((color_fetcher, mut gutter_highlights)) =
18590 self.gutter_highlights.remove(&TypeId::of::<T>())
18591 else {
18592 return;
18593 };
18594 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18595 gutter_highlights.retain(|highlight| {
18596 while let Some(range_to_remove) = ranges_to_remove.peek() {
18597 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18598 Ordering::Less | Ordering::Equal => {
18599 ranges_to_remove.next();
18600 }
18601 Ordering::Greater => {
18602 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18603 Ordering::Less | Ordering::Equal => {
18604 return false;
18605 }
18606 Ordering::Greater => break,
18607 }
18608 }
18609 }
18610 }
18611
18612 true
18613 });
18614 self.gutter_highlights
18615 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18616 }
18617
18618 #[cfg(feature = "test-support")]
18619 pub fn all_text_background_highlights(
18620 &self,
18621 window: &mut Window,
18622 cx: &mut Context<Self>,
18623 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18624 let snapshot = self.snapshot(window, cx);
18625 let buffer = &snapshot.buffer_snapshot;
18626 let start = buffer.anchor_before(0);
18627 let end = buffer.anchor_after(buffer.len());
18628 let theme = cx.theme().colors();
18629 self.background_highlights_in_range(start..end, &snapshot, theme)
18630 }
18631
18632 #[cfg(feature = "test-support")]
18633 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18634 let snapshot = self.buffer().read(cx).snapshot(cx);
18635
18636 let highlights = self
18637 .background_highlights
18638 .get(&TypeId::of::<items::BufferSearchHighlights>());
18639
18640 if let Some((_color, ranges)) = highlights {
18641 ranges
18642 .iter()
18643 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18644 .collect_vec()
18645 } else {
18646 vec![]
18647 }
18648 }
18649
18650 fn document_highlights_for_position<'a>(
18651 &'a self,
18652 position: Anchor,
18653 buffer: &'a MultiBufferSnapshot,
18654 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18655 let read_highlights = self
18656 .background_highlights
18657 .get(&TypeId::of::<DocumentHighlightRead>())
18658 .map(|h| &h.1);
18659 let write_highlights = self
18660 .background_highlights
18661 .get(&TypeId::of::<DocumentHighlightWrite>())
18662 .map(|h| &h.1);
18663 let left_position = position.bias_left(buffer);
18664 let right_position = position.bias_right(buffer);
18665 read_highlights
18666 .into_iter()
18667 .chain(write_highlights)
18668 .flat_map(move |ranges| {
18669 let start_ix = match ranges.binary_search_by(|probe| {
18670 let cmp = probe.end.cmp(&left_position, buffer);
18671 if cmp.is_ge() {
18672 Ordering::Greater
18673 } else {
18674 Ordering::Less
18675 }
18676 }) {
18677 Ok(i) | Err(i) => i,
18678 };
18679
18680 ranges[start_ix..]
18681 .iter()
18682 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18683 })
18684 }
18685
18686 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18687 self.background_highlights
18688 .get(&TypeId::of::<T>())
18689 .map_or(false, |(_, highlights)| !highlights.is_empty())
18690 }
18691
18692 pub fn background_highlights_in_range(
18693 &self,
18694 search_range: Range<Anchor>,
18695 display_snapshot: &DisplaySnapshot,
18696 theme: &ThemeColors,
18697 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18698 let mut results = Vec::new();
18699 for (color_fetcher, ranges) in self.background_highlights.values() {
18700 let color = color_fetcher(theme);
18701 let start_ix = match ranges.binary_search_by(|probe| {
18702 let cmp = probe
18703 .end
18704 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18705 if cmp.is_gt() {
18706 Ordering::Greater
18707 } else {
18708 Ordering::Less
18709 }
18710 }) {
18711 Ok(i) | Err(i) => i,
18712 };
18713 for range in &ranges[start_ix..] {
18714 if range
18715 .start
18716 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18717 .is_ge()
18718 {
18719 break;
18720 }
18721
18722 let start = range.start.to_display_point(display_snapshot);
18723 let end = range.end.to_display_point(display_snapshot);
18724 results.push((start..end, color))
18725 }
18726 }
18727 results
18728 }
18729
18730 pub fn background_highlight_row_ranges<T: 'static>(
18731 &self,
18732 search_range: Range<Anchor>,
18733 display_snapshot: &DisplaySnapshot,
18734 count: usize,
18735 ) -> Vec<RangeInclusive<DisplayPoint>> {
18736 let mut results = Vec::new();
18737 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18738 return vec![];
18739 };
18740
18741 let start_ix = match ranges.binary_search_by(|probe| {
18742 let cmp = probe
18743 .end
18744 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18745 if cmp.is_gt() {
18746 Ordering::Greater
18747 } else {
18748 Ordering::Less
18749 }
18750 }) {
18751 Ok(i) | Err(i) => i,
18752 };
18753 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18754 if let (Some(start_display), Some(end_display)) = (start, end) {
18755 results.push(
18756 start_display.to_display_point(display_snapshot)
18757 ..=end_display.to_display_point(display_snapshot),
18758 );
18759 }
18760 };
18761 let mut start_row: Option<Point> = None;
18762 let mut end_row: Option<Point> = None;
18763 if ranges.len() > count {
18764 return Vec::new();
18765 }
18766 for range in &ranges[start_ix..] {
18767 if range
18768 .start
18769 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18770 .is_ge()
18771 {
18772 break;
18773 }
18774 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18775 if let Some(current_row) = &end_row {
18776 if end.row == current_row.row {
18777 continue;
18778 }
18779 }
18780 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18781 if start_row.is_none() {
18782 assert_eq!(end_row, None);
18783 start_row = Some(start);
18784 end_row = Some(end);
18785 continue;
18786 }
18787 if let Some(current_end) = end_row.as_mut() {
18788 if start.row > current_end.row + 1 {
18789 push_region(start_row, end_row);
18790 start_row = Some(start);
18791 end_row = Some(end);
18792 } else {
18793 // Merge two hunks.
18794 *current_end = end;
18795 }
18796 } else {
18797 unreachable!();
18798 }
18799 }
18800 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18801 push_region(start_row, end_row);
18802 results
18803 }
18804
18805 pub fn gutter_highlights_in_range(
18806 &self,
18807 search_range: Range<Anchor>,
18808 display_snapshot: &DisplaySnapshot,
18809 cx: &App,
18810 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18811 let mut results = Vec::new();
18812 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18813 let color = color_fetcher(cx);
18814 let start_ix = match ranges.binary_search_by(|probe| {
18815 let cmp = probe
18816 .end
18817 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18818 if cmp.is_gt() {
18819 Ordering::Greater
18820 } else {
18821 Ordering::Less
18822 }
18823 }) {
18824 Ok(i) | Err(i) => i,
18825 };
18826 for range in &ranges[start_ix..] {
18827 if range
18828 .start
18829 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18830 .is_ge()
18831 {
18832 break;
18833 }
18834
18835 let start = range.start.to_display_point(display_snapshot);
18836 let end = range.end.to_display_point(display_snapshot);
18837 results.push((start..end, color))
18838 }
18839 }
18840 results
18841 }
18842
18843 /// Get the text ranges corresponding to the redaction query
18844 pub fn redacted_ranges(
18845 &self,
18846 search_range: Range<Anchor>,
18847 display_snapshot: &DisplaySnapshot,
18848 cx: &App,
18849 ) -> Vec<Range<DisplayPoint>> {
18850 display_snapshot
18851 .buffer_snapshot
18852 .redacted_ranges(search_range, |file| {
18853 if let Some(file) = file {
18854 file.is_private()
18855 && EditorSettings::get(
18856 Some(SettingsLocation {
18857 worktree_id: file.worktree_id(cx),
18858 path: file.path().as_ref(),
18859 }),
18860 cx,
18861 )
18862 .redact_private_values
18863 } else {
18864 false
18865 }
18866 })
18867 .map(|range| {
18868 range.start.to_display_point(display_snapshot)
18869 ..range.end.to_display_point(display_snapshot)
18870 })
18871 .collect()
18872 }
18873
18874 pub fn highlight_text<T: 'static>(
18875 &mut self,
18876 ranges: Vec<Range<Anchor>>,
18877 style: HighlightStyle,
18878 cx: &mut Context<Self>,
18879 ) {
18880 self.display_map.update(cx, |map, _| {
18881 map.highlight_text(TypeId::of::<T>(), ranges, style)
18882 });
18883 cx.notify();
18884 }
18885
18886 pub(crate) fn highlight_inlays<T: 'static>(
18887 &mut self,
18888 highlights: Vec<InlayHighlight>,
18889 style: HighlightStyle,
18890 cx: &mut Context<Self>,
18891 ) {
18892 self.display_map.update(cx, |map, _| {
18893 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18894 });
18895 cx.notify();
18896 }
18897
18898 pub fn text_highlights<'a, T: 'static>(
18899 &'a self,
18900 cx: &'a App,
18901 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18902 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18903 }
18904
18905 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18906 let cleared = self
18907 .display_map
18908 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18909 if cleared {
18910 cx.notify();
18911 }
18912 }
18913
18914 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18915 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18916 && self.focus_handle.is_focused(window)
18917 }
18918
18919 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18920 self.show_cursor_when_unfocused = is_enabled;
18921 cx.notify();
18922 }
18923
18924 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18925 cx.notify();
18926 }
18927
18928 fn on_debug_session_event(
18929 &mut self,
18930 _session: Entity<Session>,
18931 event: &SessionEvent,
18932 cx: &mut Context<Self>,
18933 ) {
18934 match event {
18935 SessionEvent::InvalidateInlineValue => {
18936 self.refresh_inline_values(cx);
18937 }
18938 _ => {}
18939 }
18940 }
18941
18942 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18943 let Some(project) = self.project.clone() else {
18944 return;
18945 };
18946
18947 if !self.inline_value_cache.enabled {
18948 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18949 self.splice_inlays(&inlays, Vec::new(), cx);
18950 return;
18951 }
18952
18953 let current_execution_position = self
18954 .highlighted_rows
18955 .get(&TypeId::of::<ActiveDebugLine>())
18956 .and_then(|lines| lines.last().map(|line| line.range.start));
18957
18958 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18959 let inline_values = editor
18960 .update(cx, |editor, cx| {
18961 let Some(current_execution_position) = current_execution_position else {
18962 return Some(Task::ready(Ok(Vec::new())));
18963 };
18964
18965 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18966 let snapshot = buffer.snapshot(cx);
18967
18968 let excerpt = snapshot.excerpt_containing(
18969 current_execution_position..current_execution_position,
18970 )?;
18971
18972 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18973 })?;
18974
18975 let range =
18976 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18977
18978 project.inline_values(buffer, range, cx)
18979 })
18980 .ok()
18981 .flatten()?
18982 .await
18983 .context("refreshing debugger inlays")
18984 .log_err()?;
18985
18986 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18987
18988 for (buffer_id, inline_value) in inline_values
18989 .into_iter()
18990 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18991 {
18992 buffer_inline_values
18993 .entry(buffer_id)
18994 .or_default()
18995 .push(inline_value);
18996 }
18997
18998 editor
18999 .update(cx, |editor, cx| {
19000 let snapshot = editor.buffer.read(cx).snapshot(cx);
19001 let mut new_inlays = Vec::default();
19002
19003 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19004 let buffer_id = buffer_snapshot.remote_id();
19005 buffer_inline_values
19006 .get(&buffer_id)
19007 .into_iter()
19008 .flatten()
19009 .for_each(|hint| {
19010 let inlay = Inlay::debugger_hint(
19011 post_inc(&mut editor.next_inlay_id),
19012 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19013 hint.text(),
19014 );
19015
19016 new_inlays.push(inlay);
19017 });
19018 }
19019
19020 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19021 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19022
19023 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19024 })
19025 .ok()?;
19026 Some(())
19027 });
19028 }
19029
19030 fn on_buffer_event(
19031 &mut self,
19032 multibuffer: &Entity<MultiBuffer>,
19033 event: &multi_buffer::Event,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 match event {
19038 multi_buffer::Event::Edited {
19039 singleton_buffer_edited,
19040 edited_buffer,
19041 } => {
19042 self.scrollbar_marker_state.dirty = true;
19043 self.active_indent_guides_state.dirty = true;
19044 self.refresh_active_diagnostics(cx);
19045 self.refresh_code_actions(window, cx);
19046 self.refresh_selected_text_highlights(true, window, cx);
19047 refresh_matching_bracket_highlights(self, window, cx);
19048 if self.has_active_inline_completion() {
19049 self.update_visible_inline_completion(window, cx);
19050 }
19051 if let Some(project) = self.project.as_ref() {
19052 if let Some(edited_buffer) = edited_buffer {
19053 project.update(cx, |project, cx| {
19054 self.registered_buffers
19055 .entry(edited_buffer.read(cx).remote_id())
19056 .or_insert_with(|| {
19057 project
19058 .register_buffer_with_language_servers(&edited_buffer, cx)
19059 });
19060 });
19061 if edited_buffer.read(cx).file().is_some() {
19062 self.pull_diagnostics(
19063 Some(edited_buffer.read(cx).remote_id()),
19064 window,
19065 cx,
19066 );
19067 }
19068 }
19069 }
19070 cx.emit(EditorEvent::BufferEdited);
19071 cx.emit(SearchEvent::MatchesInvalidated);
19072 if *singleton_buffer_edited {
19073 if let Some(buffer) = edited_buffer {
19074 if buffer.read(cx).file().is_none() {
19075 cx.emit(EditorEvent::TitleChanged);
19076 }
19077 }
19078 if let Some(project) = &self.project {
19079 #[allow(clippy::mutable_key_type)]
19080 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19081 multibuffer
19082 .all_buffers()
19083 .into_iter()
19084 .filter_map(|buffer| {
19085 buffer.update(cx, |buffer, cx| {
19086 let language = buffer.language()?;
19087 let should_discard = project.update(cx, |project, cx| {
19088 project.is_local()
19089 && !project.has_language_servers_for(buffer, cx)
19090 });
19091 should_discard.not().then_some(language.clone())
19092 })
19093 })
19094 .collect::<HashSet<_>>()
19095 });
19096 if !languages_affected.is_empty() {
19097 self.refresh_inlay_hints(
19098 InlayHintRefreshReason::BufferEdited(languages_affected),
19099 cx,
19100 );
19101 }
19102 }
19103 }
19104
19105 let Some(project) = &self.project else { return };
19106 let (telemetry, is_via_ssh) = {
19107 let project = project.read(cx);
19108 let telemetry = project.client().telemetry().clone();
19109 let is_via_ssh = project.is_via_ssh();
19110 (telemetry, is_via_ssh)
19111 };
19112 refresh_linked_ranges(self, window, cx);
19113 telemetry.log_edit_event("editor", is_via_ssh);
19114 }
19115 multi_buffer::Event::ExcerptsAdded {
19116 buffer,
19117 predecessor,
19118 excerpts,
19119 } => {
19120 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19121 let buffer_id = buffer.read(cx).remote_id();
19122 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19123 if let Some(project) = &self.project {
19124 update_uncommitted_diff_for_buffer(
19125 cx.entity(),
19126 project,
19127 [buffer.clone()],
19128 self.buffer.clone(),
19129 cx,
19130 )
19131 .detach();
19132 }
19133 }
19134 cx.emit(EditorEvent::ExcerptsAdded {
19135 buffer: buffer.clone(),
19136 predecessor: *predecessor,
19137 excerpts: excerpts.clone(),
19138 });
19139 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19140 }
19141 multi_buffer::Event::ExcerptsRemoved {
19142 ids,
19143 removed_buffer_ids,
19144 } => {
19145 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19146 let buffer = self.buffer.read(cx);
19147 self.registered_buffers
19148 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19149 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19150 cx.emit(EditorEvent::ExcerptsRemoved {
19151 ids: ids.clone(),
19152 removed_buffer_ids: removed_buffer_ids.clone(),
19153 })
19154 }
19155 multi_buffer::Event::ExcerptsEdited {
19156 excerpt_ids,
19157 buffer_ids,
19158 } => {
19159 self.display_map.update(cx, |map, cx| {
19160 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19161 });
19162 cx.emit(EditorEvent::ExcerptsEdited {
19163 ids: excerpt_ids.clone(),
19164 })
19165 }
19166 multi_buffer::Event::ExcerptsExpanded { ids } => {
19167 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19168 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19169 }
19170 multi_buffer::Event::Reparsed(buffer_id) => {
19171 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19172 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19173
19174 cx.emit(EditorEvent::Reparsed(*buffer_id));
19175 }
19176 multi_buffer::Event::DiffHunksToggled => {
19177 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19178 }
19179 multi_buffer::Event::LanguageChanged(buffer_id) => {
19180 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19181 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19182 cx.emit(EditorEvent::Reparsed(*buffer_id));
19183 cx.notify();
19184 }
19185 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19186 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19187 multi_buffer::Event::FileHandleChanged
19188 | multi_buffer::Event::Reloaded
19189 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19190 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19191 multi_buffer::Event::DiagnosticsUpdated => {
19192 self.update_diagnostics_state(window, cx);
19193 }
19194 _ => {}
19195 };
19196 }
19197
19198 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19199 self.refresh_active_diagnostics(cx);
19200 self.refresh_inline_diagnostics(true, window, cx);
19201 self.scrollbar_marker_state.dirty = true;
19202 cx.notify();
19203 }
19204
19205 pub fn start_temporary_diff_override(&mut self) {
19206 self.load_diff_task.take();
19207 self.temporary_diff_override = true;
19208 }
19209
19210 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19211 self.temporary_diff_override = false;
19212 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19213 self.buffer.update(cx, |buffer, cx| {
19214 buffer.set_all_diff_hunks_collapsed(cx);
19215 });
19216
19217 if let Some(project) = self.project.clone() {
19218 self.load_diff_task = Some(
19219 update_uncommitted_diff_for_buffer(
19220 cx.entity(),
19221 &project,
19222 self.buffer.read(cx).all_buffers(),
19223 self.buffer.clone(),
19224 cx,
19225 )
19226 .shared(),
19227 );
19228 }
19229 }
19230
19231 fn on_display_map_changed(
19232 &mut self,
19233 _: Entity<DisplayMap>,
19234 _: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 cx.notify();
19238 }
19239
19240 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19241 let new_severity = if self.diagnostics_enabled() {
19242 EditorSettings::get_global(cx)
19243 .diagnostics_max_severity
19244 .unwrap_or(DiagnosticSeverity::Hint)
19245 } else {
19246 DiagnosticSeverity::Off
19247 };
19248 self.set_max_diagnostics_severity(new_severity, cx);
19249 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19250 self.update_edit_prediction_settings(cx);
19251 self.refresh_inline_completion(true, false, window, cx);
19252 self.refresh_inlay_hints(
19253 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19254 self.selections.newest_anchor().head(),
19255 &self.buffer.read(cx).snapshot(cx),
19256 cx,
19257 )),
19258 cx,
19259 );
19260
19261 let old_cursor_shape = self.cursor_shape;
19262
19263 {
19264 let editor_settings = EditorSettings::get_global(cx);
19265 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19266 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19267 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19268 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19269 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19270 }
19271
19272 if old_cursor_shape != self.cursor_shape {
19273 cx.emit(EditorEvent::CursorShapeChanged);
19274 }
19275
19276 let project_settings = ProjectSettings::get_global(cx);
19277 self.serialize_dirty_buffers =
19278 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19279
19280 if self.mode.is_full() {
19281 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19282 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19283 if self.show_inline_diagnostics != show_inline_diagnostics {
19284 self.show_inline_diagnostics = show_inline_diagnostics;
19285 self.refresh_inline_diagnostics(false, window, cx);
19286 }
19287
19288 if self.git_blame_inline_enabled != inline_blame_enabled {
19289 self.toggle_git_blame_inline_internal(false, window, cx);
19290 }
19291
19292 let minimap_settings = EditorSettings::get_global(cx).minimap;
19293 if self.minimap_visibility != MinimapVisibility::Disabled {
19294 if self.minimap_visibility.settings_visibility()
19295 != minimap_settings.minimap_enabled()
19296 {
19297 self.set_minimap_visibility(
19298 MinimapVisibility::for_mode(self.mode(), cx),
19299 window,
19300 cx,
19301 );
19302 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19303 minimap_entity.update(cx, |minimap_editor, cx| {
19304 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19305 })
19306 }
19307 }
19308 }
19309
19310 cx.notify();
19311 }
19312
19313 pub fn set_searchable(&mut self, searchable: bool) {
19314 self.searchable = searchable;
19315 }
19316
19317 pub fn searchable(&self) -> bool {
19318 self.searchable
19319 }
19320
19321 fn open_proposed_changes_editor(
19322 &mut self,
19323 _: &OpenProposedChangesEditor,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) {
19327 let Some(workspace) = self.workspace() else {
19328 cx.propagate();
19329 return;
19330 };
19331
19332 let selections = self.selections.all::<usize>(cx);
19333 let multi_buffer = self.buffer.read(cx);
19334 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19335 let mut new_selections_by_buffer = HashMap::default();
19336 for selection in selections {
19337 for (buffer, range, _) in
19338 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19339 {
19340 let mut range = range.to_point(buffer);
19341 range.start.column = 0;
19342 range.end.column = buffer.line_len(range.end.row);
19343 new_selections_by_buffer
19344 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19345 .or_insert(Vec::new())
19346 .push(range)
19347 }
19348 }
19349
19350 let proposed_changes_buffers = new_selections_by_buffer
19351 .into_iter()
19352 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19353 .collect::<Vec<_>>();
19354 let proposed_changes_editor = cx.new(|cx| {
19355 ProposedChangesEditor::new(
19356 "Proposed changes",
19357 proposed_changes_buffers,
19358 self.project.clone(),
19359 window,
19360 cx,
19361 )
19362 });
19363
19364 window.defer(cx, move |window, cx| {
19365 workspace.update(cx, |workspace, cx| {
19366 workspace.active_pane().update(cx, |pane, cx| {
19367 pane.add_item(
19368 Box::new(proposed_changes_editor),
19369 true,
19370 true,
19371 None,
19372 window,
19373 cx,
19374 );
19375 });
19376 });
19377 });
19378 }
19379
19380 pub fn open_excerpts_in_split(
19381 &mut self,
19382 _: &OpenExcerptsSplit,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 self.open_excerpts_common(None, true, window, cx)
19387 }
19388
19389 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19390 self.open_excerpts_common(None, false, window, cx)
19391 }
19392
19393 fn open_excerpts_common(
19394 &mut self,
19395 jump_data: Option<JumpData>,
19396 split: bool,
19397 window: &mut Window,
19398 cx: &mut Context<Self>,
19399 ) {
19400 let Some(workspace) = self.workspace() else {
19401 cx.propagate();
19402 return;
19403 };
19404
19405 if self.buffer.read(cx).is_singleton() {
19406 cx.propagate();
19407 return;
19408 }
19409
19410 let mut new_selections_by_buffer = HashMap::default();
19411 match &jump_data {
19412 Some(JumpData::MultiBufferPoint {
19413 excerpt_id,
19414 position,
19415 anchor,
19416 line_offset_from_top,
19417 }) => {
19418 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19419 if let Some(buffer) = multi_buffer_snapshot
19420 .buffer_id_for_excerpt(*excerpt_id)
19421 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19422 {
19423 let buffer_snapshot = buffer.read(cx).snapshot();
19424 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19425 language::ToPoint::to_point(anchor, &buffer_snapshot)
19426 } else {
19427 buffer_snapshot.clip_point(*position, Bias::Left)
19428 };
19429 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19430 new_selections_by_buffer.insert(
19431 buffer,
19432 (
19433 vec![jump_to_offset..jump_to_offset],
19434 Some(*line_offset_from_top),
19435 ),
19436 );
19437 }
19438 }
19439 Some(JumpData::MultiBufferRow {
19440 row,
19441 line_offset_from_top,
19442 }) => {
19443 let point = MultiBufferPoint::new(row.0, 0);
19444 if let Some((buffer, buffer_point, _)) =
19445 self.buffer.read(cx).point_to_buffer_point(point, cx)
19446 {
19447 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19448 new_selections_by_buffer
19449 .entry(buffer)
19450 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19451 .0
19452 .push(buffer_offset..buffer_offset)
19453 }
19454 }
19455 None => {
19456 let selections = self.selections.all::<usize>(cx);
19457 let multi_buffer = self.buffer.read(cx);
19458 for selection in selections {
19459 for (snapshot, range, _, anchor) in multi_buffer
19460 .snapshot(cx)
19461 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19462 {
19463 if let Some(anchor) = anchor {
19464 // selection is in a deleted hunk
19465 let Some(buffer_id) = anchor.buffer_id else {
19466 continue;
19467 };
19468 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19469 continue;
19470 };
19471 let offset = text::ToOffset::to_offset(
19472 &anchor.text_anchor,
19473 &buffer_handle.read(cx).snapshot(),
19474 );
19475 let range = offset..offset;
19476 new_selections_by_buffer
19477 .entry(buffer_handle)
19478 .or_insert((Vec::new(), None))
19479 .0
19480 .push(range)
19481 } else {
19482 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19483 else {
19484 continue;
19485 };
19486 new_selections_by_buffer
19487 .entry(buffer_handle)
19488 .or_insert((Vec::new(), None))
19489 .0
19490 .push(range)
19491 }
19492 }
19493 }
19494 }
19495 }
19496
19497 new_selections_by_buffer
19498 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19499
19500 if new_selections_by_buffer.is_empty() {
19501 return;
19502 }
19503
19504 // We defer the pane interaction because we ourselves are a workspace item
19505 // and activating a new item causes the pane to call a method on us reentrantly,
19506 // which panics if we're on the stack.
19507 window.defer(cx, move |window, cx| {
19508 workspace.update(cx, |workspace, cx| {
19509 let pane = if split {
19510 workspace.adjacent_pane(window, cx)
19511 } else {
19512 workspace.active_pane().clone()
19513 };
19514
19515 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19516 let editor = buffer
19517 .read(cx)
19518 .file()
19519 .is_none()
19520 .then(|| {
19521 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19522 // so `workspace.open_project_item` will never find them, always opening a new editor.
19523 // Instead, we try to activate the existing editor in the pane first.
19524 let (editor, pane_item_index) =
19525 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19526 let editor = item.downcast::<Editor>()?;
19527 let singleton_buffer =
19528 editor.read(cx).buffer().read(cx).as_singleton()?;
19529 if singleton_buffer == buffer {
19530 Some((editor, i))
19531 } else {
19532 None
19533 }
19534 })?;
19535 pane.update(cx, |pane, cx| {
19536 pane.activate_item(pane_item_index, true, true, window, cx)
19537 });
19538 Some(editor)
19539 })
19540 .flatten()
19541 .unwrap_or_else(|| {
19542 workspace.open_project_item::<Self>(
19543 pane.clone(),
19544 buffer,
19545 true,
19546 true,
19547 window,
19548 cx,
19549 )
19550 });
19551
19552 editor.update(cx, |editor, cx| {
19553 let autoscroll = match scroll_offset {
19554 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19555 None => Autoscroll::newest(),
19556 };
19557 let nav_history = editor.nav_history.take();
19558 editor.change_selections(Some(autoscroll), window, cx, |s| {
19559 s.select_ranges(ranges);
19560 });
19561 editor.nav_history = nav_history;
19562 });
19563 }
19564 })
19565 });
19566 }
19567
19568 // For now, don't allow opening excerpts in buffers that aren't backed by
19569 // regular project files.
19570 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19571 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19572 }
19573
19574 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19575 let snapshot = self.buffer.read(cx).read(cx);
19576 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19577 Some(
19578 ranges
19579 .iter()
19580 .map(move |range| {
19581 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19582 })
19583 .collect(),
19584 )
19585 }
19586
19587 fn selection_replacement_ranges(
19588 &self,
19589 range: Range<OffsetUtf16>,
19590 cx: &mut App,
19591 ) -> Vec<Range<OffsetUtf16>> {
19592 let selections = self.selections.all::<OffsetUtf16>(cx);
19593 let newest_selection = selections
19594 .iter()
19595 .max_by_key(|selection| selection.id)
19596 .unwrap();
19597 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19598 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19599 let snapshot = self.buffer.read(cx).read(cx);
19600 selections
19601 .into_iter()
19602 .map(|mut selection| {
19603 selection.start.0 =
19604 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19605 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19606 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19607 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19608 })
19609 .collect()
19610 }
19611
19612 fn report_editor_event(
19613 &self,
19614 event_type: &'static str,
19615 file_extension: Option<String>,
19616 cx: &App,
19617 ) {
19618 if cfg!(any(test, feature = "test-support")) {
19619 return;
19620 }
19621
19622 let Some(project) = &self.project else { return };
19623
19624 // If None, we are in a file without an extension
19625 let file = self
19626 .buffer
19627 .read(cx)
19628 .as_singleton()
19629 .and_then(|b| b.read(cx).file());
19630 let file_extension = file_extension.or(file
19631 .as_ref()
19632 .and_then(|file| Path::new(file.file_name(cx)).extension())
19633 .and_then(|e| e.to_str())
19634 .map(|a| a.to_string()));
19635
19636 let vim_mode = vim_enabled(cx);
19637
19638 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19639 let copilot_enabled = edit_predictions_provider
19640 == language::language_settings::EditPredictionProvider::Copilot;
19641 let copilot_enabled_for_language = self
19642 .buffer
19643 .read(cx)
19644 .language_settings(cx)
19645 .show_edit_predictions;
19646
19647 let project = project.read(cx);
19648 telemetry::event!(
19649 event_type,
19650 file_extension,
19651 vim_mode,
19652 copilot_enabled,
19653 copilot_enabled_for_language,
19654 edit_predictions_provider,
19655 is_via_ssh = project.is_via_ssh(),
19656 );
19657 }
19658
19659 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19660 /// with each line being an array of {text, highlight} objects.
19661 fn copy_highlight_json(
19662 &mut self,
19663 _: &CopyHighlightJson,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 #[derive(Serialize)]
19668 struct Chunk<'a> {
19669 text: String,
19670 highlight: Option<&'a str>,
19671 }
19672
19673 let snapshot = self.buffer.read(cx).snapshot(cx);
19674 let range = self
19675 .selected_text_range(false, window, cx)
19676 .and_then(|selection| {
19677 if selection.range.is_empty() {
19678 None
19679 } else {
19680 Some(selection.range)
19681 }
19682 })
19683 .unwrap_or_else(|| 0..snapshot.len());
19684
19685 let chunks = snapshot.chunks(range, true);
19686 let mut lines = Vec::new();
19687 let mut line: VecDeque<Chunk> = VecDeque::new();
19688
19689 let Some(style) = self.style.as_ref() else {
19690 return;
19691 };
19692
19693 for chunk in chunks {
19694 let highlight = chunk
19695 .syntax_highlight_id
19696 .and_then(|id| id.name(&style.syntax));
19697 let mut chunk_lines = chunk.text.split('\n').peekable();
19698 while let Some(text) = chunk_lines.next() {
19699 let mut merged_with_last_token = false;
19700 if let Some(last_token) = line.back_mut() {
19701 if last_token.highlight == highlight {
19702 last_token.text.push_str(text);
19703 merged_with_last_token = true;
19704 }
19705 }
19706
19707 if !merged_with_last_token {
19708 line.push_back(Chunk {
19709 text: text.into(),
19710 highlight,
19711 });
19712 }
19713
19714 if chunk_lines.peek().is_some() {
19715 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19716 line.pop_front();
19717 }
19718 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19719 line.pop_back();
19720 }
19721
19722 lines.push(mem::take(&mut line));
19723 }
19724 }
19725 }
19726
19727 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19728 return;
19729 };
19730 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19731 }
19732
19733 pub fn open_context_menu(
19734 &mut self,
19735 _: &OpenContextMenu,
19736 window: &mut Window,
19737 cx: &mut Context<Self>,
19738 ) {
19739 self.request_autoscroll(Autoscroll::newest(), cx);
19740 let position = self.selections.newest_display(cx).start;
19741 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19742 }
19743
19744 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19745 &self.inlay_hint_cache
19746 }
19747
19748 pub fn replay_insert_event(
19749 &mut self,
19750 text: &str,
19751 relative_utf16_range: Option<Range<isize>>,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 if !self.input_enabled {
19756 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19757 return;
19758 }
19759 if let Some(relative_utf16_range) = relative_utf16_range {
19760 let selections = self.selections.all::<OffsetUtf16>(cx);
19761 self.change_selections(None, window, cx, |s| {
19762 let new_ranges = selections.into_iter().map(|range| {
19763 let start = OffsetUtf16(
19764 range
19765 .head()
19766 .0
19767 .saturating_add_signed(relative_utf16_range.start),
19768 );
19769 let end = OffsetUtf16(
19770 range
19771 .head()
19772 .0
19773 .saturating_add_signed(relative_utf16_range.end),
19774 );
19775 start..end
19776 });
19777 s.select_ranges(new_ranges);
19778 });
19779 }
19780
19781 self.handle_input(text, window, cx);
19782 }
19783
19784 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19785 let Some(provider) = self.semantics_provider.as_ref() else {
19786 return false;
19787 };
19788
19789 let mut supports = false;
19790 self.buffer().update(cx, |this, cx| {
19791 this.for_each_buffer(|buffer| {
19792 supports |= provider.supports_inlay_hints(buffer, cx);
19793 });
19794 });
19795
19796 supports
19797 }
19798
19799 pub fn is_focused(&self, window: &Window) -> bool {
19800 self.focus_handle.is_focused(window)
19801 }
19802
19803 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19804 cx.emit(EditorEvent::Focused);
19805
19806 if let Some(descendant) = self
19807 .last_focused_descendant
19808 .take()
19809 .and_then(|descendant| descendant.upgrade())
19810 {
19811 window.focus(&descendant);
19812 } else {
19813 if let Some(blame) = self.blame.as_ref() {
19814 blame.update(cx, GitBlame::focus)
19815 }
19816
19817 self.blink_manager.update(cx, BlinkManager::enable);
19818 self.show_cursor_names(window, cx);
19819 self.buffer.update(cx, |buffer, cx| {
19820 buffer.finalize_last_transaction(cx);
19821 if self.leader_id.is_none() {
19822 buffer.set_active_selections(
19823 &self.selections.disjoint_anchors(),
19824 self.selections.line_mode,
19825 self.cursor_shape,
19826 cx,
19827 );
19828 }
19829 });
19830 }
19831 }
19832
19833 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19834 cx.emit(EditorEvent::FocusedIn)
19835 }
19836
19837 fn handle_focus_out(
19838 &mut self,
19839 event: FocusOutEvent,
19840 _window: &mut Window,
19841 cx: &mut Context<Self>,
19842 ) {
19843 if event.blurred != self.focus_handle {
19844 self.last_focused_descendant = Some(event.blurred);
19845 }
19846 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19847 }
19848
19849 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19850 self.blink_manager.update(cx, BlinkManager::disable);
19851 self.buffer
19852 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19853
19854 if let Some(blame) = self.blame.as_ref() {
19855 blame.update(cx, GitBlame::blur)
19856 }
19857 if !self.hover_state.focused(window, cx) {
19858 hide_hover(self, cx);
19859 }
19860 if !self
19861 .context_menu
19862 .borrow()
19863 .as_ref()
19864 .is_some_and(|context_menu| context_menu.focused(window, cx))
19865 {
19866 self.hide_context_menu(window, cx);
19867 }
19868 self.discard_inline_completion(false, cx);
19869 cx.emit(EditorEvent::Blurred);
19870 cx.notify();
19871 }
19872
19873 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19874 let mut pending: String = window
19875 .pending_input_keystrokes()
19876 .into_iter()
19877 .flatten()
19878 .filter_map(|keystroke| {
19879 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19880 keystroke.key_char.clone()
19881 } else {
19882 None
19883 }
19884 })
19885 .collect();
19886
19887 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19888 pending = "".to_string();
19889 }
19890
19891 let existing_pending = self
19892 .text_highlights::<PendingInput>(cx)
19893 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19894 if existing_pending.is_none() && pending.is_empty() {
19895 return;
19896 }
19897 let transaction =
19898 self.transact(window, cx, |this, window, cx| {
19899 let selections = this.selections.all::<usize>(cx);
19900 let edits = selections
19901 .iter()
19902 .map(|selection| (selection.end..selection.end, pending.clone()));
19903 this.edit(edits, cx);
19904 this.change_selections(None, window, cx, |s| {
19905 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19906 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19907 }));
19908 });
19909 if let Some(existing_ranges) = existing_pending {
19910 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19911 this.edit(edits, cx);
19912 }
19913 });
19914
19915 let snapshot = self.snapshot(window, cx);
19916 let ranges = self
19917 .selections
19918 .all::<usize>(cx)
19919 .into_iter()
19920 .map(|selection| {
19921 snapshot.buffer_snapshot.anchor_after(selection.end)
19922 ..snapshot
19923 .buffer_snapshot
19924 .anchor_before(selection.end + pending.len())
19925 })
19926 .collect();
19927
19928 if pending.is_empty() {
19929 self.clear_highlights::<PendingInput>(cx);
19930 } else {
19931 self.highlight_text::<PendingInput>(
19932 ranges,
19933 HighlightStyle {
19934 underline: Some(UnderlineStyle {
19935 thickness: px(1.),
19936 color: None,
19937 wavy: false,
19938 }),
19939 ..Default::default()
19940 },
19941 cx,
19942 );
19943 }
19944
19945 self.ime_transaction = self.ime_transaction.or(transaction);
19946 if let Some(transaction) = self.ime_transaction {
19947 self.buffer.update(cx, |buffer, cx| {
19948 buffer.group_until_transaction(transaction, cx);
19949 });
19950 }
19951
19952 if self.text_highlights::<PendingInput>(cx).is_none() {
19953 self.ime_transaction.take();
19954 }
19955 }
19956
19957 pub fn register_action_renderer(
19958 &mut self,
19959 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19960 ) -> Subscription {
19961 let id = self.next_editor_action_id.post_inc();
19962 self.editor_actions
19963 .borrow_mut()
19964 .insert(id, Box::new(listener));
19965
19966 let editor_actions = self.editor_actions.clone();
19967 Subscription::new(move || {
19968 editor_actions.borrow_mut().remove(&id);
19969 })
19970 }
19971
19972 pub fn register_action<A: Action>(
19973 &mut self,
19974 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19975 ) -> Subscription {
19976 let id = self.next_editor_action_id.post_inc();
19977 let listener = Arc::new(listener);
19978 self.editor_actions.borrow_mut().insert(
19979 id,
19980 Box::new(move |_, window, _| {
19981 let listener = listener.clone();
19982 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19983 let action = action.downcast_ref().unwrap();
19984 if phase == DispatchPhase::Bubble {
19985 listener(action, window, cx)
19986 }
19987 })
19988 }),
19989 );
19990
19991 let editor_actions = self.editor_actions.clone();
19992 Subscription::new(move || {
19993 editor_actions.borrow_mut().remove(&id);
19994 })
19995 }
19996
19997 pub fn file_header_size(&self) -> u32 {
19998 FILE_HEADER_HEIGHT
19999 }
20000
20001 pub fn restore(
20002 &mut self,
20003 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20004 window: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 let workspace = self.workspace();
20008 let project = self.project.as_ref();
20009 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20010 let mut tasks = Vec::new();
20011 for (buffer_id, changes) in revert_changes {
20012 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20013 buffer.update(cx, |buffer, cx| {
20014 buffer.edit(
20015 changes
20016 .into_iter()
20017 .map(|(range, text)| (range, text.to_string())),
20018 None,
20019 cx,
20020 );
20021 });
20022
20023 if let Some(project) =
20024 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20025 {
20026 project.update(cx, |project, cx| {
20027 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20028 })
20029 }
20030 }
20031 }
20032 tasks
20033 });
20034 cx.spawn_in(window, async move |_, cx| {
20035 for (buffer, task) in save_tasks {
20036 let result = task.await;
20037 if result.is_err() {
20038 let Some(path) = buffer
20039 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20040 .ok()
20041 else {
20042 continue;
20043 };
20044 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20045 let Some(task) = cx
20046 .update_window_entity(&workspace, |workspace, window, cx| {
20047 workspace
20048 .open_path_preview(path, None, false, false, false, window, cx)
20049 })
20050 .ok()
20051 else {
20052 continue;
20053 };
20054 task.await.log_err();
20055 }
20056 }
20057 }
20058 })
20059 .detach();
20060 self.change_selections(None, window, cx, |selections| selections.refresh());
20061 }
20062
20063 pub fn to_pixel_point(
20064 &self,
20065 source: multi_buffer::Anchor,
20066 editor_snapshot: &EditorSnapshot,
20067 window: &mut Window,
20068 ) -> Option<gpui::Point<Pixels>> {
20069 let source_point = source.to_display_point(editor_snapshot);
20070 self.display_to_pixel_point(source_point, editor_snapshot, window)
20071 }
20072
20073 pub fn display_to_pixel_point(
20074 &self,
20075 source: DisplayPoint,
20076 editor_snapshot: &EditorSnapshot,
20077 window: &mut Window,
20078 ) -> Option<gpui::Point<Pixels>> {
20079 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20080 let text_layout_details = self.text_layout_details(window);
20081 let scroll_top = text_layout_details
20082 .scroll_anchor
20083 .scroll_position(editor_snapshot)
20084 .y;
20085
20086 if source.row().as_f32() < scroll_top.floor() {
20087 return None;
20088 }
20089 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20090 let source_y = line_height * (source.row().as_f32() - scroll_top);
20091 Some(gpui::Point::new(source_x, source_y))
20092 }
20093
20094 pub fn has_visible_completions_menu(&self) -> bool {
20095 !self.edit_prediction_preview_is_active()
20096 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20097 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20098 })
20099 }
20100
20101 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20102 if self.mode.is_minimap() {
20103 return;
20104 }
20105 self.addons
20106 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20107 }
20108
20109 pub fn unregister_addon<T: Addon>(&mut self) {
20110 self.addons.remove(&std::any::TypeId::of::<T>());
20111 }
20112
20113 pub fn addon<T: Addon>(&self) -> Option<&T> {
20114 let type_id = std::any::TypeId::of::<T>();
20115 self.addons
20116 .get(&type_id)
20117 .and_then(|item| item.to_any().downcast_ref::<T>())
20118 }
20119
20120 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20121 let type_id = std::any::TypeId::of::<T>();
20122 self.addons
20123 .get_mut(&type_id)
20124 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20125 }
20126
20127 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20128 let text_layout_details = self.text_layout_details(window);
20129 let style = &text_layout_details.editor_style;
20130 let font_id = window.text_system().resolve_font(&style.text.font());
20131 let font_size = style.text.font_size.to_pixels(window.rem_size());
20132 let line_height = style.text.line_height_in_pixels(window.rem_size());
20133 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20134
20135 gpui::Size::new(em_width, line_height)
20136 }
20137
20138 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20139 self.load_diff_task.clone()
20140 }
20141
20142 fn read_metadata_from_db(
20143 &mut self,
20144 item_id: u64,
20145 workspace_id: WorkspaceId,
20146 window: &mut Window,
20147 cx: &mut Context<Editor>,
20148 ) {
20149 if self.is_singleton(cx)
20150 && !self.mode.is_minimap()
20151 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20152 {
20153 let buffer_snapshot = OnceCell::new();
20154
20155 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20156 if !folds.is_empty() {
20157 let snapshot =
20158 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20159 self.fold_ranges(
20160 folds
20161 .into_iter()
20162 .map(|(start, end)| {
20163 snapshot.clip_offset(start, Bias::Left)
20164 ..snapshot.clip_offset(end, Bias::Right)
20165 })
20166 .collect(),
20167 false,
20168 window,
20169 cx,
20170 );
20171 }
20172 }
20173
20174 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20175 if !selections.is_empty() {
20176 let snapshot =
20177 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20178 // skip adding the initial selection to selection history
20179 self.selection_history.mode = SelectionHistoryMode::Skipping;
20180 self.change_selections(None, window, cx, |s| {
20181 s.select_ranges(selections.into_iter().map(|(start, end)| {
20182 snapshot.clip_offset(start, Bias::Left)
20183 ..snapshot.clip_offset(end, Bias::Right)
20184 }));
20185 });
20186 self.selection_history.mode = SelectionHistoryMode::Normal;
20187 }
20188 };
20189 }
20190
20191 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20192 }
20193}
20194
20195fn vim_enabled(cx: &App) -> bool {
20196 cx.global::<SettingsStore>()
20197 .raw_user_settings()
20198 .get("vim_mode")
20199 == Some(&serde_json::Value::Bool(true))
20200}
20201
20202fn process_completion_for_edit(
20203 completion: &Completion,
20204 intent: CompletionIntent,
20205 buffer: &Entity<Buffer>,
20206 cursor_position: &text::Anchor,
20207 cx: &mut Context<Editor>,
20208) -> CompletionEdit {
20209 let buffer = buffer.read(cx);
20210 let buffer_snapshot = buffer.snapshot();
20211 let (snippet, new_text) = if completion.is_snippet() {
20212 // Workaround for typescript language server issues so that methods don't expand within
20213 // strings and functions with type expressions. The previous point is used because the query
20214 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20215 let mut snippet_source = completion.new_text.clone();
20216 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20217 previous_point.column = previous_point.column.saturating_sub(1);
20218 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20219 if scope.prefers_label_for_snippet_in_completion() {
20220 if let Some(label) = completion.label() {
20221 if matches!(
20222 completion.kind(),
20223 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20224 ) {
20225 snippet_source = label;
20226 }
20227 }
20228 }
20229 }
20230 match Snippet::parse(&snippet_source).log_err() {
20231 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20232 None => (None, completion.new_text.clone()),
20233 }
20234 } else {
20235 (None, completion.new_text.clone())
20236 };
20237
20238 let mut range_to_replace = {
20239 let replace_range = &completion.replace_range;
20240 if let CompletionSource::Lsp {
20241 insert_range: Some(insert_range),
20242 ..
20243 } = &completion.source
20244 {
20245 debug_assert_eq!(
20246 insert_range.start, replace_range.start,
20247 "insert_range and replace_range should start at the same position"
20248 );
20249 debug_assert!(
20250 insert_range
20251 .start
20252 .cmp(&cursor_position, &buffer_snapshot)
20253 .is_le(),
20254 "insert_range should start before or at cursor position"
20255 );
20256 debug_assert!(
20257 replace_range
20258 .start
20259 .cmp(&cursor_position, &buffer_snapshot)
20260 .is_le(),
20261 "replace_range should start before or at cursor position"
20262 );
20263 debug_assert!(
20264 insert_range
20265 .end
20266 .cmp(&cursor_position, &buffer_snapshot)
20267 .is_le(),
20268 "insert_range should end before or at cursor position"
20269 );
20270
20271 let should_replace = match intent {
20272 CompletionIntent::CompleteWithInsert => false,
20273 CompletionIntent::CompleteWithReplace => true,
20274 CompletionIntent::Complete | CompletionIntent::Compose => {
20275 let insert_mode =
20276 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20277 .completions
20278 .lsp_insert_mode;
20279 match insert_mode {
20280 LspInsertMode::Insert => false,
20281 LspInsertMode::Replace => true,
20282 LspInsertMode::ReplaceSubsequence => {
20283 let mut text_to_replace = buffer.chars_for_range(
20284 buffer.anchor_before(replace_range.start)
20285 ..buffer.anchor_after(replace_range.end),
20286 );
20287 let mut current_needle = text_to_replace.next();
20288 for haystack_ch in completion.label.text.chars() {
20289 if let Some(needle_ch) = current_needle {
20290 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20291 current_needle = text_to_replace.next();
20292 }
20293 }
20294 }
20295 current_needle.is_none()
20296 }
20297 LspInsertMode::ReplaceSuffix => {
20298 if replace_range
20299 .end
20300 .cmp(&cursor_position, &buffer_snapshot)
20301 .is_gt()
20302 {
20303 let range_after_cursor = *cursor_position..replace_range.end;
20304 let text_after_cursor = buffer
20305 .text_for_range(
20306 buffer.anchor_before(range_after_cursor.start)
20307 ..buffer.anchor_after(range_after_cursor.end),
20308 )
20309 .collect::<String>()
20310 .to_ascii_lowercase();
20311 completion
20312 .label
20313 .text
20314 .to_ascii_lowercase()
20315 .ends_with(&text_after_cursor)
20316 } else {
20317 true
20318 }
20319 }
20320 }
20321 }
20322 };
20323
20324 if should_replace {
20325 replace_range.clone()
20326 } else {
20327 insert_range.clone()
20328 }
20329 } else {
20330 replace_range.clone()
20331 }
20332 };
20333
20334 if range_to_replace
20335 .end
20336 .cmp(&cursor_position, &buffer_snapshot)
20337 .is_lt()
20338 {
20339 range_to_replace.end = *cursor_position;
20340 }
20341
20342 CompletionEdit {
20343 new_text,
20344 replace_range: range_to_replace.to_offset(&buffer),
20345 snippet,
20346 }
20347}
20348
20349struct CompletionEdit {
20350 new_text: String,
20351 replace_range: Range<usize>,
20352 snippet: Option<Snippet>,
20353}
20354
20355fn insert_extra_newline_brackets(
20356 buffer: &MultiBufferSnapshot,
20357 range: Range<usize>,
20358 language: &language::LanguageScope,
20359) -> bool {
20360 let leading_whitespace_len = buffer
20361 .reversed_chars_at(range.start)
20362 .take_while(|c| c.is_whitespace() && *c != '\n')
20363 .map(|c| c.len_utf8())
20364 .sum::<usize>();
20365 let trailing_whitespace_len = buffer
20366 .chars_at(range.end)
20367 .take_while(|c| c.is_whitespace() && *c != '\n')
20368 .map(|c| c.len_utf8())
20369 .sum::<usize>();
20370 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20371
20372 language.brackets().any(|(pair, enabled)| {
20373 let pair_start = pair.start.trim_end();
20374 let pair_end = pair.end.trim_start();
20375
20376 enabled
20377 && pair.newline
20378 && buffer.contains_str_at(range.end, pair_end)
20379 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20380 })
20381}
20382
20383fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20384 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20385 [(buffer, range, _)] => (*buffer, range.clone()),
20386 _ => return false,
20387 };
20388 let pair = {
20389 let mut result: Option<BracketMatch> = None;
20390
20391 for pair in buffer
20392 .all_bracket_ranges(range.clone())
20393 .filter(move |pair| {
20394 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20395 })
20396 {
20397 let len = pair.close_range.end - pair.open_range.start;
20398
20399 if let Some(existing) = &result {
20400 let existing_len = existing.close_range.end - existing.open_range.start;
20401 if len > existing_len {
20402 continue;
20403 }
20404 }
20405
20406 result = Some(pair);
20407 }
20408
20409 result
20410 };
20411 let Some(pair) = pair else {
20412 return false;
20413 };
20414 pair.newline_only
20415 && buffer
20416 .chars_for_range(pair.open_range.end..range.start)
20417 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20418 .all(|c| c.is_whitespace() && c != '\n')
20419}
20420
20421fn update_uncommitted_diff_for_buffer(
20422 editor: Entity<Editor>,
20423 project: &Entity<Project>,
20424 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20425 buffer: Entity<MultiBuffer>,
20426 cx: &mut App,
20427) -> Task<()> {
20428 let mut tasks = Vec::new();
20429 project.update(cx, |project, cx| {
20430 for buffer in buffers {
20431 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20432 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20433 }
20434 }
20435 });
20436 cx.spawn(async move |cx| {
20437 let diffs = future::join_all(tasks).await;
20438 if editor
20439 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20440 .unwrap_or(false)
20441 {
20442 return;
20443 }
20444
20445 buffer
20446 .update(cx, |buffer, cx| {
20447 for diff in diffs.into_iter().flatten() {
20448 buffer.add_diff(diff, cx);
20449 }
20450 })
20451 .ok();
20452 })
20453}
20454
20455fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20456 let tab_size = tab_size.get() as usize;
20457 let mut width = offset;
20458
20459 for ch in text.chars() {
20460 width += if ch == '\t' {
20461 tab_size - (width % tab_size)
20462 } else {
20463 1
20464 };
20465 }
20466
20467 width - offset
20468}
20469
20470#[cfg(test)]
20471mod tests {
20472 use super::*;
20473
20474 #[test]
20475 fn test_string_size_with_expanded_tabs() {
20476 let nz = |val| NonZeroU32::new(val).unwrap();
20477 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20478 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20479 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20480 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20481 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20482 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20483 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20484 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20485 }
20486}
20487
20488/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20489struct WordBreakingTokenizer<'a> {
20490 input: &'a str,
20491}
20492
20493impl<'a> WordBreakingTokenizer<'a> {
20494 fn new(input: &'a str) -> Self {
20495 Self { input }
20496 }
20497}
20498
20499fn is_char_ideographic(ch: char) -> bool {
20500 use unicode_script::Script::*;
20501 use unicode_script::UnicodeScript;
20502 matches!(ch.script(), Han | Tangut | Yi)
20503}
20504
20505fn is_grapheme_ideographic(text: &str) -> bool {
20506 text.chars().any(is_char_ideographic)
20507}
20508
20509fn is_grapheme_whitespace(text: &str) -> bool {
20510 text.chars().any(|x| x.is_whitespace())
20511}
20512
20513fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20514 text.chars().next().map_or(false, |ch| {
20515 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20516 })
20517}
20518
20519#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20520enum WordBreakToken<'a> {
20521 Word { token: &'a str, grapheme_len: usize },
20522 InlineWhitespace { token: &'a str, grapheme_len: usize },
20523 Newline,
20524}
20525
20526impl<'a> Iterator for WordBreakingTokenizer<'a> {
20527 /// Yields a span, the count of graphemes in the token, and whether it was
20528 /// whitespace. Note that it also breaks at word boundaries.
20529 type Item = WordBreakToken<'a>;
20530
20531 fn next(&mut self) -> Option<Self::Item> {
20532 use unicode_segmentation::UnicodeSegmentation;
20533 if self.input.is_empty() {
20534 return None;
20535 }
20536
20537 let mut iter = self.input.graphemes(true).peekable();
20538 let mut offset = 0;
20539 let mut grapheme_len = 0;
20540 if let Some(first_grapheme) = iter.next() {
20541 let is_newline = first_grapheme == "\n";
20542 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20543 offset += first_grapheme.len();
20544 grapheme_len += 1;
20545 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20546 if let Some(grapheme) = iter.peek().copied() {
20547 if should_stay_with_preceding_ideograph(grapheme) {
20548 offset += grapheme.len();
20549 grapheme_len += 1;
20550 }
20551 }
20552 } else {
20553 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20554 let mut next_word_bound = words.peek().copied();
20555 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20556 next_word_bound = words.next();
20557 }
20558 while let Some(grapheme) = iter.peek().copied() {
20559 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20560 break;
20561 };
20562 if is_grapheme_whitespace(grapheme) != is_whitespace
20563 || (grapheme == "\n") != is_newline
20564 {
20565 break;
20566 };
20567 offset += grapheme.len();
20568 grapheme_len += 1;
20569 iter.next();
20570 }
20571 }
20572 let token = &self.input[..offset];
20573 self.input = &self.input[offset..];
20574 if token == "\n" {
20575 Some(WordBreakToken::Newline)
20576 } else if is_whitespace {
20577 Some(WordBreakToken::InlineWhitespace {
20578 token,
20579 grapheme_len,
20580 })
20581 } else {
20582 Some(WordBreakToken::Word {
20583 token,
20584 grapheme_len,
20585 })
20586 }
20587 } else {
20588 None
20589 }
20590 }
20591}
20592
20593#[test]
20594fn test_word_breaking_tokenizer() {
20595 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20596 ("", &[]),
20597 (" ", &[whitespace(" ", 2)]),
20598 ("Ʒ", &[word("Ʒ", 1)]),
20599 ("Ǽ", &[word("Ǽ", 1)]),
20600 ("⋑", &[word("⋑", 1)]),
20601 ("⋑⋑", &[word("⋑⋑", 2)]),
20602 (
20603 "原理,进而",
20604 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20605 ),
20606 (
20607 "hello world",
20608 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20609 ),
20610 (
20611 "hello, world",
20612 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20613 ),
20614 (
20615 " hello world",
20616 &[
20617 whitespace(" ", 2),
20618 word("hello", 5),
20619 whitespace(" ", 1),
20620 word("world", 5),
20621 ],
20622 ),
20623 (
20624 "这是什么 \n 钢笔",
20625 &[
20626 word("这", 1),
20627 word("是", 1),
20628 word("什", 1),
20629 word("么", 1),
20630 whitespace(" ", 1),
20631 newline(),
20632 whitespace(" ", 1),
20633 word("钢", 1),
20634 word("笔", 1),
20635 ],
20636 ),
20637 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20638 ];
20639
20640 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20641 WordBreakToken::Word {
20642 token,
20643 grapheme_len,
20644 }
20645 }
20646
20647 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20648 WordBreakToken::InlineWhitespace {
20649 token,
20650 grapheme_len,
20651 }
20652 }
20653
20654 fn newline() -> WordBreakToken<'static> {
20655 WordBreakToken::Newline
20656 }
20657
20658 for (input, result) in tests {
20659 assert_eq!(
20660 WordBreakingTokenizer::new(input)
20661 .collect::<Vec<_>>()
20662 .as_slice(),
20663 *result,
20664 );
20665 }
20666}
20667
20668fn wrap_with_prefix(
20669 line_prefix: String,
20670 unwrapped_text: String,
20671 wrap_column: usize,
20672 tab_size: NonZeroU32,
20673 preserve_existing_whitespace: bool,
20674) -> String {
20675 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20676 let mut wrapped_text = String::new();
20677 let mut current_line = line_prefix.clone();
20678
20679 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20680 let mut current_line_len = line_prefix_len;
20681 let mut in_whitespace = false;
20682 for token in tokenizer {
20683 let have_preceding_whitespace = in_whitespace;
20684 match token {
20685 WordBreakToken::Word {
20686 token,
20687 grapheme_len,
20688 } => {
20689 in_whitespace = false;
20690 if current_line_len + grapheme_len > wrap_column
20691 && current_line_len != line_prefix_len
20692 {
20693 wrapped_text.push_str(current_line.trim_end());
20694 wrapped_text.push('\n');
20695 current_line.truncate(line_prefix.len());
20696 current_line_len = line_prefix_len;
20697 }
20698 current_line.push_str(token);
20699 current_line_len += grapheme_len;
20700 }
20701 WordBreakToken::InlineWhitespace {
20702 mut token,
20703 mut grapheme_len,
20704 } => {
20705 in_whitespace = true;
20706 if have_preceding_whitespace && !preserve_existing_whitespace {
20707 continue;
20708 }
20709 if !preserve_existing_whitespace {
20710 token = " ";
20711 grapheme_len = 1;
20712 }
20713 if current_line_len + grapheme_len > wrap_column {
20714 wrapped_text.push_str(current_line.trim_end());
20715 wrapped_text.push('\n');
20716 current_line.truncate(line_prefix.len());
20717 current_line_len = line_prefix_len;
20718 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20719 current_line.push_str(token);
20720 current_line_len += grapheme_len;
20721 }
20722 }
20723 WordBreakToken::Newline => {
20724 in_whitespace = true;
20725 if preserve_existing_whitespace {
20726 wrapped_text.push_str(current_line.trim_end());
20727 wrapped_text.push('\n');
20728 current_line.truncate(line_prefix.len());
20729 current_line_len = line_prefix_len;
20730 } else if have_preceding_whitespace {
20731 continue;
20732 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20733 {
20734 wrapped_text.push_str(current_line.trim_end());
20735 wrapped_text.push('\n');
20736 current_line.truncate(line_prefix.len());
20737 current_line_len = line_prefix_len;
20738 } else if current_line_len != line_prefix_len {
20739 current_line.push(' ');
20740 current_line_len += 1;
20741 }
20742 }
20743 }
20744 }
20745
20746 if !current_line.is_empty() {
20747 wrapped_text.push_str(¤t_line);
20748 }
20749 wrapped_text
20750}
20751
20752#[test]
20753fn test_wrap_with_prefix() {
20754 assert_eq!(
20755 wrap_with_prefix(
20756 "# ".to_string(),
20757 "abcdefg".to_string(),
20758 4,
20759 NonZeroU32::new(4).unwrap(),
20760 false,
20761 ),
20762 "# abcdefg"
20763 );
20764 assert_eq!(
20765 wrap_with_prefix(
20766 "".to_string(),
20767 "\thello world".to_string(),
20768 8,
20769 NonZeroU32::new(4).unwrap(),
20770 false,
20771 ),
20772 "hello\nworld"
20773 );
20774 assert_eq!(
20775 wrap_with_prefix(
20776 "// ".to_string(),
20777 "xx \nyy zz aa bb cc".to_string(),
20778 12,
20779 NonZeroU32::new(4).unwrap(),
20780 false,
20781 ),
20782 "// xx yy zz\n// aa bb cc"
20783 );
20784 assert_eq!(
20785 wrap_with_prefix(
20786 String::new(),
20787 "这是什么 \n 钢笔".to_string(),
20788 3,
20789 NonZeroU32::new(4).unwrap(),
20790 false,
20791 ),
20792 "这是什\n么 钢\n笔"
20793 );
20794}
20795
20796pub trait CollaborationHub {
20797 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20798 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20799 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20800}
20801
20802impl CollaborationHub for Entity<Project> {
20803 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20804 self.read(cx).collaborators()
20805 }
20806
20807 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20808 self.read(cx).user_store().read(cx).participant_indices()
20809 }
20810
20811 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20812 let this = self.read(cx);
20813 let user_ids = this.collaborators().values().map(|c| c.user_id);
20814 this.user_store().read(cx).participant_names(user_ids, cx)
20815 }
20816}
20817
20818pub trait SemanticsProvider {
20819 fn hover(
20820 &self,
20821 buffer: &Entity<Buffer>,
20822 position: text::Anchor,
20823 cx: &mut App,
20824 ) -> Option<Task<Vec<project::Hover>>>;
20825
20826 fn inline_values(
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 inlay_hints(
20834 &self,
20835 buffer_handle: Entity<Buffer>,
20836 range: Range<text::Anchor>,
20837 cx: &mut App,
20838 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20839
20840 fn resolve_inlay_hint(
20841 &self,
20842 hint: InlayHint,
20843 buffer_handle: Entity<Buffer>,
20844 server_id: LanguageServerId,
20845 cx: &mut App,
20846 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20847
20848 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20849
20850 fn document_highlights(
20851 &self,
20852 buffer: &Entity<Buffer>,
20853 position: text::Anchor,
20854 cx: &mut App,
20855 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20856
20857 fn definitions(
20858 &self,
20859 buffer: &Entity<Buffer>,
20860 position: text::Anchor,
20861 kind: GotoDefinitionKind,
20862 cx: &mut App,
20863 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20864
20865 fn range_for_rename(
20866 &self,
20867 buffer: &Entity<Buffer>,
20868 position: text::Anchor,
20869 cx: &mut App,
20870 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20871
20872 fn perform_rename(
20873 &self,
20874 buffer: &Entity<Buffer>,
20875 position: text::Anchor,
20876 new_name: String,
20877 cx: &mut App,
20878 ) -> Option<Task<Result<ProjectTransaction>>>;
20879
20880 fn pull_diagnostics_for_buffer(
20881 &self,
20882 buffer: Entity<Buffer>,
20883 cx: &mut App,
20884 ) -> Task<anyhow::Result<()>>;
20885}
20886
20887pub trait CompletionProvider {
20888 fn completions(
20889 &self,
20890 excerpt_id: ExcerptId,
20891 buffer: &Entity<Buffer>,
20892 buffer_position: text::Anchor,
20893 trigger: CompletionContext,
20894 window: &mut Window,
20895 cx: &mut Context<Editor>,
20896 ) -> Task<Result<Vec<CompletionResponse>>>;
20897
20898 fn resolve_completions(
20899 &self,
20900 _buffer: Entity<Buffer>,
20901 _completion_indices: Vec<usize>,
20902 _completions: Rc<RefCell<Box<[Completion]>>>,
20903 _cx: &mut Context<Editor>,
20904 ) -> Task<Result<bool>> {
20905 Task::ready(Ok(false))
20906 }
20907
20908 fn apply_additional_edits_for_completion(
20909 &self,
20910 _buffer: Entity<Buffer>,
20911 _completions: Rc<RefCell<Box<[Completion]>>>,
20912 _completion_index: usize,
20913 _push_to_history: bool,
20914 _cx: &mut Context<Editor>,
20915 ) -> Task<Result<Option<language::Transaction>>> {
20916 Task::ready(Ok(None))
20917 }
20918
20919 fn is_completion_trigger(
20920 &self,
20921 buffer: &Entity<Buffer>,
20922 position: language::Anchor,
20923 text: &str,
20924 trigger_in_words: bool,
20925 menu_is_open: bool,
20926 cx: &mut Context<Editor>,
20927 ) -> bool;
20928
20929 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20930
20931 fn sort_completions(&self) -> bool {
20932 true
20933 }
20934
20935 fn filter_completions(&self) -> bool {
20936 true
20937 }
20938}
20939
20940pub trait CodeActionProvider {
20941 fn id(&self) -> Arc<str>;
20942
20943 fn code_actions(
20944 &self,
20945 buffer: &Entity<Buffer>,
20946 range: Range<text::Anchor>,
20947 window: &mut Window,
20948 cx: &mut App,
20949 ) -> Task<Result<Vec<CodeAction>>>;
20950
20951 fn apply_code_action(
20952 &self,
20953 buffer_handle: Entity<Buffer>,
20954 action: CodeAction,
20955 excerpt_id: ExcerptId,
20956 push_to_history: bool,
20957 window: &mut Window,
20958 cx: &mut App,
20959 ) -> Task<Result<ProjectTransaction>>;
20960}
20961
20962impl CodeActionProvider for Entity<Project> {
20963 fn id(&self) -> Arc<str> {
20964 "project".into()
20965 }
20966
20967 fn code_actions(
20968 &self,
20969 buffer: &Entity<Buffer>,
20970 range: Range<text::Anchor>,
20971 _window: &mut Window,
20972 cx: &mut App,
20973 ) -> Task<Result<Vec<CodeAction>>> {
20974 self.update(cx, |project, cx| {
20975 let code_lens = project.code_lens(buffer, range.clone(), cx);
20976 let code_actions = project.code_actions(buffer, range, None, cx);
20977 cx.background_spawn(async move {
20978 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20979 Ok(code_lens
20980 .context("code lens fetch")?
20981 .into_iter()
20982 .chain(code_actions.context("code action fetch")?)
20983 .collect())
20984 })
20985 })
20986 }
20987
20988 fn apply_code_action(
20989 &self,
20990 buffer_handle: Entity<Buffer>,
20991 action: CodeAction,
20992 _excerpt_id: ExcerptId,
20993 push_to_history: bool,
20994 _window: &mut Window,
20995 cx: &mut App,
20996 ) -> Task<Result<ProjectTransaction>> {
20997 self.update(cx, |project, cx| {
20998 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20999 })
21000 }
21001}
21002
21003fn snippet_completions(
21004 project: &Project,
21005 buffer: &Entity<Buffer>,
21006 buffer_position: text::Anchor,
21007 cx: &mut App,
21008) -> Task<Result<CompletionResponse>> {
21009 let languages = buffer.read(cx).languages_at(buffer_position);
21010 let snippet_store = project.snippets().read(cx);
21011
21012 let scopes: Vec<_> = languages
21013 .iter()
21014 .filter_map(|language| {
21015 let language_name = language.lsp_id();
21016 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21017
21018 if snippets.is_empty() {
21019 None
21020 } else {
21021 Some((language.default_scope(), snippets))
21022 }
21023 })
21024 .collect();
21025
21026 if scopes.is_empty() {
21027 return Task::ready(Ok(CompletionResponse {
21028 completions: vec![],
21029 is_incomplete: false,
21030 }));
21031 }
21032
21033 let snapshot = buffer.read(cx).text_snapshot();
21034 let chars: String = snapshot
21035 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21036 .collect();
21037 let executor = cx.background_executor().clone();
21038
21039 cx.background_spawn(async move {
21040 let mut is_incomplete = false;
21041 let mut completions: Vec<Completion> = Vec::new();
21042 for (scope, snippets) in scopes.into_iter() {
21043 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21044 let mut last_word = chars
21045 .chars()
21046 .take_while(|c| classifier.is_word(*c))
21047 .collect::<String>();
21048 last_word = last_word.chars().rev().collect();
21049
21050 if last_word.is_empty() {
21051 return Ok(CompletionResponse {
21052 completions: vec![],
21053 is_incomplete: true,
21054 });
21055 }
21056
21057 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21058 let to_lsp = |point: &text::Anchor| {
21059 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21060 point_to_lsp(end)
21061 };
21062 let lsp_end = to_lsp(&buffer_position);
21063
21064 let candidates = snippets
21065 .iter()
21066 .enumerate()
21067 .flat_map(|(ix, snippet)| {
21068 snippet
21069 .prefix
21070 .iter()
21071 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21072 })
21073 .collect::<Vec<StringMatchCandidate>>();
21074
21075 const MAX_RESULTS: usize = 100;
21076 let mut matches = fuzzy::match_strings(
21077 &candidates,
21078 &last_word,
21079 last_word.chars().any(|c| c.is_uppercase()),
21080 MAX_RESULTS,
21081 &Default::default(),
21082 executor.clone(),
21083 )
21084 .await;
21085
21086 if matches.len() >= MAX_RESULTS {
21087 is_incomplete = true;
21088 }
21089
21090 // Remove all candidates where the query's start does not match the start of any word in the candidate
21091 if let Some(query_start) = last_word.chars().next() {
21092 matches.retain(|string_match| {
21093 split_words(&string_match.string).any(|word| {
21094 // Check that the first codepoint of the word as lowercase matches the first
21095 // codepoint of the query as lowercase
21096 word.chars()
21097 .flat_map(|codepoint| codepoint.to_lowercase())
21098 .zip(query_start.to_lowercase())
21099 .all(|(word_cp, query_cp)| word_cp == query_cp)
21100 })
21101 });
21102 }
21103
21104 let matched_strings = matches
21105 .into_iter()
21106 .map(|m| m.string)
21107 .collect::<HashSet<_>>();
21108
21109 completions.extend(snippets.iter().filter_map(|snippet| {
21110 let matching_prefix = snippet
21111 .prefix
21112 .iter()
21113 .find(|prefix| matched_strings.contains(*prefix))?;
21114 let start = as_offset - last_word.len();
21115 let start = snapshot.anchor_before(start);
21116 let range = start..buffer_position;
21117 let lsp_start = to_lsp(&start);
21118 let lsp_range = lsp::Range {
21119 start: lsp_start,
21120 end: lsp_end,
21121 };
21122 Some(Completion {
21123 replace_range: range,
21124 new_text: snippet.body.clone(),
21125 source: CompletionSource::Lsp {
21126 insert_range: None,
21127 server_id: LanguageServerId(usize::MAX),
21128 resolved: true,
21129 lsp_completion: Box::new(lsp::CompletionItem {
21130 label: snippet.prefix.first().unwrap().clone(),
21131 kind: Some(CompletionItemKind::SNIPPET),
21132 label_details: snippet.description.as_ref().map(|description| {
21133 lsp::CompletionItemLabelDetails {
21134 detail: Some(description.clone()),
21135 description: None,
21136 }
21137 }),
21138 insert_text_format: Some(InsertTextFormat::SNIPPET),
21139 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21140 lsp::InsertReplaceEdit {
21141 new_text: snippet.body.clone(),
21142 insert: lsp_range,
21143 replace: lsp_range,
21144 },
21145 )),
21146 filter_text: Some(snippet.body.clone()),
21147 sort_text: Some(char::MAX.to_string()),
21148 ..lsp::CompletionItem::default()
21149 }),
21150 lsp_defaults: None,
21151 },
21152 label: CodeLabel {
21153 text: matching_prefix.clone(),
21154 runs: Vec::new(),
21155 filter_range: 0..matching_prefix.len(),
21156 },
21157 icon_path: None,
21158 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21159 single_line: snippet.name.clone().into(),
21160 plain_text: snippet
21161 .description
21162 .clone()
21163 .map(|description| description.into()),
21164 }),
21165 insert_text_mode: None,
21166 confirm: None,
21167 })
21168 }))
21169 }
21170
21171 Ok(CompletionResponse {
21172 completions,
21173 is_incomplete,
21174 })
21175 })
21176}
21177
21178impl CompletionProvider for Entity<Project> {
21179 fn completions(
21180 &self,
21181 _excerpt_id: ExcerptId,
21182 buffer: &Entity<Buffer>,
21183 buffer_position: text::Anchor,
21184 options: CompletionContext,
21185 _window: &mut Window,
21186 cx: &mut Context<Editor>,
21187 ) -> Task<Result<Vec<CompletionResponse>>> {
21188 self.update(cx, |project, cx| {
21189 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21190 let project_completions = project.completions(buffer, buffer_position, options, cx);
21191 cx.background_spawn(async move {
21192 let mut responses = project_completions.await?;
21193 let snippets = snippets.await?;
21194 if !snippets.completions.is_empty() {
21195 responses.push(snippets);
21196 }
21197 Ok(responses)
21198 })
21199 })
21200 }
21201
21202 fn resolve_completions(
21203 &self,
21204 buffer: Entity<Buffer>,
21205 completion_indices: Vec<usize>,
21206 completions: Rc<RefCell<Box<[Completion]>>>,
21207 cx: &mut Context<Editor>,
21208 ) -> Task<Result<bool>> {
21209 self.update(cx, |project, cx| {
21210 project.lsp_store().update(cx, |lsp_store, cx| {
21211 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21212 })
21213 })
21214 }
21215
21216 fn apply_additional_edits_for_completion(
21217 &self,
21218 buffer: Entity<Buffer>,
21219 completions: Rc<RefCell<Box<[Completion]>>>,
21220 completion_index: usize,
21221 push_to_history: bool,
21222 cx: &mut Context<Editor>,
21223 ) -> Task<Result<Option<language::Transaction>>> {
21224 self.update(cx, |project, cx| {
21225 project.lsp_store().update(cx, |lsp_store, cx| {
21226 lsp_store.apply_additional_edits_for_completion(
21227 buffer,
21228 completions,
21229 completion_index,
21230 push_to_history,
21231 cx,
21232 )
21233 })
21234 })
21235 }
21236
21237 fn is_completion_trigger(
21238 &self,
21239 buffer: &Entity<Buffer>,
21240 position: language::Anchor,
21241 text: &str,
21242 trigger_in_words: bool,
21243 menu_is_open: bool,
21244 cx: &mut Context<Editor>,
21245 ) -> bool {
21246 let mut chars = text.chars();
21247 let char = if let Some(char) = chars.next() {
21248 char
21249 } else {
21250 return false;
21251 };
21252 if chars.next().is_some() {
21253 return false;
21254 }
21255
21256 let buffer = buffer.read(cx);
21257 let snapshot = buffer.snapshot();
21258 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21259 return false;
21260 }
21261 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21262 if trigger_in_words && classifier.is_word(char) {
21263 return true;
21264 }
21265
21266 buffer.completion_triggers().contains(text)
21267 }
21268}
21269
21270impl SemanticsProvider for Entity<Project> {
21271 fn hover(
21272 &self,
21273 buffer: &Entity<Buffer>,
21274 position: text::Anchor,
21275 cx: &mut App,
21276 ) -> Option<Task<Vec<project::Hover>>> {
21277 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21278 }
21279
21280 fn document_highlights(
21281 &self,
21282 buffer: &Entity<Buffer>,
21283 position: text::Anchor,
21284 cx: &mut App,
21285 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21286 Some(self.update(cx, |project, cx| {
21287 project.document_highlights(buffer, position, cx)
21288 }))
21289 }
21290
21291 fn definitions(
21292 &self,
21293 buffer: &Entity<Buffer>,
21294 position: text::Anchor,
21295 kind: GotoDefinitionKind,
21296 cx: &mut App,
21297 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21298 Some(self.update(cx, |project, cx| match kind {
21299 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21300 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21301 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21302 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21303 }))
21304 }
21305
21306 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21307 // TODO: make this work for remote projects
21308 self.update(cx, |project, cx| {
21309 if project
21310 .active_debug_session(cx)
21311 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21312 {
21313 return true;
21314 }
21315
21316 buffer.update(cx, |buffer, cx| {
21317 project.any_language_server_supports_inlay_hints(buffer, cx)
21318 })
21319 })
21320 }
21321
21322 fn inline_values(
21323 &self,
21324 buffer_handle: Entity<Buffer>,
21325
21326 range: Range<text::Anchor>,
21327 cx: &mut App,
21328 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21329 self.update(cx, |project, cx| {
21330 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21331
21332 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21333 })
21334 }
21335
21336 fn inlay_hints(
21337 &self,
21338 buffer_handle: Entity<Buffer>,
21339 range: Range<text::Anchor>,
21340 cx: &mut App,
21341 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21342 Some(self.update(cx, |project, cx| {
21343 project.inlay_hints(buffer_handle, range, cx)
21344 }))
21345 }
21346
21347 fn resolve_inlay_hint(
21348 &self,
21349 hint: InlayHint,
21350 buffer_handle: Entity<Buffer>,
21351 server_id: LanguageServerId,
21352 cx: &mut App,
21353 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21354 Some(self.update(cx, |project, cx| {
21355 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21356 }))
21357 }
21358
21359 fn range_for_rename(
21360 &self,
21361 buffer: &Entity<Buffer>,
21362 position: text::Anchor,
21363 cx: &mut App,
21364 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21365 Some(self.update(cx, |project, cx| {
21366 let buffer = buffer.clone();
21367 let task = project.prepare_rename(buffer.clone(), position, cx);
21368 cx.spawn(async move |_, cx| {
21369 Ok(match task.await? {
21370 PrepareRenameResponse::Success(range) => Some(range),
21371 PrepareRenameResponse::InvalidPosition => None,
21372 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21373 // Fallback on using TreeSitter info to determine identifier range
21374 buffer.read_with(cx, |buffer, _| {
21375 let snapshot = buffer.snapshot();
21376 let (range, kind) = snapshot.surrounding_word(position);
21377 if kind != Some(CharKind::Word) {
21378 return None;
21379 }
21380 Some(
21381 snapshot.anchor_before(range.start)
21382 ..snapshot.anchor_after(range.end),
21383 )
21384 })?
21385 }
21386 })
21387 })
21388 }))
21389 }
21390
21391 fn perform_rename(
21392 &self,
21393 buffer: &Entity<Buffer>,
21394 position: text::Anchor,
21395 new_name: String,
21396 cx: &mut App,
21397 ) -> Option<Task<Result<ProjectTransaction>>> {
21398 Some(self.update(cx, |project, cx| {
21399 project.perform_rename(buffer.clone(), position, new_name, cx)
21400 }))
21401 }
21402
21403 fn pull_diagnostics_for_buffer(
21404 &self,
21405 buffer: Entity<Buffer>,
21406 cx: &mut App,
21407 ) -> Task<anyhow::Result<()>> {
21408 let diagnostics = self.update(cx, |project, cx| {
21409 project
21410 .lsp_store()
21411 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21412 });
21413 let project = self.clone();
21414 cx.spawn(async move |cx| {
21415 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21416 project.update(cx, |project, cx| {
21417 project.lsp_store().update(cx, |lsp_store, cx| {
21418 for diagnostics_set in diagnostics {
21419 let LspPullDiagnostics::Response {
21420 server_id,
21421 uri,
21422 diagnostics,
21423 } = diagnostics_set
21424 else {
21425 continue;
21426 };
21427
21428 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21429 let disk_based_sources = adapter
21430 .as_ref()
21431 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21432 .unwrap_or(&[]);
21433 match diagnostics {
21434 PulledDiagnostics::Unchanged { result_id } => {
21435 lsp_store
21436 .merge_diagnostics(
21437 server_id,
21438 lsp::PublishDiagnosticsParams {
21439 uri: uri.clone(),
21440 diagnostics: Vec::new(),
21441 version: None,
21442 },
21443 Some(result_id),
21444 DiagnosticSourceKind::Pulled,
21445 disk_based_sources,
21446 |_, _| true,
21447 cx,
21448 )
21449 .log_err();
21450 }
21451 PulledDiagnostics::Changed {
21452 diagnostics,
21453 result_id,
21454 } => {
21455 lsp_store
21456 .merge_diagnostics(
21457 server_id,
21458 lsp::PublishDiagnosticsParams {
21459 uri: uri.clone(),
21460 diagnostics,
21461 version: None,
21462 },
21463 result_id,
21464 DiagnosticSourceKind::Pulled,
21465 disk_based_sources,
21466 |old_diagnostic, _| match old_diagnostic.source_kind {
21467 DiagnosticSourceKind::Pulled => false,
21468 DiagnosticSourceKind::Other
21469 | DiagnosticSourceKind::Pushed => true,
21470 },
21471 cx,
21472 )
21473 .log_err();
21474 }
21475 }
21476 }
21477 })
21478 })
21479 })
21480 }
21481}
21482
21483fn inlay_hint_settings(
21484 location: Anchor,
21485 snapshot: &MultiBufferSnapshot,
21486 cx: &mut Context<Editor>,
21487) -> InlayHintSettings {
21488 let file = snapshot.file_at(location);
21489 let language = snapshot.language_at(location).map(|l| l.name());
21490 language_settings(language, file, cx).inlay_hints
21491}
21492
21493fn consume_contiguous_rows(
21494 contiguous_row_selections: &mut Vec<Selection<Point>>,
21495 selection: &Selection<Point>,
21496 display_map: &DisplaySnapshot,
21497 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21498) -> (MultiBufferRow, MultiBufferRow) {
21499 contiguous_row_selections.push(selection.clone());
21500 let start_row = MultiBufferRow(selection.start.row);
21501 let mut end_row = ending_row(selection, display_map);
21502
21503 while let Some(next_selection) = selections.peek() {
21504 if next_selection.start.row <= end_row.0 {
21505 end_row = ending_row(next_selection, display_map);
21506 contiguous_row_selections.push(selections.next().unwrap().clone());
21507 } else {
21508 break;
21509 }
21510 }
21511 (start_row, end_row)
21512}
21513
21514fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21515 if next_selection.end.column > 0 || next_selection.is_empty() {
21516 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21517 } else {
21518 MultiBufferRow(next_selection.end.row)
21519 }
21520}
21521
21522impl EditorSnapshot {
21523 pub fn remote_selections_in_range<'a>(
21524 &'a self,
21525 range: &'a Range<Anchor>,
21526 collaboration_hub: &dyn CollaborationHub,
21527 cx: &'a App,
21528 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21529 let participant_names = collaboration_hub.user_names(cx);
21530 let participant_indices = collaboration_hub.user_participant_indices(cx);
21531 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21532 let collaborators_by_replica_id = collaborators_by_peer_id
21533 .values()
21534 .map(|collaborator| (collaborator.replica_id, collaborator))
21535 .collect::<HashMap<_, _>>();
21536 self.buffer_snapshot
21537 .selections_in_range(range, false)
21538 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21539 if replica_id == AGENT_REPLICA_ID {
21540 Some(RemoteSelection {
21541 replica_id,
21542 selection,
21543 cursor_shape,
21544 line_mode,
21545 collaborator_id: CollaboratorId::Agent,
21546 user_name: Some("Agent".into()),
21547 color: cx.theme().players().agent(),
21548 })
21549 } else {
21550 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21551 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21552 let user_name = participant_names.get(&collaborator.user_id).cloned();
21553 Some(RemoteSelection {
21554 replica_id,
21555 selection,
21556 cursor_shape,
21557 line_mode,
21558 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21559 user_name,
21560 color: if let Some(index) = participant_index {
21561 cx.theme().players().color_for_participant(index.0)
21562 } else {
21563 cx.theme().players().absent()
21564 },
21565 })
21566 }
21567 })
21568 }
21569
21570 pub fn hunks_for_ranges(
21571 &self,
21572 ranges: impl IntoIterator<Item = Range<Point>>,
21573 ) -> Vec<MultiBufferDiffHunk> {
21574 let mut hunks = Vec::new();
21575 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21576 HashMap::default();
21577 for query_range in ranges {
21578 let query_rows =
21579 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21580 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21581 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21582 ) {
21583 // Include deleted hunks that are adjacent to the query range, because
21584 // otherwise they would be missed.
21585 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21586 if hunk.status().is_deleted() {
21587 intersects_range |= hunk.row_range.start == query_rows.end;
21588 intersects_range |= hunk.row_range.end == query_rows.start;
21589 }
21590 if intersects_range {
21591 if !processed_buffer_rows
21592 .entry(hunk.buffer_id)
21593 .or_default()
21594 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21595 {
21596 continue;
21597 }
21598 hunks.push(hunk);
21599 }
21600 }
21601 }
21602
21603 hunks
21604 }
21605
21606 fn display_diff_hunks_for_rows<'a>(
21607 &'a self,
21608 display_rows: Range<DisplayRow>,
21609 folded_buffers: &'a HashSet<BufferId>,
21610 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21611 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21612 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21613
21614 self.buffer_snapshot
21615 .diff_hunks_in_range(buffer_start..buffer_end)
21616 .filter_map(|hunk| {
21617 if folded_buffers.contains(&hunk.buffer_id) {
21618 return None;
21619 }
21620
21621 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21622 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21623
21624 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21625 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21626
21627 let display_hunk = if hunk_display_start.column() != 0 {
21628 DisplayDiffHunk::Folded {
21629 display_row: hunk_display_start.row(),
21630 }
21631 } else {
21632 let mut end_row = hunk_display_end.row();
21633 if hunk_display_end.column() > 0 {
21634 end_row.0 += 1;
21635 }
21636 let is_created_file = hunk.is_created_file();
21637 DisplayDiffHunk::Unfolded {
21638 status: hunk.status(),
21639 diff_base_byte_range: hunk.diff_base_byte_range,
21640 display_row_range: hunk_display_start.row()..end_row,
21641 multi_buffer_range: Anchor::range_in_buffer(
21642 hunk.excerpt_id,
21643 hunk.buffer_id,
21644 hunk.buffer_range,
21645 ),
21646 is_created_file,
21647 }
21648 };
21649
21650 Some(display_hunk)
21651 })
21652 }
21653
21654 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21655 self.display_snapshot.buffer_snapshot.language_at(position)
21656 }
21657
21658 pub fn is_focused(&self) -> bool {
21659 self.is_focused
21660 }
21661
21662 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21663 self.placeholder_text.as_ref()
21664 }
21665
21666 pub fn scroll_position(&self) -> gpui::Point<f32> {
21667 self.scroll_anchor.scroll_position(&self.display_snapshot)
21668 }
21669
21670 fn gutter_dimensions(
21671 &self,
21672 font_id: FontId,
21673 font_size: Pixels,
21674 max_line_number_width: Pixels,
21675 cx: &App,
21676 ) -> Option<GutterDimensions> {
21677 if !self.show_gutter {
21678 return None;
21679 }
21680
21681 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21682 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21683
21684 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21685 matches!(
21686 ProjectSettings::get_global(cx).git.git_gutter,
21687 Some(GitGutterSetting::TrackedFiles)
21688 )
21689 });
21690 let gutter_settings = EditorSettings::get_global(cx).gutter;
21691 let show_line_numbers = self
21692 .show_line_numbers
21693 .unwrap_or(gutter_settings.line_numbers);
21694 let line_gutter_width = if show_line_numbers {
21695 // Avoid flicker-like gutter resizes when the line number gains another digit by
21696 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21697 let min_width_for_number_on_gutter =
21698 ch_advance * gutter_settings.min_line_number_digits as f32;
21699 max_line_number_width.max(min_width_for_number_on_gutter)
21700 } else {
21701 0.0.into()
21702 };
21703
21704 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21705 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21706
21707 let git_blame_entries_width =
21708 self.git_blame_gutter_max_author_length
21709 .map(|max_author_length| {
21710 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21711 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21712
21713 /// The number of characters to dedicate to gaps and margins.
21714 const SPACING_WIDTH: usize = 4;
21715
21716 let max_char_count = max_author_length.min(renderer.max_author_length())
21717 + ::git::SHORT_SHA_LENGTH
21718 + MAX_RELATIVE_TIMESTAMP.len()
21719 + SPACING_WIDTH;
21720
21721 ch_advance * max_char_count
21722 });
21723
21724 let is_singleton = self.buffer_snapshot.is_singleton();
21725
21726 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21727 left_padding += if !is_singleton {
21728 ch_width * 4.0
21729 } else if show_runnables || show_breakpoints {
21730 ch_width * 3.0
21731 } else if show_git_gutter && show_line_numbers {
21732 ch_width * 2.0
21733 } else if show_git_gutter || show_line_numbers {
21734 ch_width
21735 } else {
21736 px(0.)
21737 };
21738
21739 let shows_folds = is_singleton && gutter_settings.folds;
21740
21741 let right_padding = if shows_folds && show_line_numbers {
21742 ch_width * 4.0
21743 } else if shows_folds || (!is_singleton && show_line_numbers) {
21744 ch_width * 3.0
21745 } else if show_line_numbers {
21746 ch_width
21747 } else {
21748 px(0.)
21749 };
21750
21751 Some(GutterDimensions {
21752 left_padding,
21753 right_padding,
21754 width: line_gutter_width + left_padding + right_padding,
21755 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21756 git_blame_entries_width,
21757 })
21758 }
21759
21760 pub fn render_crease_toggle(
21761 &self,
21762 buffer_row: MultiBufferRow,
21763 row_contains_cursor: bool,
21764 editor: Entity<Editor>,
21765 window: &mut Window,
21766 cx: &mut App,
21767 ) -> Option<AnyElement> {
21768 let folded = self.is_line_folded(buffer_row);
21769 let mut is_foldable = false;
21770
21771 if let Some(crease) = self
21772 .crease_snapshot
21773 .query_row(buffer_row, &self.buffer_snapshot)
21774 {
21775 is_foldable = true;
21776 match crease {
21777 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21778 if let Some(render_toggle) = render_toggle {
21779 let toggle_callback =
21780 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21781 if folded {
21782 editor.update(cx, |editor, cx| {
21783 editor.fold_at(buffer_row, window, cx)
21784 });
21785 } else {
21786 editor.update(cx, |editor, cx| {
21787 editor.unfold_at(buffer_row, window, cx)
21788 });
21789 }
21790 });
21791 return Some((render_toggle)(
21792 buffer_row,
21793 folded,
21794 toggle_callback,
21795 window,
21796 cx,
21797 ));
21798 }
21799 }
21800 }
21801 }
21802
21803 is_foldable |= self.starts_indent(buffer_row);
21804
21805 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21806 Some(
21807 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21808 .toggle_state(folded)
21809 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21810 if folded {
21811 this.unfold_at(buffer_row, window, cx);
21812 } else {
21813 this.fold_at(buffer_row, window, cx);
21814 }
21815 }))
21816 .into_any_element(),
21817 )
21818 } else {
21819 None
21820 }
21821 }
21822
21823 pub fn render_crease_trailer(
21824 &self,
21825 buffer_row: MultiBufferRow,
21826 window: &mut Window,
21827 cx: &mut App,
21828 ) -> Option<AnyElement> {
21829 let folded = self.is_line_folded(buffer_row);
21830 if let Crease::Inline { render_trailer, .. } = self
21831 .crease_snapshot
21832 .query_row(buffer_row, &self.buffer_snapshot)?
21833 {
21834 let render_trailer = render_trailer.as_ref()?;
21835 Some(render_trailer(buffer_row, folded, window, cx))
21836 } else {
21837 None
21838 }
21839 }
21840}
21841
21842impl Deref for EditorSnapshot {
21843 type Target = DisplaySnapshot;
21844
21845 fn deref(&self) -> &Self::Target {
21846 &self.display_snapshot
21847 }
21848}
21849
21850#[derive(Clone, Debug, PartialEq, Eq)]
21851pub enum EditorEvent {
21852 InputIgnored {
21853 text: Arc<str>,
21854 },
21855 InputHandled {
21856 utf16_range_to_replace: Option<Range<isize>>,
21857 text: Arc<str>,
21858 },
21859 ExcerptsAdded {
21860 buffer: Entity<Buffer>,
21861 predecessor: ExcerptId,
21862 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21863 },
21864 ExcerptsRemoved {
21865 ids: Vec<ExcerptId>,
21866 removed_buffer_ids: Vec<BufferId>,
21867 },
21868 BufferFoldToggled {
21869 ids: Vec<ExcerptId>,
21870 folded: bool,
21871 },
21872 ExcerptsEdited {
21873 ids: Vec<ExcerptId>,
21874 },
21875 ExcerptsExpanded {
21876 ids: Vec<ExcerptId>,
21877 },
21878 BufferEdited,
21879 Edited {
21880 transaction_id: clock::Lamport,
21881 },
21882 Reparsed(BufferId),
21883 Focused,
21884 FocusedIn,
21885 Blurred,
21886 DirtyChanged,
21887 Saved,
21888 TitleChanged,
21889 DiffBaseChanged,
21890 SelectionsChanged {
21891 local: bool,
21892 },
21893 ScrollPositionChanged {
21894 local: bool,
21895 autoscroll: bool,
21896 },
21897 Closed,
21898 TransactionUndone {
21899 transaction_id: clock::Lamport,
21900 },
21901 TransactionBegun {
21902 transaction_id: clock::Lamport,
21903 },
21904 Reloaded,
21905 CursorShapeChanged,
21906 PushedToNavHistory {
21907 anchor: Anchor,
21908 is_deactivate: bool,
21909 },
21910}
21911
21912impl EventEmitter<EditorEvent> for Editor {}
21913
21914impl Focusable for Editor {
21915 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21916 self.focus_handle.clone()
21917 }
21918}
21919
21920impl Render for Editor {
21921 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21922 let settings = ThemeSettings::get_global(cx);
21923
21924 let mut text_style = match self.mode {
21925 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21926 color: cx.theme().colors().editor_foreground,
21927 font_family: settings.ui_font.family.clone(),
21928 font_features: settings.ui_font.features.clone(),
21929 font_fallbacks: settings.ui_font.fallbacks.clone(),
21930 font_size: rems(0.875).into(),
21931 font_weight: settings.ui_font.weight,
21932 line_height: relative(settings.buffer_line_height.value()),
21933 ..Default::default()
21934 },
21935 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21936 color: cx.theme().colors().editor_foreground,
21937 font_family: settings.buffer_font.family.clone(),
21938 font_features: settings.buffer_font.features.clone(),
21939 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21940 font_size: settings.buffer_font_size(cx).into(),
21941 font_weight: settings.buffer_font.weight,
21942 line_height: relative(settings.buffer_line_height.value()),
21943 ..Default::default()
21944 },
21945 };
21946 if let Some(text_style_refinement) = &self.text_style_refinement {
21947 text_style.refine(text_style_refinement)
21948 }
21949
21950 let background = match self.mode {
21951 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21952 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
21953 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21954 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21955 };
21956
21957 EditorElement::new(
21958 &cx.entity(),
21959 EditorStyle {
21960 background,
21961 local_player: cx.theme().players().local(),
21962 text: text_style,
21963 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21964 syntax: cx.theme().syntax().clone(),
21965 status: cx.theme().status().clone(),
21966 inlay_hints_style: make_inlay_hints_style(cx),
21967 inline_completion_styles: make_suggestion_styles(cx),
21968 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21969 show_underlines: !self.mode.is_minimap(),
21970 },
21971 )
21972 }
21973}
21974
21975impl EntityInputHandler for Editor {
21976 fn text_for_range(
21977 &mut self,
21978 range_utf16: Range<usize>,
21979 adjusted_range: &mut Option<Range<usize>>,
21980 _: &mut Window,
21981 cx: &mut Context<Self>,
21982 ) -> Option<String> {
21983 let snapshot = self.buffer.read(cx).read(cx);
21984 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21985 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21986 if (start.0..end.0) != range_utf16 {
21987 adjusted_range.replace(start.0..end.0);
21988 }
21989 Some(snapshot.text_for_range(start..end).collect())
21990 }
21991
21992 fn selected_text_range(
21993 &mut self,
21994 ignore_disabled_input: bool,
21995 _: &mut Window,
21996 cx: &mut Context<Self>,
21997 ) -> Option<UTF16Selection> {
21998 // Prevent the IME menu from appearing when holding down an alphabetic key
21999 // while input is disabled.
22000 if !ignore_disabled_input && !self.input_enabled {
22001 return None;
22002 }
22003
22004 let selection = self.selections.newest::<OffsetUtf16>(cx);
22005 let range = selection.range();
22006
22007 Some(UTF16Selection {
22008 range: range.start.0..range.end.0,
22009 reversed: selection.reversed,
22010 })
22011 }
22012
22013 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22014 let snapshot = self.buffer.read(cx).read(cx);
22015 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22016 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22017 }
22018
22019 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22020 self.clear_highlights::<InputComposition>(cx);
22021 self.ime_transaction.take();
22022 }
22023
22024 fn replace_text_in_range(
22025 &mut self,
22026 range_utf16: Option<Range<usize>>,
22027 text: &str,
22028 window: &mut Window,
22029 cx: &mut Context<Self>,
22030 ) {
22031 if !self.input_enabled {
22032 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22033 return;
22034 }
22035
22036 self.transact(window, cx, |this, window, cx| {
22037 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22038 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22039 Some(this.selection_replacement_ranges(range_utf16, cx))
22040 } else {
22041 this.marked_text_ranges(cx)
22042 };
22043
22044 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22045 let newest_selection_id = this.selections.newest_anchor().id;
22046 this.selections
22047 .all::<OffsetUtf16>(cx)
22048 .iter()
22049 .zip(ranges_to_replace.iter())
22050 .find_map(|(selection, range)| {
22051 if selection.id == newest_selection_id {
22052 Some(
22053 (range.start.0 as isize - selection.head().0 as isize)
22054 ..(range.end.0 as isize - selection.head().0 as isize),
22055 )
22056 } else {
22057 None
22058 }
22059 })
22060 });
22061
22062 cx.emit(EditorEvent::InputHandled {
22063 utf16_range_to_replace: range_to_replace,
22064 text: text.into(),
22065 });
22066
22067 if let Some(new_selected_ranges) = new_selected_ranges {
22068 this.change_selections(None, window, cx, |selections| {
22069 selections.select_ranges(new_selected_ranges)
22070 });
22071 this.backspace(&Default::default(), window, cx);
22072 }
22073
22074 this.handle_input(text, window, cx);
22075 });
22076
22077 if let Some(transaction) = self.ime_transaction {
22078 self.buffer.update(cx, |buffer, cx| {
22079 buffer.group_until_transaction(transaction, cx);
22080 });
22081 }
22082
22083 self.unmark_text(window, cx);
22084 }
22085
22086 fn replace_and_mark_text_in_range(
22087 &mut self,
22088 range_utf16: Option<Range<usize>>,
22089 text: &str,
22090 new_selected_range_utf16: Option<Range<usize>>,
22091 window: &mut Window,
22092 cx: &mut Context<Self>,
22093 ) {
22094 if !self.input_enabled {
22095 return;
22096 }
22097
22098 let transaction = self.transact(window, cx, |this, window, cx| {
22099 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22100 let snapshot = this.buffer.read(cx).read(cx);
22101 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22102 for marked_range in &mut marked_ranges {
22103 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22104 marked_range.start.0 += relative_range_utf16.start;
22105 marked_range.start =
22106 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22107 marked_range.end =
22108 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22109 }
22110 }
22111 Some(marked_ranges)
22112 } else if let Some(range_utf16) = range_utf16 {
22113 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22114 Some(this.selection_replacement_ranges(range_utf16, cx))
22115 } else {
22116 None
22117 };
22118
22119 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22120 let newest_selection_id = this.selections.newest_anchor().id;
22121 this.selections
22122 .all::<OffsetUtf16>(cx)
22123 .iter()
22124 .zip(ranges_to_replace.iter())
22125 .find_map(|(selection, range)| {
22126 if selection.id == newest_selection_id {
22127 Some(
22128 (range.start.0 as isize - selection.head().0 as isize)
22129 ..(range.end.0 as isize - selection.head().0 as isize),
22130 )
22131 } else {
22132 None
22133 }
22134 })
22135 });
22136
22137 cx.emit(EditorEvent::InputHandled {
22138 utf16_range_to_replace: range_to_replace,
22139 text: text.into(),
22140 });
22141
22142 if let Some(ranges) = ranges_to_replace {
22143 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22144 }
22145
22146 let marked_ranges = {
22147 let snapshot = this.buffer.read(cx).read(cx);
22148 this.selections
22149 .disjoint_anchors()
22150 .iter()
22151 .map(|selection| {
22152 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22153 })
22154 .collect::<Vec<_>>()
22155 };
22156
22157 if text.is_empty() {
22158 this.unmark_text(window, cx);
22159 } else {
22160 this.highlight_text::<InputComposition>(
22161 marked_ranges.clone(),
22162 HighlightStyle {
22163 underline: Some(UnderlineStyle {
22164 thickness: px(1.),
22165 color: None,
22166 wavy: false,
22167 }),
22168 ..Default::default()
22169 },
22170 cx,
22171 );
22172 }
22173
22174 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22175 let use_autoclose = this.use_autoclose;
22176 let use_auto_surround = this.use_auto_surround;
22177 this.set_use_autoclose(false);
22178 this.set_use_auto_surround(false);
22179 this.handle_input(text, window, cx);
22180 this.set_use_autoclose(use_autoclose);
22181 this.set_use_auto_surround(use_auto_surround);
22182
22183 if let Some(new_selected_range) = new_selected_range_utf16 {
22184 let snapshot = this.buffer.read(cx).read(cx);
22185 let new_selected_ranges = marked_ranges
22186 .into_iter()
22187 .map(|marked_range| {
22188 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22189 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22190 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22191 snapshot.clip_offset_utf16(new_start, Bias::Left)
22192 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22193 })
22194 .collect::<Vec<_>>();
22195
22196 drop(snapshot);
22197 this.change_selections(None, window, cx, |selections| {
22198 selections.select_ranges(new_selected_ranges)
22199 });
22200 }
22201 });
22202
22203 self.ime_transaction = self.ime_transaction.or(transaction);
22204 if let Some(transaction) = self.ime_transaction {
22205 self.buffer.update(cx, |buffer, cx| {
22206 buffer.group_until_transaction(transaction, cx);
22207 });
22208 }
22209
22210 if self.text_highlights::<InputComposition>(cx).is_none() {
22211 self.ime_transaction.take();
22212 }
22213 }
22214
22215 fn bounds_for_range(
22216 &mut self,
22217 range_utf16: Range<usize>,
22218 element_bounds: gpui::Bounds<Pixels>,
22219 window: &mut Window,
22220 cx: &mut Context<Self>,
22221 ) -> Option<gpui::Bounds<Pixels>> {
22222 let text_layout_details = self.text_layout_details(window);
22223 let gpui::Size {
22224 width: em_width,
22225 height: line_height,
22226 } = self.character_size(window);
22227
22228 let snapshot = self.snapshot(window, cx);
22229 let scroll_position = snapshot.scroll_position();
22230 let scroll_left = scroll_position.x * em_width;
22231
22232 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22233 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22234 + self.gutter_dimensions.width
22235 + self.gutter_dimensions.margin;
22236 let y = line_height * (start.row().as_f32() - scroll_position.y);
22237
22238 Some(Bounds {
22239 origin: element_bounds.origin + point(x, y),
22240 size: size(em_width, line_height),
22241 })
22242 }
22243
22244 fn character_index_for_point(
22245 &mut self,
22246 point: gpui::Point<Pixels>,
22247 _window: &mut Window,
22248 _cx: &mut Context<Self>,
22249 ) -> Option<usize> {
22250 let position_map = self.last_position_map.as_ref()?;
22251 if !position_map.text_hitbox.contains(&point) {
22252 return None;
22253 }
22254 let display_point = position_map.point_for_position(point).previous_valid;
22255 let anchor = position_map
22256 .snapshot
22257 .display_point_to_anchor(display_point, Bias::Left);
22258 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22259 Some(utf16_offset.0)
22260 }
22261}
22262
22263trait SelectionExt {
22264 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22265 fn spanned_rows(
22266 &self,
22267 include_end_if_at_line_start: bool,
22268 map: &DisplaySnapshot,
22269 ) -> Range<MultiBufferRow>;
22270}
22271
22272impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22273 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22274 let start = self
22275 .start
22276 .to_point(&map.buffer_snapshot)
22277 .to_display_point(map);
22278 let end = self
22279 .end
22280 .to_point(&map.buffer_snapshot)
22281 .to_display_point(map);
22282 if self.reversed {
22283 end..start
22284 } else {
22285 start..end
22286 }
22287 }
22288
22289 fn spanned_rows(
22290 &self,
22291 include_end_if_at_line_start: bool,
22292 map: &DisplaySnapshot,
22293 ) -> Range<MultiBufferRow> {
22294 let start = self.start.to_point(&map.buffer_snapshot);
22295 let mut end = self.end.to_point(&map.buffer_snapshot);
22296 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22297 end.row -= 1;
22298 }
22299
22300 let buffer_start = map.prev_line_boundary(start).0;
22301 let buffer_end = map.next_line_boundary(end).0;
22302 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22303 }
22304}
22305
22306impl<T: InvalidationRegion> InvalidationStack<T> {
22307 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22308 where
22309 S: Clone + ToOffset,
22310 {
22311 while let Some(region) = self.last() {
22312 let all_selections_inside_invalidation_ranges =
22313 if selections.len() == region.ranges().len() {
22314 selections
22315 .iter()
22316 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22317 .all(|(selection, invalidation_range)| {
22318 let head = selection.head().to_offset(buffer);
22319 invalidation_range.start <= head && invalidation_range.end >= head
22320 })
22321 } else {
22322 false
22323 };
22324
22325 if all_selections_inside_invalidation_ranges {
22326 break;
22327 } else {
22328 self.pop();
22329 }
22330 }
22331 }
22332}
22333
22334impl<T> Default for InvalidationStack<T> {
22335 fn default() -> Self {
22336 Self(Default::default())
22337 }
22338}
22339
22340impl<T> Deref for InvalidationStack<T> {
22341 type Target = Vec<T>;
22342
22343 fn deref(&self) -> &Self::Target {
22344 &self.0
22345 }
22346}
22347
22348impl<T> DerefMut for InvalidationStack<T> {
22349 fn deref_mut(&mut self) -> &mut Self::Target {
22350 &mut self.0
22351 }
22352}
22353
22354impl InvalidationRegion for SnippetState {
22355 fn ranges(&self) -> &[Range<Anchor>] {
22356 &self.ranges[self.active_index]
22357 }
22358}
22359
22360fn inline_completion_edit_text(
22361 current_snapshot: &BufferSnapshot,
22362 edits: &[(Range<Anchor>, String)],
22363 edit_preview: &EditPreview,
22364 include_deletions: bool,
22365 cx: &App,
22366) -> HighlightedText {
22367 let edits = edits
22368 .iter()
22369 .map(|(anchor, text)| {
22370 (
22371 anchor.start.text_anchor..anchor.end.text_anchor,
22372 text.clone(),
22373 )
22374 })
22375 .collect::<Vec<_>>();
22376
22377 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22378}
22379
22380pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22381 match severity {
22382 lsp::DiagnosticSeverity::ERROR => colors.error,
22383 lsp::DiagnosticSeverity::WARNING => colors.warning,
22384 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22385 lsp::DiagnosticSeverity::HINT => colors.info,
22386 _ => colors.ignored,
22387 }
22388}
22389
22390pub fn styled_runs_for_code_label<'a>(
22391 label: &'a CodeLabel,
22392 syntax_theme: &'a theme::SyntaxTheme,
22393) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22394 let fade_out = HighlightStyle {
22395 fade_out: Some(0.35),
22396 ..Default::default()
22397 };
22398
22399 let mut prev_end = label.filter_range.end;
22400 label
22401 .runs
22402 .iter()
22403 .enumerate()
22404 .flat_map(move |(ix, (range, highlight_id))| {
22405 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22406 style
22407 } else {
22408 return Default::default();
22409 };
22410 let mut muted_style = style;
22411 muted_style.highlight(fade_out);
22412
22413 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22414 if range.start >= label.filter_range.end {
22415 if range.start > prev_end {
22416 runs.push((prev_end..range.start, fade_out));
22417 }
22418 runs.push((range.clone(), muted_style));
22419 } else if range.end <= label.filter_range.end {
22420 runs.push((range.clone(), style));
22421 } else {
22422 runs.push((range.start..label.filter_range.end, style));
22423 runs.push((label.filter_range.end..range.end, muted_style));
22424 }
22425 prev_end = cmp::max(prev_end, range.end);
22426
22427 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22428 runs.push((prev_end..label.text.len(), fade_out));
22429 }
22430
22431 runs
22432 })
22433}
22434
22435pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22436 let mut prev_index = 0;
22437 let mut prev_codepoint: Option<char> = None;
22438 text.char_indices()
22439 .chain([(text.len(), '\0')])
22440 .filter_map(move |(index, codepoint)| {
22441 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22442 let is_boundary = index == text.len()
22443 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22444 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22445 if is_boundary {
22446 let chunk = &text[prev_index..index];
22447 prev_index = index;
22448 Some(chunk)
22449 } else {
22450 None
22451 }
22452 })
22453}
22454
22455pub trait RangeToAnchorExt: Sized {
22456 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22457
22458 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22459 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22460 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22461 }
22462}
22463
22464impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22465 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22466 let start_offset = self.start.to_offset(snapshot);
22467 let end_offset = self.end.to_offset(snapshot);
22468 if start_offset == end_offset {
22469 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22470 } else {
22471 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22472 }
22473 }
22474}
22475
22476pub trait RowExt {
22477 fn as_f32(&self) -> f32;
22478
22479 fn next_row(&self) -> Self;
22480
22481 fn previous_row(&self) -> Self;
22482
22483 fn minus(&self, other: Self) -> u32;
22484}
22485
22486impl RowExt for DisplayRow {
22487 fn as_f32(&self) -> f32 {
22488 self.0 as f32
22489 }
22490
22491 fn next_row(&self) -> Self {
22492 Self(self.0 + 1)
22493 }
22494
22495 fn previous_row(&self) -> Self {
22496 Self(self.0.saturating_sub(1))
22497 }
22498
22499 fn minus(&self, other: Self) -> u32 {
22500 self.0 - other.0
22501 }
22502}
22503
22504impl RowExt for MultiBufferRow {
22505 fn as_f32(&self) -> f32 {
22506 self.0 as f32
22507 }
22508
22509 fn next_row(&self) -> Self {
22510 Self(self.0 + 1)
22511 }
22512
22513 fn previous_row(&self) -> Self {
22514 Self(self.0.saturating_sub(1))
22515 }
22516
22517 fn minus(&self, other: Self) -> u32 {
22518 self.0 - other.0
22519 }
22520}
22521
22522trait RowRangeExt {
22523 type Row;
22524
22525 fn len(&self) -> usize;
22526
22527 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22528}
22529
22530impl RowRangeExt for Range<MultiBufferRow> {
22531 type Row = MultiBufferRow;
22532
22533 fn len(&self) -> usize {
22534 (self.end.0 - self.start.0) as usize
22535 }
22536
22537 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22538 (self.start.0..self.end.0).map(MultiBufferRow)
22539 }
22540}
22541
22542impl RowRangeExt for Range<DisplayRow> {
22543 type Row = DisplayRow;
22544
22545 fn len(&self) -> usize {
22546 (self.end.0 - self.start.0) as usize
22547 }
22548
22549 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22550 (self.start.0..self.end.0).map(DisplayRow)
22551 }
22552}
22553
22554/// If select range has more than one line, we
22555/// just point the cursor to range.start.
22556fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22557 if range.start.row == range.end.row {
22558 range
22559 } else {
22560 range.start..range.start
22561 }
22562}
22563pub struct KillRing(ClipboardItem);
22564impl Global for KillRing {}
22565
22566const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22567
22568enum BreakpointPromptEditAction {
22569 Log,
22570 Condition,
22571 HitCondition,
22572}
22573
22574struct BreakpointPromptEditor {
22575 pub(crate) prompt: Entity<Editor>,
22576 editor: WeakEntity<Editor>,
22577 breakpoint_anchor: Anchor,
22578 breakpoint: Breakpoint,
22579 edit_action: BreakpointPromptEditAction,
22580 block_ids: HashSet<CustomBlockId>,
22581 editor_margins: Arc<Mutex<EditorMargins>>,
22582 _subscriptions: Vec<Subscription>,
22583}
22584
22585impl BreakpointPromptEditor {
22586 const MAX_LINES: u8 = 4;
22587
22588 fn new(
22589 editor: WeakEntity<Editor>,
22590 breakpoint_anchor: Anchor,
22591 breakpoint: Breakpoint,
22592 edit_action: BreakpointPromptEditAction,
22593 window: &mut Window,
22594 cx: &mut Context<Self>,
22595 ) -> Self {
22596 let base_text = match edit_action {
22597 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22598 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22599 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22600 }
22601 .map(|msg| msg.to_string())
22602 .unwrap_or_default();
22603
22604 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22605 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22606
22607 let prompt = cx.new(|cx| {
22608 let mut prompt = Editor::new(
22609 EditorMode::AutoHeight {
22610 min_lines: 1,
22611 max_lines: Self::MAX_LINES as usize,
22612 },
22613 buffer,
22614 None,
22615 window,
22616 cx,
22617 );
22618 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22619 prompt.set_show_cursor_when_unfocused(false, cx);
22620 prompt.set_placeholder_text(
22621 match edit_action {
22622 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22623 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22624 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22625 },
22626 cx,
22627 );
22628
22629 prompt
22630 });
22631
22632 Self {
22633 prompt,
22634 editor,
22635 breakpoint_anchor,
22636 breakpoint,
22637 edit_action,
22638 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22639 block_ids: Default::default(),
22640 _subscriptions: vec![],
22641 }
22642 }
22643
22644 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22645 self.block_ids.extend(block_ids)
22646 }
22647
22648 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22649 if let Some(editor) = self.editor.upgrade() {
22650 let message = self
22651 .prompt
22652 .read(cx)
22653 .buffer
22654 .read(cx)
22655 .as_singleton()
22656 .expect("A multi buffer in breakpoint prompt isn't possible")
22657 .read(cx)
22658 .as_rope()
22659 .to_string();
22660
22661 editor.update(cx, |editor, cx| {
22662 editor.edit_breakpoint_at_anchor(
22663 self.breakpoint_anchor,
22664 self.breakpoint.clone(),
22665 match self.edit_action {
22666 BreakpointPromptEditAction::Log => {
22667 BreakpointEditAction::EditLogMessage(message.into())
22668 }
22669 BreakpointPromptEditAction::Condition => {
22670 BreakpointEditAction::EditCondition(message.into())
22671 }
22672 BreakpointPromptEditAction::HitCondition => {
22673 BreakpointEditAction::EditHitCondition(message.into())
22674 }
22675 },
22676 cx,
22677 );
22678
22679 editor.remove_blocks(self.block_ids.clone(), None, cx);
22680 cx.focus_self(window);
22681 });
22682 }
22683 }
22684
22685 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22686 self.editor
22687 .update(cx, |editor, cx| {
22688 editor.remove_blocks(self.block_ids.clone(), None, cx);
22689 window.focus(&editor.focus_handle);
22690 })
22691 .log_err();
22692 }
22693
22694 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22695 let settings = ThemeSettings::get_global(cx);
22696 let text_style = TextStyle {
22697 color: if self.prompt.read(cx).read_only(cx) {
22698 cx.theme().colors().text_disabled
22699 } else {
22700 cx.theme().colors().text
22701 },
22702 font_family: settings.buffer_font.family.clone(),
22703 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22704 font_size: settings.buffer_font_size(cx).into(),
22705 font_weight: settings.buffer_font.weight,
22706 line_height: relative(settings.buffer_line_height.value()),
22707 ..Default::default()
22708 };
22709 EditorElement::new(
22710 &self.prompt,
22711 EditorStyle {
22712 background: cx.theme().colors().editor_background,
22713 local_player: cx.theme().players().local(),
22714 text: text_style,
22715 ..Default::default()
22716 },
22717 )
22718 }
22719}
22720
22721impl Render for BreakpointPromptEditor {
22722 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22723 let editor_margins = *self.editor_margins.lock();
22724 let gutter_dimensions = editor_margins.gutter;
22725 h_flex()
22726 .key_context("Editor")
22727 .bg(cx.theme().colors().editor_background)
22728 .border_y_1()
22729 .border_color(cx.theme().status().info_border)
22730 .size_full()
22731 .py(window.line_height() / 2.5)
22732 .on_action(cx.listener(Self::confirm))
22733 .on_action(cx.listener(Self::cancel))
22734 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22735 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22736 }
22737}
22738
22739impl Focusable for BreakpointPromptEditor {
22740 fn focus_handle(&self, cx: &App) -> FocusHandle {
22741 self.prompt.focus_handle(cx)
22742 }
22743}
22744
22745fn all_edits_insertions_or_deletions(
22746 edits: &Vec<(Range<Anchor>, String)>,
22747 snapshot: &MultiBufferSnapshot,
22748) -> bool {
22749 let mut all_insertions = true;
22750 let mut all_deletions = true;
22751
22752 for (range, new_text) in edits.iter() {
22753 let range_is_empty = range.to_offset(&snapshot).is_empty();
22754 let text_is_empty = new_text.is_empty();
22755
22756 if range_is_empty != text_is_empty {
22757 if range_is_empty {
22758 all_deletions = false;
22759 } else {
22760 all_insertions = false;
22761 }
22762 } else {
22763 return false;
22764 }
22765
22766 if !all_insertions && !all_deletions {
22767 return false;
22768 }
22769 }
22770 all_insertions || all_deletions
22771}
22772
22773struct MissingEditPredictionKeybindingTooltip;
22774
22775impl Render for MissingEditPredictionKeybindingTooltip {
22776 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22777 ui::tooltip_container(window, cx, |container, _, cx| {
22778 container
22779 .flex_shrink_0()
22780 .max_w_80()
22781 .min_h(rems_from_px(124.))
22782 .justify_between()
22783 .child(
22784 v_flex()
22785 .flex_1()
22786 .text_ui_sm(cx)
22787 .child(Label::new("Conflict with Accept Keybinding"))
22788 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22789 )
22790 .child(
22791 h_flex()
22792 .pb_1()
22793 .gap_1()
22794 .items_end()
22795 .w_full()
22796 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22797 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22798 }))
22799 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22800 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22801 })),
22802 )
22803 })
22804 }
22805}
22806
22807#[derive(Debug, Clone, Copy, PartialEq)]
22808pub struct LineHighlight {
22809 pub background: Background,
22810 pub border: Option<gpui::Hsla>,
22811 pub include_gutter: bool,
22812 pub type_id: Option<TypeId>,
22813}
22814
22815fn render_diff_hunk_controls(
22816 row: u32,
22817 status: &DiffHunkStatus,
22818 hunk_range: Range<Anchor>,
22819 is_created_file: bool,
22820 line_height: Pixels,
22821 editor: &Entity<Editor>,
22822 _window: &mut Window,
22823 cx: &mut App,
22824) -> AnyElement {
22825 h_flex()
22826 .h(line_height)
22827 .mr_1()
22828 .gap_1()
22829 .px_0p5()
22830 .pb_1()
22831 .border_x_1()
22832 .border_b_1()
22833 .border_color(cx.theme().colors().border_variant)
22834 .rounded_b_lg()
22835 .bg(cx.theme().colors().editor_background)
22836 .gap_1()
22837 .block_mouse_except_scroll()
22838 .shadow_md()
22839 .child(if status.has_secondary_hunk() {
22840 Button::new(("stage", row as u64), "Stage")
22841 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22842 .tooltip({
22843 let focus_handle = editor.focus_handle(cx);
22844 move |window, cx| {
22845 Tooltip::for_action_in(
22846 "Stage Hunk",
22847 &::git::ToggleStaged,
22848 &focus_handle,
22849 window,
22850 cx,
22851 )
22852 }
22853 })
22854 .on_click({
22855 let editor = editor.clone();
22856 move |_event, _window, cx| {
22857 editor.update(cx, |editor, cx| {
22858 editor.stage_or_unstage_diff_hunks(
22859 true,
22860 vec![hunk_range.start..hunk_range.start],
22861 cx,
22862 );
22863 });
22864 }
22865 })
22866 } else {
22867 Button::new(("unstage", row as u64), "Unstage")
22868 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22869 .tooltip({
22870 let focus_handle = editor.focus_handle(cx);
22871 move |window, cx| {
22872 Tooltip::for_action_in(
22873 "Unstage Hunk",
22874 &::git::ToggleStaged,
22875 &focus_handle,
22876 window,
22877 cx,
22878 )
22879 }
22880 })
22881 .on_click({
22882 let editor = editor.clone();
22883 move |_event, _window, cx| {
22884 editor.update(cx, |editor, cx| {
22885 editor.stage_or_unstage_diff_hunks(
22886 false,
22887 vec![hunk_range.start..hunk_range.start],
22888 cx,
22889 );
22890 });
22891 }
22892 })
22893 })
22894 .child(
22895 Button::new(("restore", row as u64), "Restore")
22896 .tooltip({
22897 let focus_handle = editor.focus_handle(cx);
22898 move |window, cx| {
22899 Tooltip::for_action_in(
22900 "Restore Hunk",
22901 &::git::Restore,
22902 &focus_handle,
22903 window,
22904 cx,
22905 )
22906 }
22907 })
22908 .on_click({
22909 let editor = editor.clone();
22910 move |_event, window, cx| {
22911 editor.update(cx, |editor, cx| {
22912 let snapshot = editor.snapshot(window, cx);
22913 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22914 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22915 });
22916 }
22917 })
22918 .disabled(is_created_file),
22919 )
22920 .when(
22921 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22922 |el| {
22923 el.child(
22924 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22925 .shape(IconButtonShape::Square)
22926 .icon_size(IconSize::Small)
22927 // .disabled(!has_multiple_hunks)
22928 .tooltip({
22929 let focus_handle = editor.focus_handle(cx);
22930 move |window, cx| {
22931 Tooltip::for_action_in(
22932 "Next Hunk",
22933 &GoToHunk,
22934 &focus_handle,
22935 window,
22936 cx,
22937 )
22938 }
22939 })
22940 .on_click({
22941 let editor = editor.clone();
22942 move |_event, window, cx| {
22943 editor.update(cx, |editor, cx| {
22944 let snapshot = editor.snapshot(window, cx);
22945 let position =
22946 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22947 editor.go_to_hunk_before_or_after_position(
22948 &snapshot,
22949 position,
22950 Direction::Next,
22951 window,
22952 cx,
22953 );
22954 editor.expand_selected_diff_hunks(cx);
22955 });
22956 }
22957 }),
22958 )
22959 .child(
22960 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22961 .shape(IconButtonShape::Square)
22962 .icon_size(IconSize::Small)
22963 // .disabled(!has_multiple_hunks)
22964 .tooltip({
22965 let focus_handle = editor.focus_handle(cx);
22966 move |window, cx| {
22967 Tooltip::for_action_in(
22968 "Previous Hunk",
22969 &GoToPreviousHunk,
22970 &focus_handle,
22971 window,
22972 cx,
22973 )
22974 }
22975 })
22976 .on_click({
22977 let editor = editor.clone();
22978 move |_event, window, cx| {
22979 editor.update(cx, |editor, cx| {
22980 let snapshot = editor.snapshot(window, cx);
22981 let point =
22982 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22983 editor.go_to_hunk_before_or_after_position(
22984 &snapshot,
22985 point,
22986 Direction::Prev,
22987 window,
22988 cx,
22989 );
22990 editor.expand_selected_diff_hunks(cx);
22991 });
22992 }
22993 }),
22994 )
22995 },
22996 )
22997 .into_any_element()
22998}