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,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, CompletionResponse, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215use crate::{
216 code_context_menus::CompletionsMenuSource,
217 hover_links::{find_url, find_url_from_range},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
239pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
240
241pub type RenderDiffHunkControlsFn = Arc<
242 dyn Fn(
243 u32,
244 &DiffHunkStatus,
245 Range<Anchor>,
246 bool,
247 Pixels,
248 &Entity<Editor>,
249 &mut Window,
250 &mut App,
251 ) -> AnyElement,
252>;
253
254const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
255 alt: true,
256 shift: true,
257 control: false,
258 platform: false,
259 function: false,
260};
261
262struct InlineValueCache {
263 enabled: bool,
264 inlays: Vec<InlayId>,
265 refresh_task: Task<Option<()>>,
266}
267
268impl InlineValueCache {
269 fn new(enabled: bool) -> Self {
270 Self {
271 enabled,
272 inlays: Vec::new(),
273 refresh_task: Task::ready(None),
274 }
275 }
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
279pub enum InlayId {
280 InlineCompletion(usize),
281 Hint(usize),
282 DebuggerValue(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::Hint(id) => *id,
290 Self::DebuggerValue(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300enum SelectedTextHighlight {}
301
302pub enum ConflictsOuter {}
303pub enum ConflictsOurs {}
304pub enum ConflictsTheirs {}
305pub enum ConflictsOursMarker {}
306pub enum ConflictsTheirsMarker {}
307
308#[derive(Debug, Copy, Clone, PartialEq, Eq)]
309pub enum Navigated {
310 Yes,
311 No,
312}
313
314impl Navigated {
315 pub fn from_bool(yes: bool) -> Navigated {
316 if yes { Navigated::Yes } else { Navigated::No }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321enum DisplayDiffHunk {
322 Folded {
323 display_row: DisplayRow,
324 },
325 Unfolded {
326 is_created_file: bool,
327 diff_base_byte_range: Range<usize>,
328 display_row_range: Range<DisplayRow>,
329 multi_buffer_range: Range<Anchor>,
330 status: DiffHunkStatus,
331 },
332}
333
334pub enum HideMouseCursorOrigin {
335 TypingAction,
336 MovementAction,
337}
338
339pub fn init_settings(cx: &mut App) {
340 EditorSettings::register(cx);
341}
342
343pub fn init(cx: &mut App) {
344 init_settings(cx);
345
346 cx.set_global(GlobalBlameRenderer(Arc::new(())));
347
348 workspace::register_project_item::<Editor>(cx);
349 workspace::FollowableViewRegistry::register::<Editor>(cx);
350 workspace::register_serializable_item::<Editor>(cx);
351
352 cx.observe_new(
353 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
354 workspace.register_action(Editor::new_file);
355 workspace.register_action(Editor::new_file_vertical);
356 workspace.register_action(Editor::new_file_horizontal);
357 workspace.register_action(Editor::cancel_language_server_work);
358 },
359 )
360 .detach();
361
362 cx.on_action(move |_: &workspace::NewFile, cx| {
363 let app_state = workspace::AppState::global(cx);
364 if let Some(app_state) = app_state.upgrade() {
365 workspace::open_new(
366 Default::default(),
367 app_state,
368 cx,
369 |workspace, window, cx| {
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 cx.on_action(move |_: &workspace::NewWindow, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 cx.activate(true);
385 Editor::new_file(workspace, &Default::default(), window, cx)
386 },
387 )
388 .detach();
389 }
390 });
391}
392
393pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
394 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
395}
396
397pub trait DiagnosticRenderer {
398 fn render_group(
399 &self,
400 diagnostic_group: Vec<DiagnosticEntry<Point>>,
401 buffer_id: BufferId,
402 snapshot: EditorSnapshot,
403 editor: WeakEntity<Editor>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntry<Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 cx: &mut App,
413 ) -> Option<Entity<markdown::Markdown>>;
414
415 fn open_link(
416 &self,
417 editor: &mut Editor,
418 link: SharedString,
419 window: &mut Window,
420 cx: &mut Context<Editor>,
421 );
422}
423
424pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
425
426impl GlobalDiagnosticRenderer {
427 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
428 cx.try_global::<Self>().map(|g| g.0.clone())
429 }
430}
431
432impl gpui::Global for GlobalDiagnosticRenderer {}
433pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
434 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
435}
436
437pub struct SearchWithinRange;
438
439trait InvalidationRegion {
440 fn ranges(&self) -> &[Range<Anchor>];
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum SelectPhase {
445 Begin {
446 position: DisplayPoint,
447 add: bool,
448 click_count: usize,
449 },
450 BeginColumnar {
451 position: DisplayPoint,
452 reset: bool,
453 goal_column: u32,
454 },
455 Extend {
456 position: DisplayPoint,
457 click_count: usize,
458 },
459 Update {
460 position: DisplayPoint,
461 goal_column: u32,
462 scroll_delta: gpui::Point<f32>,
463 },
464 End,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Clone, PartialEq, Eq, Debug)]
476pub enum EditorMode {
477 SingleLine {
478 auto_width: bool,
479 },
480 AutoHeight {
481 max_lines: usize,
482 },
483 Full {
484 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
485 scale_ui_elements_with_buffer_font_size: bool,
486 /// When set to `true`, the editor will render a background for the active line.
487 show_active_line_background: bool,
488 /// When set to `true`, the editor's height will be determined by its content.
489 sized_by_content: bool,
490 },
491 Minimap {
492 parent: WeakEntity<Editor>,
493 },
494}
495
496impl EditorMode {
497 pub fn full() -> Self {
498 Self::Full {
499 scale_ui_elements_with_buffer_font_size: true,
500 show_active_line_background: true,
501 sized_by_content: false,
502 }
503 }
504
505 pub fn is_full(&self) -> bool {
506 matches!(self, Self::Full { .. })
507 }
508
509 fn is_minimap(&self) -> bool {
510 matches!(self, Self::Minimap { .. })
511 }
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum SoftWrap {
516 /// Prefer not to wrap at all.
517 ///
518 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
519 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
520 GitDiff,
521 /// Prefer a single line generally, unless an overly long line is encountered.
522 None,
523 /// Soft wrap lines that exceed the editor width.
524 EditorWidth,
525 /// Soft wrap lines at the preferred line length.
526 Column(u32),
527 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
528 Bounded(u32),
529}
530
531#[derive(Clone)]
532pub struct EditorStyle {
533 pub background: Hsla,
534 pub local_player: PlayerColor,
535 pub text: TextStyle,
536 pub scrollbar_width: Pixels,
537 pub syntax: Arc<SyntaxTheme>,
538 pub status: StatusColors,
539 pub inlay_hints_style: HighlightStyle,
540 pub inline_completion_styles: InlineCompletionStyles,
541 pub unnecessary_code_fade: f32,
542 pub show_underlines: bool,
543}
544
545impl Default for EditorStyle {
546 fn default() -> Self {
547 Self {
548 background: Hsla::default(),
549 local_player: PlayerColor::default(),
550 text: TextStyle::default(),
551 scrollbar_width: Pixels::default(),
552 syntax: Default::default(),
553 // HACK: Status colors don't have a real default.
554 // We should look into removing the status colors from the editor
555 // style and retrieve them directly from the theme.
556 status: StatusColors::dark(),
557 inlay_hints_style: HighlightStyle::default(),
558 inline_completion_styles: InlineCompletionStyles {
559 insertion: HighlightStyle::default(),
560 whitespace: HighlightStyle::default(),
561 },
562 unnecessary_code_fade: Default::default(),
563 show_underlines: true,
564 }
565 }
566}
567
568pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
569 let show_background = language_settings::language_settings(None, None, cx)
570 .inlay_hints
571 .show_background;
572
573 HighlightStyle {
574 color: Some(cx.theme().status().hint),
575 background_color: show_background.then(|| cx.theme().status().hint_background),
576 ..HighlightStyle::default()
577 }
578}
579
580pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
581 InlineCompletionStyles {
582 insertion: HighlightStyle {
583 color: Some(cx.theme().status().predictive),
584 ..HighlightStyle::default()
585 },
586 whitespace: HighlightStyle {
587 background_color: Some(cx.theme().status().created_background),
588 ..HighlightStyle::default()
589 },
590 }
591}
592
593type CompletionId = usize;
594
595pub(crate) enum EditDisplayMode {
596 TabAccept,
597 DiffPopover,
598 Inline,
599}
600
601enum InlineCompletion {
602 Edit {
603 edits: Vec<(Range<Anchor>, String)>,
604 edit_preview: Option<EditPreview>,
605 display_mode: EditDisplayMode,
606 snapshot: BufferSnapshot,
607 },
608 Move {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612}
613
614struct InlineCompletionState {
615 inlay_ids: Vec<InlayId>,
616 completion: InlineCompletion,
617 completion_id: Option<SharedString>,
618 invalidation_range: Range<Anchor>,
619}
620
621enum EditPredictionSettings {
622 Disabled,
623 Enabled {
624 show_in_menu: bool,
625 preview_requires_modifier: bool,
626 },
627}
628
629enum InlineCompletionHighlight {}
630
631#[derive(Debug, Clone)]
632struct InlineDiagnostic {
633 message: SharedString,
634 group_id: usize,
635 is_primary: bool,
636 start: Point,
637 severity: lsp::DiagnosticSeverity,
638}
639
640pub enum MenuInlineCompletionsPolicy {
641 Never,
642 ByProvider,
643}
644
645pub enum EditPredictionPreview {
646 /// Modifier is not pressed
647 Inactive { released_too_fast: bool },
648 /// Modifier pressed
649 Active {
650 since: Instant,
651 previous_scroll_position: Option<ScrollAnchor>,
652 },
653}
654
655impl EditPredictionPreview {
656 pub fn released_too_fast(&self) -> bool {
657 match self {
658 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
659 EditPredictionPreview::Active { .. } => false,
660 }
661 }
662
663 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
664 if let EditPredictionPreview::Active {
665 previous_scroll_position,
666 ..
667 } = self
668 {
669 *previous_scroll_position = scroll_position;
670 }
671 }
672}
673
674pub struct ContextMenuOptions {
675 pub min_entries_visible: usize,
676 pub max_entries_visible: usize,
677 pub placement: Option<ContextMenuPlacement>,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq)]
681pub enum ContextMenuPlacement {
682 Above,
683 Below,
684}
685
686#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
687struct EditorActionId(usize);
688
689impl EditorActionId {
690 pub fn post_inc(&mut self) -> Self {
691 let answer = self.0;
692
693 *self = Self(answer + 1);
694
695 Self(answer)
696 }
697}
698
699// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
700// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
701
702type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
703type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
704
705#[derive(Default)]
706struct ScrollbarMarkerState {
707 scrollbar_size: Size<Pixels>,
708 dirty: bool,
709 markers: Arc<[PaintQuad]>,
710 pending_refresh: Option<Task<Result<()>>>,
711}
712
713impl ScrollbarMarkerState {
714 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
715 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
716 }
717}
718
719#[derive(Clone, Copy, PartialEq, Eq)]
720pub enum MinimapVisibility {
721 Disabled,
722 Enabled {
723 /// The configuration currently present in the users settings.
724 setting_configuration: bool,
725 /// Whether to override the currently set visibility from the users setting.
726 toggle_override: bool,
727 },
728}
729
730impl MinimapVisibility {
731 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
732 if mode.is_full() {
733 Self::Enabled {
734 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
735 toggle_override: false,
736 }
737 } else {
738 Self::Disabled
739 }
740 }
741
742 fn hidden(&self) -> Self {
743 match *self {
744 Self::Enabled {
745 setting_configuration,
746 ..
747 } => Self::Enabled {
748 setting_configuration,
749 toggle_override: setting_configuration,
750 },
751 Self::Disabled => Self::Disabled,
752 }
753 }
754
755 fn disabled(&self) -> bool {
756 match *self {
757 Self::Disabled => true,
758 _ => false,
759 }
760 }
761
762 fn settings_visibility(&self) -> bool {
763 match *self {
764 Self::Enabled {
765 setting_configuration,
766 ..
767 } => setting_configuration,
768 _ => false,
769 }
770 }
771
772 fn visible(&self) -> bool {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 toggle_override,
777 } => setting_configuration ^ toggle_override,
778 _ => false,
779 }
780 }
781
782 fn toggle_visibility(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 toggle_override,
786 setting_configuration,
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: !toggle_override,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794}
795
796#[derive(Clone, Debug)]
797struct RunnableTasks {
798 templates: Vec<(TaskSourceKind, TaskTemplate)>,
799 offset: multi_buffer::Anchor,
800 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
801 column: u32,
802 // Values of all named captures, including those starting with '_'
803 extra_variables: HashMap<String, String>,
804 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
805 context_range: Range<BufferOffset>,
806}
807
808impl RunnableTasks {
809 fn resolve<'a>(
810 &'a self,
811 cx: &'a task::TaskContext,
812 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
813 self.templates.iter().filter_map(|(kind, template)| {
814 template
815 .resolve_task(&kind.to_id_base(), cx)
816 .map(|task| (kind.clone(), task))
817 })
818 }
819}
820
821#[derive(Clone)]
822pub struct ResolvedTasks {
823 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
824 position: Anchor,
825}
826
827#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
828struct BufferOffset(usize);
829
830// Addons allow storing per-editor state in other crates (e.g. Vim)
831pub trait Addon: 'static {
832 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
833
834 fn render_buffer_header_controls(
835 &self,
836 _: &ExcerptInfo,
837 _: &Window,
838 _: &App,
839 ) -> Option<AnyElement> {
840 None
841 }
842
843 fn to_any(&self) -> &dyn std::any::Any;
844
845 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
846 None
847 }
848}
849
850/// A set of caret positions, registered when the editor was edited.
851pub struct ChangeList {
852 changes: Vec<Vec<Anchor>>,
853 /// Currently "selected" change.
854 position: Option<usize>,
855}
856
857impl ChangeList {
858 pub fn new() -> Self {
859 Self {
860 changes: Vec::new(),
861 position: None,
862 }
863 }
864
865 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
866 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
867 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
868 if self.changes.is_empty() {
869 return None;
870 }
871
872 let prev = self.position.unwrap_or(self.changes.len());
873 let next = if direction == Direction::Prev {
874 prev.saturating_sub(count)
875 } else {
876 (prev + count).min(self.changes.len() - 1)
877 };
878 self.position = Some(next);
879 self.changes.get(next).map(|anchors| anchors.as_slice())
880 }
881
882 /// Adds a new change to the list, resetting the change list position.
883 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
884 self.position.take();
885 if pop_state {
886 self.changes.pop();
887 }
888 self.changes.push(new_positions.clone());
889 }
890
891 pub fn last(&self) -> Option<&[Anchor]> {
892 self.changes.last().map(|anchors| anchors.as_slice())
893 }
894}
895
896#[derive(Clone)]
897struct InlineBlamePopoverState {
898 scroll_handle: ScrollHandle,
899 commit_message: Option<ParsedCommitMessage>,
900 markdown: Entity<Markdown>,
901}
902
903struct InlineBlamePopover {
904 position: gpui::Point<Pixels>,
905 show_task: Option<Task<()>>,
906 hide_task: Option<Task<()>>,
907 popover_bounds: Option<Bounds<Pixels>>,
908 popover_state: InlineBlamePopoverState,
909}
910
911/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
912/// a breakpoint on them.
913#[derive(Clone, Copy, Debug)]
914struct PhantomBreakpointIndicator {
915 display_row: DisplayRow,
916 /// There's a small debounce between hovering over the line and showing the indicator.
917 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
918 is_active: bool,
919 collides_with_existing_breakpoint: bool,
920}
921/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
922///
923/// See the [module level documentation](self) for more information.
924pub struct Editor {
925 focus_handle: FocusHandle,
926 last_focused_descendant: Option<WeakFocusHandle>,
927 /// The text buffer being edited
928 buffer: Entity<MultiBuffer>,
929 /// Map of how text in the buffer should be displayed.
930 /// Handles soft wraps, folds, fake inlay text insertions, etc.
931 pub display_map: Entity<DisplayMap>,
932 pub selections: SelectionsCollection,
933 pub scroll_manager: ScrollManager,
934 /// When inline assist editors are linked, they all render cursors because
935 /// typing enters text into each of them, even the ones that aren't focused.
936 pub(crate) show_cursor_when_unfocused: bool,
937 columnar_selection_tail: Option<Anchor>,
938 columnar_display_point: Option<DisplayPoint>,
939 add_selections_state: Option<AddSelectionsState>,
940 select_next_state: Option<SelectNextState>,
941 select_prev_state: Option<SelectNextState>,
942 selection_history: SelectionHistory,
943 defer_selection_effects: bool,
944 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
945 autoclose_regions: Vec<AutocloseRegion>,
946 snippet_stack: InvalidationStack<SnippetState>,
947 select_syntax_node_history: SelectSyntaxNodeHistory,
948 ime_transaction: Option<TransactionId>,
949 pub diagnostics_max_severity: DiagnosticSeverity,
950 active_diagnostics: ActiveDiagnostic,
951 show_inline_diagnostics: bool,
952 inline_diagnostics_update: Task<()>,
953 inline_diagnostics_enabled: bool,
954 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
955 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
956 hard_wrap: Option<usize>,
957
958 // TODO: make this a access method
959 pub project: Option<Entity<Project>>,
960 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
961 completion_provider: Option<Rc<dyn CompletionProvider>>,
962 collaboration_hub: Option<Box<dyn CollaborationHub>>,
963 blink_manager: Entity<BlinkManager>,
964 show_cursor_names: bool,
965 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
966 pub show_local_selections: bool,
967 mode: EditorMode,
968 show_breadcrumbs: bool,
969 show_gutter: bool,
970 show_scrollbars: ScrollbarAxes,
971 minimap_visibility: MinimapVisibility,
972 offset_content: bool,
973 disable_expand_excerpt_buttons: bool,
974 show_line_numbers: Option<bool>,
975 use_relative_line_numbers: Option<bool>,
976 show_git_diff_gutter: Option<bool>,
977 show_code_actions: Option<bool>,
978 show_runnables: Option<bool>,
979 show_breakpoints: Option<bool>,
980 show_wrap_guides: Option<bool>,
981 show_indent_guides: Option<bool>,
982 placeholder_text: Option<Arc<str>>,
983 highlight_order: usize,
984 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
985 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
986 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
987 scrollbar_marker_state: ScrollbarMarkerState,
988 active_indent_guides_state: ActiveIndentGuidesState,
989 nav_history: Option<ItemNavHistory>,
990 context_menu: RefCell<Option<CodeContextMenu>>,
991 context_menu_options: Option<ContextMenuOptions>,
992 mouse_context_menu: Option<MouseContextMenu>,
993 completion_tasks: Vec<(CompletionId, Task<()>)>,
994 inline_blame_popover: Option<InlineBlamePopover>,
995 signature_help_state: SignatureHelpState,
996 auto_signature_help: Option<bool>,
997 find_all_references_task_sources: Vec<Anchor>,
998 next_completion_id: CompletionId,
999 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1000 code_actions_task: Option<Task<Result<()>>>,
1001 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1002 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1003 document_highlights_task: Option<Task<()>>,
1004 linked_editing_range_task: Option<Task<Option<()>>>,
1005 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1006 pending_rename: Option<RenameState>,
1007 searchable: bool,
1008 cursor_shape: CursorShape,
1009 current_line_highlight: Option<CurrentLineHighlight>,
1010 collapse_matches: bool,
1011 autoindent_mode: Option<AutoindentMode>,
1012 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1013 input_enabled: bool,
1014 use_modal_editing: bool,
1015 read_only: bool,
1016 leader_id: Option<CollaboratorId>,
1017 remote_id: Option<ViewId>,
1018 pub hover_state: HoverState,
1019 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1020 gutter_hovered: bool,
1021 hovered_link_state: Option<HoveredLinkState>,
1022 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1023 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1024 active_inline_completion: Option<InlineCompletionState>,
1025 /// Used to prevent flickering as the user types while the menu is open
1026 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1027 edit_prediction_settings: EditPredictionSettings,
1028 inline_completions_hidden_for_vim_mode: bool,
1029 show_inline_completions_override: Option<bool>,
1030 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1031 edit_prediction_preview: EditPredictionPreview,
1032 edit_prediction_indent_conflict: bool,
1033 edit_prediction_requires_modifier_in_indent_conflict: bool,
1034 inlay_hint_cache: InlayHintCache,
1035 next_inlay_id: usize,
1036 _subscriptions: Vec<Subscription>,
1037 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1038 gutter_dimensions: GutterDimensions,
1039 style: Option<EditorStyle>,
1040 text_style_refinement: Option<TextStyleRefinement>,
1041 next_editor_action_id: EditorActionId,
1042 editor_actions:
1043 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1044 use_autoclose: bool,
1045 use_auto_surround: bool,
1046 auto_replace_emoji_shortcode: bool,
1047 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1048 show_git_blame_gutter: bool,
1049 show_git_blame_inline: bool,
1050 show_git_blame_inline_delay_task: Option<Task<()>>,
1051 git_blame_inline_enabled: bool,
1052 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1053 serialize_dirty_buffers: bool,
1054 show_selection_menu: Option<bool>,
1055 blame: Option<Entity<GitBlame>>,
1056 blame_subscription: Option<Subscription>,
1057 custom_context_menu: Option<
1058 Box<
1059 dyn 'static
1060 + Fn(
1061 &mut Self,
1062 DisplayPoint,
1063 &mut Window,
1064 &mut Context<Self>,
1065 ) -> Option<Entity<ui::ContextMenu>>,
1066 >,
1067 >,
1068 last_bounds: Option<Bounds<Pixels>>,
1069 last_position_map: Option<Rc<PositionMap>>,
1070 expect_bounds_change: Option<Bounds<Pixels>>,
1071 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1072 tasks_update_task: Option<Task<()>>,
1073 breakpoint_store: Option<Entity<BreakpointStore>>,
1074 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1075 in_project_search: bool,
1076 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1077 breadcrumb_header: Option<String>,
1078 focused_block: Option<FocusedBlock>,
1079 next_scroll_position: NextScrollCursorCenterTopBottom,
1080 addons: HashMap<TypeId, Box<dyn Addon>>,
1081 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1082 load_diff_task: Option<Shared<Task<()>>>,
1083 /// Whether we are temporarily displaying a diff other than git's
1084 temporary_diff_override: bool,
1085 selection_mark_mode: bool,
1086 toggle_fold_multiple_buffers: Task<()>,
1087 _scroll_cursor_center_top_bottom_task: Task<()>,
1088 serialize_selections: Task<()>,
1089 serialize_folds: Task<()>,
1090 mouse_cursor_hidden: bool,
1091 minimap: Option<Entity<Self>>,
1092 hide_mouse_mode: HideMouseMode,
1093 pub change_list: ChangeList,
1094 inline_value_cache: InlineValueCache,
1095}
1096
1097#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1098enum NextScrollCursorCenterTopBottom {
1099 #[default]
1100 Center,
1101 Top,
1102 Bottom,
1103}
1104
1105impl NextScrollCursorCenterTopBottom {
1106 fn next(&self) -> Self {
1107 match self {
1108 Self::Center => Self::Top,
1109 Self::Top => Self::Bottom,
1110 Self::Bottom => Self::Center,
1111 }
1112 }
1113}
1114
1115#[derive(Clone)]
1116pub struct EditorSnapshot {
1117 pub mode: EditorMode,
1118 show_gutter: bool,
1119 show_line_numbers: Option<bool>,
1120 show_git_diff_gutter: Option<bool>,
1121 show_code_actions: Option<bool>,
1122 show_runnables: Option<bool>,
1123 show_breakpoints: Option<bool>,
1124 git_blame_gutter_max_author_length: Option<usize>,
1125 pub display_snapshot: DisplaySnapshot,
1126 pub placeholder_text: Option<Arc<str>>,
1127 is_focused: bool,
1128 scroll_anchor: ScrollAnchor,
1129 ongoing_scroll: OngoingScroll,
1130 current_line_highlight: CurrentLineHighlight,
1131 gutter_hovered: bool,
1132}
1133
1134#[derive(Default, Debug, Clone, Copy)]
1135pub struct GutterDimensions {
1136 pub left_padding: Pixels,
1137 pub right_padding: Pixels,
1138 pub width: Pixels,
1139 pub margin: Pixels,
1140 pub git_blame_entries_width: Option<Pixels>,
1141}
1142
1143impl GutterDimensions {
1144 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1145 Self {
1146 margin: Self::default_gutter_margin(font_id, font_size, cx),
1147 ..Default::default()
1148 }
1149 }
1150
1151 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1152 -cx.text_system().descent(font_id, font_size)
1153 }
1154 /// The full width of the space taken up by the gutter.
1155 pub fn full_width(&self) -> Pixels {
1156 self.margin + self.width
1157 }
1158
1159 /// The width of the space reserved for the fold indicators,
1160 /// use alongside 'justify_end' and `gutter_width` to
1161 /// right align content with the line numbers
1162 pub fn fold_area_width(&self) -> Pixels {
1163 self.margin + self.right_padding
1164 }
1165}
1166
1167#[derive(Debug)]
1168pub struct RemoteSelection {
1169 pub replica_id: ReplicaId,
1170 pub selection: Selection<Anchor>,
1171 pub cursor_shape: CursorShape,
1172 pub collaborator_id: CollaboratorId,
1173 pub line_mode: bool,
1174 pub user_name: Option<SharedString>,
1175 pub color: PlayerColor,
1176}
1177
1178#[derive(Clone, Debug)]
1179struct SelectionHistoryEntry {
1180 selections: Arc<[Selection<Anchor>]>,
1181 select_next_state: Option<SelectNextState>,
1182 select_prev_state: Option<SelectNextState>,
1183 add_selections_state: Option<AddSelectionsState>,
1184}
1185
1186enum SelectionHistoryMode {
1187 Normal,
1188 Undoing,
1189 Redoing,
1190}
1191
1192#[derive(Clone, PartialEq, Eq, Hash)]
1193struct HoveredCursor {
1194 replica_id: u16,
1195 selection_id: usize,
1196}
1197
1198impl Default for SelectionHistoryMode {
1199 fn default() -> Self {
1200 Self::Normal
1201 }
1202}
1203
1204struct DeferredSelectionEffectsState {
1205 changed: bool,
1206 should_update_completions: bool,
1207 autoscroll: Option<Autoscroll>,
1208 old_cursor_position: Anchor,
1209 history_entry: SelectionHistoryEntry,
1210}
1211
1212#[derive(Default)]
1213struct SelectionHistory {
1214 #[allow(clippy::type_complexity)]
1215 selections_by_transaction:
1216 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1217 mode: SelectionHistoryMode,
1218 undo_stack: VecDeque<SelectionHistoryEntry>,
1219 redo_stack: VecDeque<SelectionHistoryEntry>,
1220}
1221
1222impl SelectionHistory {
1223 fn insert_transaction(
1224 &mut self,
1225 transaction_id: TransactionId,
1226 selections: Arc<[Selection<Anchor>]>,
1227 ) {
1228 self.selections_by_transaction
1229 .insert(transaction_id, (selections, None));
1230 }
1231
1232 #[allow(clippy::type_complexity)]
1233 fn transaction(
1234 &self,
1235 transaction_id: TransactionId,
1236 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1237 self.selections_by_transaction.get(&transaction_id)
1238 }
1239
1240 #[allow(clippy::type_complexity)]
1241 fn transaction_mut(
1242 &mut self,
1243 transaction_id: TransactionId,
1244 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1245 self.selections_by_transaction.get_mut(&transaction_id)
1246 }
1247
1248 fn push(&mut self, entry: SelectionHistoryEntry) {
1249 if !entry.selections.is_empty() {
1250 match self.mode {
1251 SelectionHistoryMode::Normal => {
1252 self.push_undo(entry);
1253 self.redo_stack.clear();
1254 }
1255 SelectionHistoryMode::Undoing => self.push_redo(entry),
1256 SelectionHistoryMode::Redoing => self.push_undo(entry),
1257 }
1258 }
1259 }
1260
1261 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1262 if self
1263 .undo_stack
1264 .back()
1265 .map_or(true, |e| e.selections != entry.selections)
1266 {
1267 self.undo_stack.push_back(entry);
1268 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1269 self.undo_stack.pop_front();
1270 }
1271 }
1272 }
1273
1274 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1275 if self
1276 .redo_stack
1277 .back()
1278 .map_or(true, |e| e.selections != entry.selections)
1279 {
1280 self.redo_stack.push_back(entry);
1281 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1282 self.redo_stack.pop_front();
1283 }
1284 }
1285 }
1286}
1287
1288#[derive(Clone, Copy)]
1289pub struct RowHighlightOptions {
1290 pub autoscroll: bool,
1291 pub include_gutter: bool,
1292}
1293
1294impl Default for RowHighlightOptions {
1295 fn default() -> Self {
1296 Self {
1297 autoscroll: Default::default(),
1298 include_gutter: true,
1299 }
1300 }
1301}
1302
1303struct RowHighlight {
1304 index: usize,
1305 range: Range<Anchor>,
1306 color: Hsla,
1307 options: RowHighlightOptions,
1308 type_id: TypeId,
1309}
1310
1311#[derive(Clone, Debug)]
1312struct AddSelectionsState {
1313 above: bool,
1314 stack: Vec<usize>,
1315}
1316
1317#[derive(Clone)]
1318struct SelectNextState {
1319 query: AhoCorasick,
1320 wordwise: bool,
1321 done: bool,
1322}
1323
1324impl std::fmt::Debug for SelectNextState {
1325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1326 f.debug_struct(std::any::type_name::<Self>())
1327 .field("wordwise", &self.wordwise)
1328 .field("done", &self.done)
1329 .finish()
1330 }
1331}
1332
1333#[derive(Debug)]
1334struct AutocloseRegion {
1335 selection_id: usize,
1336 range: Range<Anchor>,
1337 pair: BracketPair,
1338}
1339
1340#[derive(Debug)]
1341struct SnippetState {
1342 ranges: Vec<Vec<Range<Anchor>>>,
1343 active_index: usize,
1344 choices: Vec<Option<Vec<String>>>,
1345}
1346
1347#[doc(hidden)]
1348pub struct RenameState {
1349 pub range: Range<Anchor>,
1350 pub old_name: Arc<str>,
1351 pub editor: Entity<Editor>,
1352 block_id: CustomBlockId,
1353}
1354
1355struct InvalidationStack<T>(Vec<T>);
1356
1357struct RegisteredInlineCompletionProvider {
1358 provider: Arc<dyn InlineCompletionProviderHandle>,
1359 _subscription: Subscription,
1360}
1361
1362#[derive(Debug, PartialEq, Eq)]
1363pub struct ActiveDiagnosticGroup {
1364 pub active_range: Range<Anchor>,
1365 pub active_message: String,
1366 pub group_id: usize,
1367 pub blocks: HashSet<CustomBlockId>,
1368}
1369
1370#[derive(Debug, PartialEq, Eq)]
1371
1372pub(crate) enum ActiveDiagnostic {
1373 None,
1374 All,
1375 Group(ActiveDiagnosticGroup),
1376}
1377
1378#[derive(Serialize, Deserialize, Clone, Debug)]
1379pub struct ClipboardSelection {
1380 /// The number of bytes in this selection.
1381 pub len: usize,
1382 /// Whether this was a full-line selection.
1383 pub is_entire_line: bool,
1384 /// The indentation of the first line when this content was originally copied.
1385 pub first_line_indent: u32,
1386}
1387
1388// selections, scroll behavior, was newest selection reversed
1389type SelectSyntaxNodeHistoryState = (
1390 Box<[Selection<usize>]>,
1391 SelectSyntaxNodeScrollBehavior,
1392 bool,
1393);
1394
1395#[derive(Default)]
1396struct SelectSyntaxNodeHistory {
1397 stack: Vec<SelectSyntaxNodeHistoryState>,
1398 // disable temporarily to allow changing selections without losing the stack
1399 pub disable_clearing: bool,
1400}
1401
1402impl SelectSyntaxNodeHistory {
1403 pub fn try_clear(&mut self) {
1404 if !self.disable_clearing {
1405 self.stack.clear();
1406 }
1407 }
1408
1409 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1410 self.stack.push(selection);
1411 }
1412
1413 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1414 self.stack.pop()
1415 }
1416}
1417
1418enum SelectSyntaxNodeScrollBehavior {
1419 CursorTop,
1420 FitSelection,
1421 CursorBottom,
1422}
1423
1424#[derive(Debug)]
1425pub(crate) struct NavigationData {
1426 cursor_anchor: Anchor,
1427 cursor_position: Point,
1428 scroll_anchor: ScrollAnchor,
1429 scroll_top_row: u32,
1430}
1431
1432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1433pub enum GotoDefinitionKind {
1434 Symbol,
1435 Declaration,
1436 Type,
1437 Implementation,
1438}
1439
1440#[derive(Debug, Clone)]
1441enum InlayHintRefreshReason {
1442 ModifiersChanged(bool),
1443 Toggle(bool),
1444 SettingsChange(InlayHintSettings),
1445 NewLinesShown,
1446 BufferEdited(HashSet<Arc<Language>>),
1447 RefreshRequested,
1448 ExcerptsRemoved(Vec<ExcerptId>),
1449}
1450
1451impl InlayHintRefreshReason {
1452 fn description(&self) -> &'static str {
1453 match self {
1454 Self::ModifiersChanged(_) => "modifiers changed",
1455 Self::Toggle(_) => "toggle",
1456 Self::SettingsChange(_) => "settings change",
1457 Self::NewLinesShown => "new lines shown",
1458 Self::BufferEdited(_) => "buffer edited",
1459 Self::RefreshRequested => "refresh requested",
1460 Self::ExcerptsRemoved(_) => "excerpts removed",
1461 }
1462 }
1463}
1464
1465pub enum FormatTarget {
1466 Buffers,
1467 Ranges(Vec<Range<MultiBufferPoint>>),
1468}
1469
1470pub(crate) struct FocusedBlock {
1471 id: BlockId,
1472 focus_handle: WeakFocusHandle,
1473}
1474
1475#[derive(Clone)]
1476enum JumpData {
1477 MultiBufferRow {
1478 row: MultiBufferRow,
1479 line_offset_from_top: u32,
1480 },
1481 MultiBufferPoint {
1482 excerpt_id: ExcerptId,
1483 position: Point,
1484 anchor: text::Anchor,
1485 line_offset_from_top: u32,
1486 },
1487}
1488
1489pub enum MultibufferSelectionMode {
1490 First,
1491 All,
1492}
1493
1494#[derive(Clone, Copy, Debug, Default)]
1495pub struct RewrapOptions {
1496 pub override_language_settings: bool,
1497 pub preserve_existing_whitespace: bool,
1498}
1499
1500impl Editor {
1501 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1502 let buffer = cx.new(|cx| Buffer::local("", cx));
1503 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1504 Self::new(
1505 EditorMode::SingleLine { auto_width: false },
1506 buffer,
1507 None,
1508 window,
1509 cx,
1510 )
1511 }
1512
1513 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1514 let buffer = cx.new(|cx| Buffer::local("", cx));
1515 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1516 Self::new(EditorMode::full(), buffer, None, window, cx)
1517 }
1518
1519 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1520 let buffer = cx.new(|cx| Buffer::local("", cx));
1521 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1522 Self::new(
1523 EditorMode::SingleLine { auto_width: true },
1524 buffer,
1525 None,
1526 window,
1527 cx,
1528 )
1529 }
1530
1531 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1532 let buffer = cx.new(|cx| Buffer::local("", cx));
1533 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1534 Self::new(
1535 EditorMode::AutoHeight { max_lines },
1536 buffer,
1537 None,
1538 window,
1539 cx,
1540 )
1541 }
1542
1543 pub fn for_buffer(
1544 buffer: Entity<Buffer>,
1545 project: Option<Entity<Project>>,
1546 window: &mut Window,
1547 cx: &mut Context<Self>,
1548 ) -> Self {
1549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1550 Self::new(EditorMode::full(), buffer, project, window, cx)
1551 }
1552
1553 pub fn for_multibuffer(
1554 buffer: Entity<MultiBuffer>,
1555 project: Option<Entity<Project>>,
1556 window: &mut Window,
1557 cx: &mut Context<Self>,
1558 ) -> Self {
1559 Self::new(EditorMode::full(), buffer, project, window, cx)
1560 }
1561
1562 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1563 let mut clone = Self::new(
1564 self.mode.clone(),
1565 self.buffer.clone(),
1566 self.project.clone(),
1567 window,
1568 cx,
1569 );
1570 self.display_map.update(cx, |display_map, cx| {
1571 let snapshot = display_map.snapshot(cx);
1572 clone.display_map.update(cx, |display_map, cx| {
1573 display_map.set_state(&snapshot, cx);
1574 });
1575 });
1576 clone.folds_did_change(cx);
1577 clone.selections.clone_state(&self.selections);
1578 clone.scroll_manager.clone_state(&self.scroll_manager);
1579 clone.searchable = self.searchable;
1580 clone.read_only = self.read_only;
1581 clone
1582 }
1583
1584 pub fn new(
1585 mode: EditorMode,
1586 buffer: Entity<MultiBuffer>,
1587 project: Option<Entity<Project>>,
1588 window: &mut Window,
1589 cx: &mut Context<Self>,
1590 ) -> Self {
1591 Editor::new_internal(mode, buffer, project, None, window, cx)
1592 }
1593
1594 fn new_internal(
1595 mode: EditorMode,
1596 buffer: Entity<MultiBuffer>,
1597 project: Option<Entity<Project>>,
1598 display_map: Option<Entity<DisplayMap>>,
1599 window: &mut Window,
1600 cx: &mut Context<Self>,
1601 ) -> Self {
1602 debug_assert!(
1603 display_map.is_none() || mode.is_minimap(),
1604 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1605 );
1606
1607 let full_mode = mode.is_full();
1608 let diagnostics_max_severity = if full_mode {
1609 EditorSettings::get_global(cx)
1610 .diagnostics_max_severity
1611 .unwrap_or(DiagnosticSeverity::Hint)
1612 } else {
1613 DiagnosticSeverity::Off
1614 };
1615 let style = window.text_style();
1616 let font_size = style.font_size.to_pixels(window.rem_size());
1617 let editor = cx.entity().downgrade();
1618 let fold_placeholder = FoldPlaceholder {
1619 constrain_width: true,
1620 render: Arc::new(move |fold_id, fold_range, cx| {
1621 let editor = editor.clone();
1622 div()
1623 .id(fold_id)
1624 .bg(cx.theme().colors().ghost_element_background)
1625 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1626 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1627 .rounded_xs()
1628 .size_full()
1629 .cursor_pointer()
1630 .child("⋯")
1631 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1632 .on_click(move |_, _window, cx| {
1633 editor
1634 .update(cx, |editor, cx| {
1635 editor.unfold_ranges(
1636 &[fold_range.start..fold_range.end],
1637 true,
1638 false,
1639 cx,
1640 );
1641 cx.stop_propagation();
1642 })
1643 .ok();
1644 })
1645 .into_any()
1646 }),
1647 merge_adjacent: true,
1648 ..FoldPlaceholder::default()
1649 };
1650 let display_map = display_map.unwrap_or_else(|| {
1651 cx.new(|cx| {
1652 DisplayMap::new(
1653 buffer.clone(),
1654 style.font(),
1655 font_size,
1656 None,
1657 FILE_HEADER_HEIGHT,
1658 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1659 fold_placeholder,
1660 diagnostics_max_severity,
1661 cx,
1662 )
1663 })
1664 });
1665
1666 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1667
1668 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1669
1670 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1671 .then(|| language_settings::SoftWrap::None);
1672
1673 let mut project_subscriptions = Vec::new();
1674 if mode.is_full() {
1675 if let Some(project) = project.as_ref() {
1676 project_subscriptions.push(cx.subscribe_in(
1677 project,
1678 window,
1679 |editor, _, event, window, cx| match event {
1680 project::Event::RefreshCodeLens => {
1681 // we always query lens with actions, without storing them, always refreshing them
1682 }
1683 project::Event::RefreshInlayHints => {
1684 editor
1685 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1686 }
1687 project::Event::LanguageServerAdded(..)
1688 | project::Event::LanguageServerRemoved(..) => {
1689 if editor.tasks_update_task.is_none() {
1690 editor.tasks_update_task =
1691 Some(editor.refresh_runnables(window, cx));
1692 }
1693 }
1694 project::Event::SnippetEdit(id, snippet_edits) => {
1695 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1696 let focus_handle = editor.focus_handle(cx);
1697 if focus_handle.is_focused(window) {
1698 let snapshot = buffer.read(cx).snapshot();
1699 for (range, snippet) in snippet_edits {
1700 let editor_range =
1701 language::range_from_lsp(*range).to_offset(&snapshot);
1702 editor
1703 .insert_snippet(
1704 &[editor_range],
1705 snippet.clone(),
1706 window,
1707 cx,
1708 )
1709 .ok();
1710 }
1711 }
1712 }
1713 }
1714 _ => {}
1715 },
1716 ));
1717 if let Some(task_inventory) = project
1718 .read(cx)
1719 .task_store()
1720 .read(cx)
1721 .task_inventory()
1722 .cloned()
1723 {
1724 project_subscriptions.push(cx.observe_in(
1725 &task_inventory,
1726 window,
1727 |editor, _, window, cx| {
1728 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1729 },
1730 ));
1731 };
1732
1733 project_subscriptions.push(cx.subscribe_in(
1734 &project.read(cx).breakpoint_store(),
1735 window,
1736 |editor, _, event, window, cx| match event {
1737 BreakpointStoreEvent::ClearDebugLines => {
1738 editor.clear_row_highlights::<ActiveDebugLine>();
1739 editor.refresh_inline_values(cx);
1740 }
1741 BreakpointStoreEvent::SetDebugLine => {
1742 if editor.go_to_active_debug_line(window, cx) {
1743 cx.stop_propagation();
1744 }
1745
1746 editor.refresh_inline_values(cx);
1747 }
1748 _ => {}
1749 },
1750 ));
1751 }
1752 }
1753
1754 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1755
1756 let inlay_hint_settings =
1757 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1758 let focus_handle = cx.focus_handle();
1759 cx.on_focus(&focus_handle, window, Self::handle_focus)
1760 .detach();
1761 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1762 .detach();
1763 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1764 .detach();
1765 cx.on_blur(&focus_handle, window, Self::handle_blur)
1766 .detach();
1767
1768 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1769 Some(false)
1770 } else {
1771 None
1772 };
1773
1774 let breakpoint_store = match (&mode, project.as_ref()) {
1775 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1776 _ => None,
1777 };
1778
1779 let mut code_action_providers = Vec::new();
1780 let mut load_uncommitted_diff = None;
1781 if let Some(project) = project.clone() {
1782 load_uncommitted_diff = Some(
1783 update_uncommitted_diff_for_buffer(
1784 cx.entity(),
1785 &project,
1786 buffer.read(cx).all_buffers(),
1787 buffer.clone(),
1788 cx,
1789 )
1790 .shared(),
1791 );
1792 code_action_providers.push(Rc::new(project) as Rc<_>);
1793 }
1794
1795 let mut this = Self {
1796 focus_handle,
1797 show_cursor_when_unfocused: false,
1798 last_focused_descendant: None,
1799 buffer: buffer.clone(),
1800 display_map: display_map.clone(),
1801 selections,
1802 scroll_manager: ScrollManager::new(cx),
1803 columnar_selection_tail: None,
1804 columnar_display_point: None,
1805 add_selections_state: None,
1806 select_next_state: None,
1807 select_prev_state: None,
1808 selection_history: SelectionHistory::default(),
1809 defer_selection_effects: false,
1810 deferred_selection_effects_state: None,
1811 autoclose_regions: Vec::new(),
1812 snippet_stack: InvalidationStack::default(),
1813 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1814 ime_transaction: None,
1815 active_diagnostics: ActiveDiagnostic::None,
1816 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1817 inline_diagnostics_update: Task::ready(()),
1818 inline_diagnostics: Vec::new(),
1819 soft_wrap_mode_override,
1820 diagnostics_max_severity,
1821 hard_wrap: None,
1822 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1823 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1824 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1825 project,
1826 blink_manager: blink_manager.clone(),
1827 show_local_selections: true,
1828 show_scrollbars: ScrollbarAxes {
1829 horizontal: full_mode,
1830 vertical: full_mode,
1831 },
1832 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1833 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1834 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1835 show_gutter: mode.is_full(),
1836 show_line_numbers: None,
1837 use_relative_line_numbers: None,
1838 disable_expand_excerpt_buttons: false,
1839 show_git_diff_gutter: None,
1840 show_code_actions: None,
1841 show_runnables: None,
1842 show_breakpoints: None,
1843 show_wrap_guides: None,
1844 show_indent_guides,
1845 placeholder_text: None,
1846 highlight_order: 0,
1847 highlighted_rows: HashMap::default(),
1848 background_highlights: TreeMap::default(),
1849 gutter_highlights: TreeMap::default(),
1850 scrollbar_marker_state: ScrollbarMarkerState::default(),
1851 active_indent_guides_state: ActiveIndentGuidesState::default(),
1852 nav_history: None,
1853 context_menu: RefCell::new(None),
1854 context_menu_options: None,
1855 mouse_context_menu: None,
1856 completion_tasks: Vec::new(),
1857 inline_blame_popover: None,
1858 signature_help_state: SignatureHelpState::default(),
1859 auto_signature_help: None,
1860 find_all_references_task_sources: Vec::new(),
1861 next_completion_id: 0,
1862 next_inlay_id: 0,
1863 code_action_providers,
1864 available_code_actions: None,
1865 code_actions_task: None,
1866 quick_selection_highlight_task: None,
1867 debounced_selection_highlight_task: None,
1868 document_highlights_task: None,
1869 linked_editing_range_task: None,
1870 pending_rename: None,
1871 searchable: true,
1872 cursor_shape: EditorSettings::get_global(cx)
1873 .cursor_shape
1874 .unwrap_or_default(),
1875 current_line_highlight: None,
1876 autoindent_mode: Some(AutoindentMode::EachLine),
1877 collapse_matches: false,
1878 workspace: None,
1879 input_enabled: true,
1880 use_modal_editing: mode.is_full(),
1881 read_only: mode.is_minimap(),
1882 use_autoclose: true,
1883 use_auto_surround: true,
1884 auto_replace_emoji_shortcode: false,
1885 jsx_tag_auto_close_enabled_in_any_buffer: false,
1886 leader_id: None,
1887 remote_id: None,
1888 hover_state: HoverState::default(),
1889 pending_mouse_down: None,
1890 hovered_link_state: None,
1891 edit_prediction_provider: None,
1892 active_inline_completion: None,
1893 stale_inline_completion_in_menu: None,
1894 edit_prediction_preview: EditPredictionPreview::Inactive {
1895 released_too_fast: false,
1896 },
1897 inline_diagnostics_enabled: mode.is_full(),
1898 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1899 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1900
1901 gutter_hovered: false,
1902 pixel_position_of_newest_cursor: None,
1903 last_bounds: None,
1904 last_position_map: None,
1905 expect_bounds_change: None,
1906 gutter_dimensions: GutterDimensions::default(),
1907 style: None,
1908 show_cursor_names: false,
1909 hovered_cursors: HashMap::default(),
1910 next_editor_action_id: EditorActionId::default(),
1911 editor_actions: Rc::default(),
1912 inline_completions_hidden_for_vim_mode: false,
1913 show_inline_completions_override: None,
1914 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1915 edit_prediction_settings: EditPredictionSettings::Disabled,
1916 edit_prediction_indent_conflict: false,
1917 edit_prediction_requires_modifier_in_indent_conflict: true,
1918 custom_context_menu: None,
1919 show_git_blame_gutter: false,
1920 show_git_blame_inline: false,
1921 show_selection_menu: None,
1922 show_git_blame_inline_delay_task: None,
1923 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1924 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1925 serialize_dirty_buffers: !mode.is_minimap()
1926 && ProjectSettings::get_global(cx)
1927 .session
1928 .restore_unsaved_buffers,
1929 blame: None,
1930 blame_subscription: None,
1931 tasks: BTreeMap::default(),
1932
1933 breakpoint_store,
1934 gutter_breakpoint_indicator: (None, None),
1935 _subscriptions: vec![
1936 cx.observe(&buffer, Self::on_buffer_changed),
1937 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1938 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1939 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1940 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1941 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1942 cx.observe_window_activation(window, |editor, window, cx| {
1943 let active = window.is_window_active();
1944 editor.blink_manager.update(cx, |blink_manager, cx| {
1945 if active {
1946 blink_manager.enable(cx);
1947 } else {
1948 blink_manager.disable(cx);
1949 }
1950 });
1951 if active {
1952 editor.show_mouse_cursor();
1953 }
1954 }),
1955 ],
1956 tasks_update_task: None,
1957 linked_edit_ranges: Default::default(),
1958 in_project_search: false,
1959 previous_search_ranges: None,
1960 breadcrumb_header: None,
1961 focused_block: None,
1962 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1963 addons: HashMap::default(),
1964 registered_buffers: HashMap::default(),
1965 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1966 selection_mark_mode: false,
1967 toggle_fold_multiple_buffers: Task::ready(()),
1968 serialize_selections: Task::ready(()),
1969 serialize_folds: Task::ready(()),
1970 text_style_refinement: None,
1971 load_diff_task: load_uncommitted_diff,
1972 temporary_diff_override: false,
1973 mouse_cursor_hidden: false,
1974 minimap: None,
1975 hide_mouse_mode: EditorSettings::get_global(cx)
1976 .hide_mouse
1977 .unwrap_or_default(),
1978 change_list: ChangeList::new(),
1979 mode,
1980 };
1981 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1982 this._subscriptions
1983 .push(cx.observe(breakpoints, |_, _, cx| {
1984 cx.notify();
1985 }));
1986 }
1987 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1988 this._subscriptions.extend(project_subscriptions);
1989
1990 this._subscriptions.push(cx.subscribe_in(
1991 &cx.entity(),
1992 window,
1993 |editor, _, e: &EditorEvent, window, cx| match e {
1994 EditorEvent::ScrollPositionChanged { local, .. } => {
1995 if *local {
1996 let new_anchor = editor.scroll_manager.anchor();
1997 let snapshot = editor.snapshot(window, cx);
1998 editor.update_restoration_data(cx, move |data| {
1999 data.scroll_position = (
2000 new_anchor.top_row(&snapshot.buffer_snapshot),
2001 new_anchor.offset,
2002 );
2003 });
2004 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2005 editor.inline_blame_popover.take();
2006 }
2007 }
2008 EditorEvent::Edited { .. } => {
2009 if !vim_enabled(cx) {
2010 let (map, selections) = editor.selections.all_adjusted_display(cx);
2011 let pop_state = editor
2012 .change_list
2013 .last()
2014 .map(|previous| {
2015 previous.len() == selections.len()
2016 && previous.iter().enumerate().all(|(ix, p)| {
2017 p.to_display_point(&map).row()
2018 == selections[ix].head().row()
2019 })
2020 })
2021 .unwrap_or(false);
2022 let new_positions = selections
2023 .into_iter()
2024 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2025 .collect();
2026 editor
2027 .change_list
2028 .push_to_change_list(pop_state, new_positions);
2029 }
2030 }
2031 _ => (),
2032 },
2033 ));
2034
2035 if let Some(dap_store) = this
2036 .project
2037 .as_ref()
2038 .map(|project| project.read(cx).dap_store())
2039 {
2040 let weak_editor = cx.weak_entity();
2041
2042 this._subscriptions
2043 .push(
2044 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2045 let session_entity = cx.entity();
2046 weak_editor
2047 .update(cx, |editor, cx| {
2048 editor._subscriptions.push(
2049 cx.subscribe(&session_entity, Self::on_debug_session_event),
2050 );
2051 })
2052 .ok();
2053 }),
2054 );
2055
2056 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2057 this._subscriptions
2058 .push(cx.subscribe(&session, Self::on_debug_session_event));
2059 }
2060 }
2061
2062 this.end_selection(window, cx);
2063 this.scroll_manager.show_scrollbars(window, cx);
2064 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2065
2066 if full_mode {
2067 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2068 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2069
2070 if this.git_blame_inline_enabled {
2071 this.start_git_blame_inline(false, window, cx);
2072 }
2073
2074 this.go_to_active_debug_line(window, cx);
2075
2076 if let Some(buffer) = buffer.read(cx).as_singleton() {
2077 if let Some(project) = this.project.as_ref() {
2078 let handle = project.update(cx, |project, cx| {
2079 project.register_buffer_with_language_servers(&buffer, cx)
2080 });
2081 this.registered_buffers
2082 .insert(buffer.read(cx).remote_id(), handle);
2083 }
2084 }
2085
2086 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2087 }
2088
2089 this.report_editor_event("Editor Opened", None, cx);
2090 this
2091 }
2092
2093 pub fn deploy_mouse_context_menu(
2094 &mut self,
2095 position: gpui::Point<Pixels>,
2096 context_menu: Entity<ContextMenu>,
2097 window: &mut Window,
2098 cx: &mut Context<Self>,
2099 ) {
2100 self.mouse_context_menu = Some(MouseContextMenu::new(
2101 self,
2102 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2103 context_menu,
2104 window,
2105 cx,
2106 ));
2107 }
2108
2109 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2110 self.mouse_context_menu
2111 .as_ref()
2112 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2113 }
2114
2115 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2116 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2117 }
2118
2119 fn key_context_internal(
2120 &self,
2121 has_active_edit_prediction: bool,
2122 window: &Window,
2123 cx: &App,
2124 ) -> KeyContext {
2125 let mut key_context = KeyContext::new_with_defaults();
2126 key_context.add("Editor");
2127 let mode = match self.mode {
2128 EditorMode::SingleLine { .. } => "single_line",
2129 EditorMode::AutoHeight { .. } => "auto_height",
2130 EditorMode::Minimap { .. } => "minimap",
2131 EditorMode::Full { .. } => "full",
2132 };
2133
2134 if EditorSettings::jupyter_enabled(cx) {
2135 key_context.add("jupyter");
2136 }
2137
2138 key_context.set("mode", mode);
2139 if self.pending_rename.is_some() {
2140 key_context.add("renaming");
2141 }
2142
2143 match self.context_menu.borrow().as_ref() {
2144 Some(CodeContextMenu::Completions(_)) => {
2145 key_context.add("menu");
2146 key_context.add("showing_completions");
2147 }
2148 Some(CodeContextMenu::CodeActions(_)) => {
2149 key_context.add("menu");
2150 key_context.add("showing_code_actions")
2151 }
2152 None => {}
2153 }
2154
2155 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2156 if !self.focus_handle(cx).contains_focused(window, cx)
2157 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2158 {
2159 for addon in self.addons.values() {
2160 addon.extend_key_context(&mut key_context, cx)
2161 }
2162 }
2163
2164 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2165 if let Some(extension) = singleton_buffer
2166 .read(cx)
2167 .file()
2168 .and_then(|file| file.path().extension()?.to_str())
2169 {
2170 key_context.set("extension", extension.to_string());
2171 }
2172 } else {
2173 key_context.add("multibuffer");
2174 }
2175
2176 if has_active_edit_prediction {
2177 if self.edit_prediction_in_conflict() {
2178 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2179 } else {
2180 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2181 key_context.add("copilot_suggestion");
2182 }
2183 }
2184
2185 if self.selection_mark_mode {
2186 key_context.add("selection_mode");
2187 }
2188
2189 key_context
2190 }
2191
2192 fn show_mouse_cursor(&mut self) {
2193 self.mouse_cursor_hidden = false;
2194 }
2195
2196 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2197 self.mouse_cursor_hidden = match origin {
2198 HideMouseCursorOrigin::TypingAction => {
2199 matches!(
2200 self.hide_mouse_mode,
2201 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2202 )
2203 }
2204 HideMouseCursorOrigin::MovementAction => {
2205 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2206 }
2207 };
2208 }
2209
2210 pub fn edit_prediction_in_conflict(&self) -> bool {
2211 if !self.show_edit_predictions_in_menu() {
2212 return false;
2213 }
2214
2215 let showing_completions = self
2216 .context_menu
2217 .borrow()
2218 .as_ref()
2219 .map_or(false, |context| {
2220 matches!(context, CodeContextMenu::Completions(_))
2221 });
2222
2223 showing_completions
2224 || self.edit_prediction_requires_modifier()
2225 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2226 // bindings to insert tab characters.
2227 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2228 }
2229
2230 pub fn accept_edit_prediction_keybind(
2231 &self,
2232 window: &Window,
2233 cx: &App,
2234 ) -> AcceptEditPredictionBinding {
2235 let key_context = self.key_context_internal(true, window, cx);
2236 let in_conflict = self.edit_prediction_in_conflict();
2237
2238 AcceptEditPredictionBinding(
2239 window
2240 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2241 .into_iter()
2242 .filter(|binding| {
2243 !in_conflict
2244 || binding
2245 .keystrokes()
2246 .first()
2247 .map_or(false, |keystroke| keystroke.modifiers.modified())
2248 })
2249 .rev()
2250 .min_by_key(|binding| {
2251 binding
2252 .keystrokes()
2253 .first()
2254 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2255 }),
2256 )
2257 }
2258
2259 pub fn new_file(
2260 workspace: &mut Workspace,
2261 _: &workspace::NewFile,
2262 window: &mut Window,
2263 cx: &mut Context<Workspace>,
2264 ) {
2265 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2266 "Failed to create buffer",
2267 window,
2268 cx,
2269 |e, _, _| match e.error_code() {
2270 ErrorCode::RemoteUpgradeRequired => Some(format!(
2271 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2272 e.error_tag("required").unwrap_or("the latest version")
2273 )),
2274 _ => None,
2275 },
2276 );
2277 }
2278
2279 pub fn new_in_workspace(
2280 workspace: &mut Workspace,
2281 window: &mut Window,
2282 cx: &mut Context<Workspace>,
2283 ) -> Task<Result<Entity<Editor>>> {
2284 let project = workspace.project().clone();
2285 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2286
2287 cx.spawn_in(window, async move |workspace, cx| {
2288 let buffer = create.await?;
2289 workspace.update_in(cx, |workspace, window, cx| {
2290 let editor =
2291 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2292 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2293 editor
2294 })
2295 })
2296 }
2297
2298 fn new_file_vertical(
2299 workspace: &mut Workspace,
2300 _: &workspace::NewFileSplitVertical,
2301 window: &mut Window,
2302 cx: &mut Context<Workspace>,
2303 ) {
2304 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2305 }
2306
2307 fn new_file_horizontal(
2308 workspace: &mut Workspace,
2309 _: &workspace::NewFileSplitHorizontal,
2310 window: &mut Window,
2311 cx: &mut Context<Workspace>,
2312 ) {
2313 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2314 }
2315
2316 fn new_file_in_direction(
2317 workspace: &mut Workspace,
2318 direction: SplitDirection,
2319 window: &mut Window,
2320 cx: &mut Context<Workspace>,
2321 ) {
2322 let project = workspace.project().clone();
2323 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2324
2325 cx.spawn_in(window, async move |workspace, cx| {
2326 let buffer = create.await?;
2327 workspace.update_in(cx, move |workspace, window, cx| {
2328 workspace.split_item(
2329 direction,
2330 Box::new(
2331 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2332 ),
2333 window,
2334 cx,
2335 )
2336 })?;
2337 anyhow::Ok(())
2338 })
2339 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2340 match e.error_code() {
2341 ErrorCode::RemoteUpgradeRequired => Some(format!(
2342 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2343 e.error_tag("required").unwrap_or("the latest version")
2344 )),
2345 _ => None,
2346 }
2347 });
2348 }
2349
2350 pub fn leader_id(&self) -> Option<CollaboratorId> {
2351 self.leader_id
2352 }
2353
2354 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2355 &self.buffer
2356 }
2357
2358 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2359 self.workspace.as_ref()?.0.upgrade()
2360 }
2361
2362 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2363 self.buffer().read(cx).title(cx)
2364 }
2365
2366 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2367 let git_blame_gutter_max_author_length = self
2368 .render_git_blame_gutter(cx)
2369 .then(|| {
2370 if let Some(blame) = self.blame.as_ref() {
2371 let max_author_length =
2372 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2373 Some(max_author_length)
2374 } else {
2375 None
2376 }
2377 })
2378 .flatten();
2379
2380 EditorSnapshot {
2381 mode: self.mode.clone(),
2382 show_gutter: self.show_gutter,
2383 show_line_numbers: self.show_line_numbers,
2384 show_git_diff_gutter: self.show_git_diff_gutter,
2385 show_code_actions: self.show_code_actions,
2386 show_runnables: self.show_runnables,
2387 show_breakpoints: self.show_breakpoints,
2388 git_blame_gutter_max_author_length,
2389 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2390 scroll_anchor: self.scroll_manager.anchor(),
2391 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2392 placeholder_text: self.placeholder_text.clone(),
2393 is_focused: self.focus_handle.is_focused(window),
2394 current_line_highlight: self
2395 .current_line_highlight
2396 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2397 gutter_hovered: self.gutter_hovered,
2398 }
2399 }
2400
2401 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2402 self.buffer.read(cx).language_at(point, cx)
2403 }
2404
2405 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2406 self.buffer.read(cx).read(cx).file_at(point).cloned()
2407 }
2408
2409 pub fn active_excerpt(
2410 &self,
2411 cx: &App,
2412 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2413 self.buffer
2414 .read(cx)
2415 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2416 }
2417
2418 pub fn mode(&self) -> &EditorMode {
2419 &self.mode
2420 }
2421
2422 pub fn set_mode(&mut self, mode: EditorMode) {
2423 self.mode = mode;
2424 }
2425
2426 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2427 self.collaboration_hub.as_deref()
2428 }
2429
2430 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2431 self.collaboration_hub = Some(hub);
2432 }
2433
2434 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2435 self.in_project_search = in_project_search;
2436 }
2437
2438 pub fn set_custom_context_menu(
2439 &mut self,
2440 f: impl 'static
2441 + Fn(
2442 &mut Self,
2443 DisplayPoint,
2444 &mut Window,
2445 &mut Context<Self>,
2446 ) -> Option<Entity<ui::ContextMenu>>,
2447 ) {
2448 self.custom_context_menu = Some(Box::new(f))
2449 }
2450
2451 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2452 self.completion_provider = provider;
2453 }
2454
2455 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2456 self.semantics_provider.clone()
2457 }
2458
2459 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2460 self.semantics_provider = provider;
2461 }
2462
2463 pub fn set_edit_prediction_provider<T>(
2464 &mut self,
2465 provider: Option<Entity<T>>,
2466 window: &mut Window,
2467 cx: &mut Context<Self>,
2468 ) where
2469 T: EditPredictionProvider,
2470 {
2471 self.edit_prediction_provider =
2472 provider.map(|provider| RegisteredInlineCompletionProvider {
2473 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2474 if this.focus_handle.is_focused(window) {
2475 this.update_visible_inline_completion(window, cx);
2476 }
2477 }),
2478 provider: Arc::new(provider),
2479 });
2480 self.update_edit_prediction_settings(cx);
2481 self.refresh_inline_completion(false, false, window, cx);
2482 }
2483
2484 pub fn placeholder_text(&self) -> Option<&str> {
2485 self.placeholder_text.as_deref()
2486 }
2487
2488 pub fn set_placeholder_text(
2489 &mut self,
2490 placeholder_text: impl Into<Arc<str>>,
2491 cx: &mut Context<Self>,
2492 ) {
2493 let placeholder_text = Some(placeholder_text.into());
2494 if self.placeholder_text != placeholder_text {
2495 self.placeholder_text = placeholder_text;
2496 cx.notify();
2497 }
2498 }
2499
2500 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2501 self.cursor_shape = cursor_shape;
2502
2503 // Disrupt blink for immediate user feedback that the cursor shape has changed
2504 self.blink_manager.update(cx, BlinkManager::show_cursor);
2505
2506 cx.notify();
2507 }
2508
2509 pub fn set_current_line_highlight(
2510 &mut self,
2511 current_line_highlight: Option<CurrentLineHighlight>,
2512 ) {
2513 self.current_line_highlight = current_line_highlight;
2514 }
2515
2516 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2517 self.collapse_matches = collapse_matches;
2518 }
2519
2520 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2521 let buffers = self.buffer.read(cx).all_buffers();
2522 let Some(project) = self.project.as_ref() else {
2523 return;
2524 };
2525 project.update(cx, |project, cx| {
2526 for buffer in buffers {
2527 self.registered_buffers
2528 .entry(buffer.read(cx).remote_id())
2529 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2530 }
2531 })
2532 }
2533
2534 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2535 if self.collapse_matches {
2536 return range.start..range.start;
2537 }
2538 range.clone()
2539 }
2540
2541 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2542 if self.display_map.read(cx).clip_at_line_ends != clip {
2543 self.display_map
2544 .update(cx, |map, _| map.clip_at_line_ends = clip);
2545 }
2546 }
2547
2548 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2549 self.input_enabled = input_enabled;
2550 }
2551
2552 pub fn set_inline_completions_hidden_for_vim_mode(
2553 &mut self,
2554 hidden: bool,
2555 window: &mut Window,
2556 cx: &mut Context<Self>,
2557 ) {
2558 if hidden != self.inline_completions_hidden_for_vim_mode {
2559 self.inline_completions_hidden_for_vim_mode = hidden;
2560 if hidden {
2561 self.update_visible_inline_completion(window, cx);
2562 } else {
2563 self.refresh_inline_completion(true, false, window, cx);
2564 }
2565 }
2566 }
2567
2568 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2569 self.menu_inline_completions_policy = value;
2570 }
2571
2572 pub fn set_autoindent(&mut self, autoindent: bool) {
2573 if autoindent {
2574 self.autoindent_mode = Some(AutoindentMode::EachLine);
2575 } else {
2576 self.autoindent_mode = None;
2577 }
2578 }
2579
2580 pub fn read_only(&self, cx: &App) -> bool {
2581 self.read_only || self.buffer.read(cx).read_only()
2582 }
2583
2584 pub fn set_read_only(&mut self, read_only: bool) {
2585 self.read_only = read_only;
2586 }
2587
2588 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2589 self.use_autoclose = autoclose;
2590 }
2591
2592 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2593 self.use_auto_surround = auto_surround;
2594 }
2595
2596 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2597 self.auto_replace_emoji_shortcode = auto_replace;
2598 }
2599
2600 pub fn toggle_edit_predictions(
2601 &mut self,
2602 _: &ToggleEditPrediction,
2603 window: &mut Window,
2604 cx: &mut Context<Self>,
2605 ) {
2606 if self.show_inline_completions_override.is_some() {
2607 self.set_show_edit_predictions(None, window, cx);
2608 } else {
2609 let show_edit_predictions = !self.edit_predictions_enabled();
2610 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2611 }
2612 }
2613
2614 pub fn set_show_edit_predictions(
2615 &mut self,
2616 show_edit_predictions: Option<bool>,
2617 window: &mut Window,
2618 cx: &mut Context<Self>,
2619 ) {
2620 self.show_inline_completions_override = show_edit_predictions;
2621 self.update_edit_prediction_settings(cx);
2622
2623 if let Some(false) = show_edit_predictions {
2624 self.discard_inline_completion(false, cx);
2625 } else {
2626 self.refresh_inline_completion(false, true, window, cx);
2627 }
2628 }
2629
2630 fn inline_completions_disabled_in_scope(
2631 &self,
2632 buffer: &Entity<Buffer>,
2633 buffer_position: language::Anchor,
2634 cx: &App,
2635 ) -> bool {
2636 let snapshot = buffer.read(cx).snapshot();
2637 let settings = snapshot.settings_at(buffer_position, cx);
2638
2639 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2640 return false;
2641 };
2642
2643 scope.override_name().map_or(false, |scope_name| {
2644 settings
2645 .edit_predictions_disabled_in
2646 .iter()
2647 .any(|s| s == scope_name)
2648 })
2649 }
2650
2651 pub fn set_use_modal_editing(&mut self, to: bool) {
2652 self.use_modal_editing = to;
2653 }
2654
2655 pub fn use_modal_editing(&self) -> bool {
2656 self.use_modal_editing
2657 }
2658
2659 fn selections_did_change(
2660 &mut self,
2661 local: bool,
2662 old_cursor_position: &Anchor,
2663 should_update_completions: bool,
2664 window: &mut Window,
2665 cx: &mut Context<Self>,
2666 ) {
2667 window.invalidate_character_coordinates();
2668
2669 // Copy selections to primary selection buffer
2670 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2671 if local {
2672 let selections = self.selections.all::<usize>(cx);
2673 let buffer_handle = self.buffer.read(cx).read(cx);
2674
2675 let mut text = String::new();
2676 for (index, selection) in selections.iter().enumerate() {
2677 let text_for_selection = buffer_handle
2678 .text_for_range(selection.start..selection.end)
2679 .collect::<String>();
2680
2681 text.push_str(&text_for_selection);
2682 if index != selections.len() - 1 {
2683 text.push('\n');
2684 }
2685 }
2686
2687 if !text.is_empty() {
2688 cx.write_to_primary(ClipboardItem::new_string(text));
2689 }
2690 }
2691
2692 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2693 self.buffer.update(cx, |buffer, cx| {
2694 buffer.set_active_selections(
2695 &self.selections.disjoint_anchors(),
2696 self.selections.line_mode,
2697 self.cursor_shape,
2698 cx,
2699 )
2700 });
2701 }
2702 let display_map = self
2703 .display_map
2704 .update(cx, |display_map, cx| display_map.snapshot(cx));
2705 let buffer = &display_map.buffer_snapshot;
2706 self.add_selections_state = None;
2707 self.select_next_state = None;
2708 self.select_prev_state = None;
2709 self.select_syntax_node_history.try_clear();
2710 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2711 self.snippet_stack
2712 .invalidate(&self.selections.disjoint_anchors(), buffer);
2713 self.take_rename(false, window, cx);
2714
2715 let new_cursor_position = self.selections.newest_anchor().head();
2716
2717 self.push_to_nav_history(
2718 *old_cursor_position,
2719 Some(new_cursor_position.to_point(buffer)),
2720 false,
2721 cx,
2722 );
2723
2724 if local {
2725 let new_cursor_position = self.selections.newest_anchor().head();
2726
2727 if let Some(buffer_id) = new_cursor_position.buffer_id {
2728 if !self.registered_buffers.contains_key(&buffer_id) {
2729 if let Some(project) = self.project.as_ref() {
2730 project.update(cx, |project, cx| {
2731 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2732 return;
2733 };
2734 self.registered_buffers.insert(
2735 buffer_id,
2736 project.register_buffer_with_language_servers(&buffer, cx),
2737 );
2738 })
2739 }
2740 }
2741 }
2742
2743 let mut context_menu = self.context_menu.borrow_mut();
2744 let completion_menu = match context_menu.as_ref() {
2745 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2746 Some(CodeContextMenu::CodeActions(_)) => {
2747 *context_menu = None;
2748 None
2749 }
2750 None => None,
2751 };
2752 let completion_position = completion_menu.map(|menu| menu.initial_position);
2753 drop(context_menu);
2754
2755 if should_update_completions {
2756 if let Some(completion_position) = completion_position {
2757 let new_cursor_offset = new_cursor_position.to_offset(buffer);
2758 let position_matches =
2759 new_cursor_offset == completion_position.to_offset(buffer);
2760 let continue_showing = if position_matches {
2761 let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true);
2762 if let Some(CharKind::Word) = kind {
2763 word_range.start < new_cursor_offset
2764 } else {
2765 false
2766 }
2767 } else {
2768 false
2769 };
2770
2771 if continue_showing {
2772 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2773 } else {
2774 self.hide_context_menu(window, cx);
2775 }
2776 }
2777 }
2778
2779 hide_hover(self, cx);
2780
2781 if old_cursor_position.to_display_point(&display_map).row()
2782 != new_cursor_position.to_display_point(&display_map).row()
2783 {
2784 self.available_code_actions.take();
2785 }
2786 self.refresh_code_actions(window, cx);
2787 self.refresh_document_highlights(cx);
2788 self.refresh_selected_text_highlights(false, window, cx);
2789 refresh_matching_bracket_highlights(self, window, cx);
2790 self.update_visible_inline_completion(window, cx);
2791 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2792 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2793 self.inline_blame_popover.take();
2794 if self.git_blame_inline_enabled {
2795 self.start_inline_blame_timer(window, cx);
2796 }
2797 }
2798
2799 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2800 cx.emit(EditorEvent::SelectionsChanged { local });
2801
2802 let selections = &self.selections.disjoint;
2803 if selections.len() == 1 {
2804 cx.emit(SearchEvent::ActiveMatchChanged)
2805 }
2806 if local {
2807 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2808 let inmemory_selections = selections
2809 .iter()
2810 .map(|s| {
2811 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2812 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2813 })
2814 .collect();
2815 self.update_restoration_data(cx, |data| {
2816 data.selections = inmemory_selections;
2817 });
2818
2819 if WorkspaceSettings::get(None, cx).restore_on_startup
2820 != RestoreOnStartupBehavior::None
2821 {
2822 if let Some(workspace_id) =
2823 self.workspace.as_ref().and_then(|workspace| workspace.1)
2824 {
2825 let snapshot = self.buffer().read(cx).snapshot(cx);
2826 let selections = selections.clone();
2827 let background_executor = cx.background_executor().clone();
2828 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2829 self.serialize_selections = cx.background_spawn(async move {
2830 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2831 let db_selections = selections
2832 .iter()
2833 .map(|selection| {
2834 (
2835 selection.start.to_offset(&snapshot),
2836 selection.end.to_offset(&snapshot),
2837 )
2838 })
2839 .collect();
2840
2841 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2842 .await
2843 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2844 .log_err();
2845 });
2846 }
2847 }
2848 }
2849 }
2850
2851 cx.notify();
2852 }
2853
2854 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2855 use text::ToOffset as _;
2856 use text::ToPoint as _;
2857
2858 if self.mode.is_minimap()
2859 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2860 {
2861 return;
2862 }
2863
2864 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2865 return;
2866 };
2867
2868 let snapshot = singleton.read(cx).snapshot();
2869 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2870 let display_snapshot = display_map.snapshot(cx);
2871
2872 display_snapshot
2873 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2874 .map(|fold| {
2875 fold.range.start.text_anchor.to_point(&snapshot)
2876 ..fold.range.end.text_anchor.to_point(&snapshot)
2877 })
2878 .collect()
2879 });
2880 self.update_restoration_data(cx, |data| {
2881 data.folds = inmemory_folds;
2882 });
2883
2884 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2885 return;
2886 };
2887 let background_executor = cx.background_executor().clone();
2888 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2889 let db_folds = self.display_map.update(cx, |display_map, cx| {
2890 display_map
2891 .snapshot(cx)
2892 .folds_in_range(0..snapshot.len())
2893 .map(|fold| {
2894 (
2895 fold.range.start.text_anchor.to_offset(&snapshot),
2896 fold.range.end.text_anchor.to_offset(&snapshot),
2897 )
2898 })
2899 .collect()
2900 });
2901 self.serialize_folds = cx.background_spawn(async move {
2902 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2903 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2904 .await
2905 .with_context(|| {
2906 format!(
2907 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2908 )
2909 })
2910 .log_err();
2911 });
2912 }
2913
2914 pub fn sync_selections(
2915 &mut self,
2916 other: Entity<Editor>,
2917 cx: &mut Context<Self>,
2918 ) -> gpui::Subscription {
2919 let other_selections = other.read(cx).selections.disjoint.to_vec();
2920 self.selections.change_with(cx, |selections| {
2921 selections.select_anchors(other_selections);
2922 });
2923
2924 let other_subscription =
2925 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2926 EditorEvent::SelectionsChanged { local: true } => {
2927 let other_selections = other.read(cx).selections.disjoint.to_vec();
2928 if other_selections.is_empty() {
2929 return;
2930 }
2931 this.selections.change_with(cx, |selections| {
2932 selections.select_anchors(other_selections);
2933 });
2934 }
2935 _ => {}
2936 });
2937
2938 let this_subscription =
2939 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2940 EditorEvent::SelectionsChanged { local: true } => {
2941 let these_selections = this.selections.disjoint.to_vec();
2942 if these_selections.is_empty() {
2943 return;
2944 }
2945 other.update(cx, |other_editor, cx| {
2946 other_editor.selections.change_with(cx, |selections| {
2947 selections.select_anchors(these_selections);
2948 })
2949 });
2950 }
2951 _ => {}
2952 });
2953
2954 Subscription::join(other_subscription, this_subscription)
2955 }
2956
2957 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2958 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2959 /// effects of selection change occur at the end of the transaction.
2960 pub fn change_selections<R>(
2961 &mut self,
2962 autoscroll: Option<Autoscroll>,
2963 window: &mut Window,
2964 cx: &mut Context<Self>,
2965 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2966 ) -> R {
2967 self.change_selections_inner(true, autoscroll, window, cx, change)
2968 }
2969
2970 pub(crate) fn change_selections_without_updating_completions<R>(
2971 &mut self,
2972 autoscroll: Option<Autoscroll>,
2973 window: &mut Window,
2974 cx: &mut Context<Self>,
2975 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2976 ) -> R {
2977 self.change_selections_inner(false, autoscroll, window, cx, change)
2978 }
2979
2980 fn change_selections_inner<R>(
2981 &mut self,
2982 should_update_completions: bool,
2983 autoscroll: Option<Autoscroll>,
2984 window: &mut Window,
2985 cx: &mut Context<Self>,
2986 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2987 ) -> R {
2988 if let Some(state) = &mut self.deferred_selection_effects_state {
2989 state.autoscroll = autoscroll.or(state.autoscroll);
2990 state.should_update_completions = should_update_completions;
2991 let (changed, result) = self.selections.change_with(cx, change);
2992 state.changed |= changed;
2993 return result;
2994 }
2995 let mut state = DeferredSelectionEffectsState {
2996 changed: false,
2997 should_update_completions,
2998 autoscroll,
2999 old_cursor_position: self.selections.newest_anchor().head(),
3000 history_entry: SelectionHistoryEntry {
3001 selections: self.selections.disjoint_anchors(),
3002 select_next_state: self.select_next_state.clone(),
3003 select_prev_state: self.select_prev_state.clone(),
3004 add_selections_state: self.add_selections_state.clone(),
3005 },
3006 };
3007 let (changed, result) = self.selections.change_with(cx, change);
3008 state.changed = state.changed || changed;
3009 if self.defer_selection_effects {
3010 self.deferred_selection_effects_state = Some(state);
3011 } else {
3012 self.apply_selection_effects(state, window, cx);
3013 }
3014 result
3015 }
3016
3017 /// Defers the effects of selection change, so that the effects of multiple calls to
3018 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3019 /// to selection history and the state of popovers based on selection position aren't
3020 /// erroneously updated.
3021 pub fn with_selection_effects_deferred<R>(
3022 &mut self,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3026 ) -> R {
3027 let already_deferred = self.defer_selection_effects;
3028 self.defer_selection_effects = true;
3029 let result = update(self, window, cx);
3030 if !already_deferred {
3031 self.defer_selection_effects = false;
3032 if let Some(state) = self.deferred_selection_effects_state.take() {
3033 self.apply_selection_effects(state, window, cx);
3034 }
3035 }
3036 result
3037 }
3038
3039 fn apply_selection_effects(
3040 &mut self,
3041 state: DeferredSelectionEffectsState,
3042 window: &mut Window,
3043 cx: &mut Context<Self>,
3044 ) {
3045 if state.changed {
3046 self.selection_history.push(state.history_entry);
3047
3048 if let Some(autoscroll) = state.autoscroll {
3049 self.request_autoscroll(autoscroll, cx);
3050 }
3051
3052 let old_cursor_position = &state.old_cursor_position;
3053
3054 self.selections_did_change(
3055 true,
3056 &old_cursor_position,
3057 state.should_update_completions,
3058 window,
3059 cx,
3060 );
3061
3062 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3063 self.show_signature_help(&ShowSignatureHelp, window, cx);
3064 }
3065 }
3066 }
3067
3068 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3069 where
3070 I: IntoIterator<Item = (Range<S>, T)>,
3071 S: ToOffset,
3072 T: Into<Arc<str>>,
3073 {
3074 if self.read_only(cx) {
3075 return;
3076 }
3077
3078 self.buffer
3079 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3080 }
3081
3082 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3083 where
3084 I: IntoIterator<Item = (Range<S>, T)>,
3085 S: ToOffset,
3086 T: Into<Arc<str>>,
3087 {
3088 if self.read_only(cx) {
3089 return;
3090 }
3091
3092 self.buffer.update(cx, |buffer, cx| {
3093 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3094 });
3095 }
3096
3097 pub fn edit_with_block_indent<I, S, T>(
3098 &mut self,
3099 edits: I,
3100 original_indent_columns: Vec<Option<u32>>,
3101 cx: &mut Context<Self>,
3102 ) where
3103 I: IntoIterator<Item = (Range<S>, T)>,
3104 S: ToOffset,
3105 T: Into<Arc<str>>,
3106 {
3107 if self.read_only(cx) {
3108 return;
3109 }
3110
3111 self.buffer.update(cx, |buffer, cx| {
3112 buffer.edit(
3113 edits,
3114 Some(AutoindentMode::Block {
3115 original_indent_columns,
3116 }),
3117 cx,
3118 )
3119 });
3120 }
3121
3122 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3123 self.hide_context_menu(window, cx);
3124
3125 match phase {
3126 SelectPhase::Begin {
3127 position,
3128 add,
3129 click_count,
3130 } => self.begin_selection(position, add, click_count, window, cx),
3131 SelectPhase::BeginColumnar {
3132 position,
3133 goal_column,
3134 reset,
3135 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3136 SelectPhase::Extend {
3137 position,
3138 click_count,
3139 } => self.extend_selection(position, click_count, window, cx),
3140 SelectPhase::Update {
3141 position,
3142 goal_column,
3143 scroll_delta,
3144 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3145 SelectPhase::End => self.end_selection(window, cx),
3146 }
3147 }
3148
3149 fn extend_selection(
3150 &mut self,
3151 position: DisplayPoint,
3152 click_count: usize,
3153 window: &mut Window,
3154 cx: &mut Context<Self>,
3155 ) {
3156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3157 let tail = self.selections.newest::<usize>(cx).tail();
3158 self.begin_selection(position, false, click_count, window, cx);
3159
3160 let position = position.to_offset(&display_map, Bias::Left);
3161 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3162
3163 let mut pending_selection = self
3164 .selections
3165 .pending_anchor()
3166 .expect("extend_selection not called with pending selection");
3167 if position >= tail {
3168 pending_selection.start = tail_anchor;
3169 } else {
3170 pending_selection.end = tail_anchor;
3171 pending_selection.reversed = true;
3172 }
3173
3174 let mut pending_mode = self.selections.pending_mode().unwrap();
3175 match &mut pending_mode {
3176 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3177 _ => {}
3178 }
3179
3180 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3181
3182 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3183 s.set_pending(pending_selection, pending_mode)
3184 });
3185 }
3186
3187 fn begin_selection(
3188 &mut self,
3189 position: DisplayPoint,
3190 add: bool,
3191 click_count: usize,
3192 window: &mut Window,
3193 cx: &mut Context<Self>,
3194 ) {
3195 if !self.focus_handle.is_focused(window) {
3196 self.last_focused_descendant = None;
3197 window.focus(&self.focus_handle);
3198 }
3199
3200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3201 let buffer = &display_map.buffer_snapshot;
3202 let position = display_map.clip_point(position, Bias::Left);
3203
3204 let start;
3205 let end;
3206 let mode;
3207 let mut auto_scroll;
3208 match click_count {
3209 1 => {
3210 start = buffer.anchor_before(position.to_point(&display_map));
3211 end = start;
3212 mode = SelectMode::Character;
3213 auto_scroll = true;
3214 }
3215 2 => {
3216 let range = movement::surrounding_word(&display_map, position);
3217 start = buffer.anchor_before(range.start.to_point(&display_map));
3218 end = buffer.anchor_before(range.end.to_point(&display_map));
3219 mode = SelectMode::Word(start..end);
3220 auto_scroll = true;
3221 }
3222 3 => {
3223 let position = display_map
3224 .clip_point(position, Bias::Left)
3225 .to_point(&display_map);
3226 let line_start = display_map.prev_line_boundary(position).0;
3227 let next_line_start = buffer.clip_point(
3228 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3229 Bias::Left,
3230 );
3231 start = buffer.anchor_before(line_start);
3232 end = buffer.anchor_before(next_line_start);
3233 mode = SelectMode::Line(start..end);
3234 auto_scroll = true;
3235 }
3236 _ => {
3237 start = buffer.anchor_before(0);
3238 end = buffer.anchor_before(buffer.len());
3239 mode = SelectMode::All;
3240 auto_scroll = false;
3241 }
3242 }
3243 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3244
3245 let point_to_delete: Option<usize> = {
3246 let selected_points: Vec<Selection<Point>> =
3247 self.selections.disjoint_in_range(start..end, cx);
3248
3249 if !add || click_count > 1 {
3250 None
3251 } else if !selected_points.is_empty() {
3252 Some(selected_points[0].id)
3253 } else {
3254 let clicked_point_already_selected =
3255 self.selections.disjoint.iter().find(|selection| {
3256 selection.start.to_point(buffer) == start.to_point(buffer)
3257 || selection.end.to_point(buffer) == end.to_point(buffer)
3258 });
3259
3260 clicked_point_already_selected.map(|selection| selection.id)
3261 }
3262 };
3263
3264 let selections_count = self.selections.count();
3265
3266 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3267 if let Some(point_to_delete) = point_to_delete {
3268 s.delete(point_to_delete);
3269
3270 if selections_count == 1 {
3271 s.set_pending_anchor_range(start..end, mode);
3272 }
3273 } else {
3274 if !add {
3275 s.clear_disjoint();
3276 }
3277
3278 s.set_pending_anchor_range(start..end, mode);
3279 }
3280 });
3281 }
3282
3283 fn begin_columnar_selection(
3284 &mut self,
3285 position: DisplayPoint,
3286 goal_column: u32,
3287 reset: bool,
3288 window: &mut Window,
3289 cx: &mut Context<Self>,
3290 ) {
3291 if !self.focus_handle.is_focused(window) {
3292 self.last_focused_descendant = None;
3293 window.focus(&self.focus_handle);
3294 }
3295
3296 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3297
3298 if reset {
3299 let pointer_position = display_map
3300 .buffer_snapshot
3301 .anchor_before(position.to_point(&display_map));
3302
3303 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3304 s.clear_disjoint();
3305 s.set_pending_anchor_range(
3306 pointer_position..pointer_position,
3307 SelectMode::Character,
3308 );
3309 });
3310 if position.column() != goal_column {
3311 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3312 } else {
3313 self.columnar_display_point = None;
3314 }
3315 }
3316
3317 let tail = self.selections.newest::<Point>(cx).tail();
3318 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3319
3320 if !reset {
3321 self.columnar_display_point = None;
3322 self.select_columns(
3323 tail.to_display_point(&display_map),
3324 position,
3325 goal_column,
3326 &display_map,
3327 window,
3328 cx,
3329 );
3330 }
3331 }
3332
3333 fn update_selection(
3334 &mut self,
3335 position: DisplayPoint,
3336 goal_column: u32,
3337 scroll_delta: gpui::Point<f32>,
3338 window: &mut Window,
3339 cx: &mut Context<Self>,
3340 ) {
3341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3342
3343 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3344 let tail = self
3345 .columnar_display_point
3346 .unwrap_or_else(|| tail.to_display_point(&display_map));
3347 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3348 } else if let Some(mut pending) = self.selections.pending_anchor() {
3349 let buffer = self.buffer.read(cx).snapshot(cx);
3350 let head;
3351 let tail;
3352 let mode = self.selections.pending_mode().unwrap();
3353 match &mode {
3354 SelectMode::Character => {
3355 head = position.to_point(&display_map);
3356 tail = pending.tail().to_point(&buffer);
3357 }
3358 SelectMode::Word(original_range) => {
3359 let original_display_range = original_range.start.to_display_point(&display_map)
3360 ..original_range.end.to_display_point(&display_map);
3361 let original_buffer_range = original_display_range.start.to_point(&display_map)
3362 ..original_display_range.end.to_point(&display_map);
3363 if movement::is_inside_word(&display_map, position)
3364 || original_display_range.contains(&position)
3365 {
3366 let word_range = movement::surrounding_word(&display_map, position);
3367 if word_range.start < original_display_range.start {
3368 head = word_range.start.to_point(&display_map);
3369 } else {
3370 head = word_range.end.to_point(&display_map);
3371 }
3372 } else {
3373 head = position.to_point(&display_map);
3374 }
3375
3376 if head <= original_buffer_range.start {
3377 tail = original_buffer_range.end;
3378 } else {
3379 tail = original_buffer_range.start;
3380 }
3381 }
3382 SelectMode::Line(original_range) => {
3383 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3384
3385 let position = display_map
3386 .clip_point(position, Bias::Left)
3387 .to_point(&display_map);
3388 let line_start = display_map.prev_line_boundary(position).0;
3389 let next_line_start = buffer.clip_point(
3390 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3391 Bias::Left,
3392 );
3393
3394 if line_start < original_range.start {
3395 head = line_start
3396 } else {
3397 head = next_line_start
3398 }
3399
3400 if head <= original_range.start {
3401 tail = original_range.end;
3402 } else {
3403 tail = original_range.start;
3404 }
3405 }
3406 SelectMode::All => {
3407 return;
3408 }
3409 };
3410
3411 if head < tail {
3412 pending.start = buffer.anchor_before(head);
3413 pending.end = buffer.anchor_before(tail);
3414 pending.reversed = true;
3415 } else {
3416 pending.start = buffer.anchor_before(tail);
3417 pending.end = buffer.anchor_before(head);
3418 pending.reversed = false;
3419 }
3420
3421 self.change_selections(None, window, cx, |s| {
3422 s.set_pending(pending, mode);
3423 });
3424 } else {
3425 log::error!("update_selection dispatched with no pending selection");
3426 return;
3427 }
3428
3429 self.apply_scroll_delta(scroll_delta, window, cx);
3430 cx.notify();
3431 }
3432
3433 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3434 self.columnar_selection_tail.take();
3435 if self.selections.pending_anchor().is_some() {
3436 let selections = self.selections.all::<usize>(cx);
3437 self.change_selections(None, window, cx, |s| {
3438 s.select(selections);
3439 s.clear_pending();
3440 });
3441 }
3442 }
3443
3444 fn select_columns(
3445 &mut self,
3446 tail: DisplayPoint,
3447 head: DisplayPoint,
3448 goal_column: u32,
3449 display_map: &DisplaySnapshot,
3450 window: &mut Window,
3451 cx: &mut Context<Self>,
3452 ) {
3453 let start_row = cmp::min(tail.row(), head.row());
3454 let end_row = cmp::max(tail.row(), head.row());
3455 let start_column = cmp::min(tail.column(), goal_column);
3456 let end_column = cmp::max(tail.column(), goal_column);
3457 let reversed = start_column < tail.column();
3458
3459 let selection_ranges = (start_row.0..=end_row.0)
3460 .map(DisplayRow)
3461 .filter_map(|row| {
3462 if !display_map.is_block_line(row) {
3463 let start = display_map
3464 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3465 .to_point(display_map);
3466 let end = display_map
3467 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3468 .to_point(display_map);
3469 if reversed {
3470 Some(end..start)
3471 } else {
3472 Some(start..end)
3473 }
3474 } else {
3475 None
3476 }
3477 })
3478 .collect::<Vec<_>>();
3479
3480 let mut non_empty_ranges = selection_ranges
3481 .iter()
3482 .filter(|selection_range| selection_range.start != selection_range.end)
3483 .peekable();
3484
3485 let ranges = if non_empty_ranges.peek().is_some() {
3486 non_empty_ranges.cloned().collect()
3487 } else {
3488 selection_ranges
3489 };
3490
3491 self.change_selections(None, window, cx, |s| {
3492 s.select_ranges(ranges);
3493 });
3494 cx.notify();
3495 }
3496
3497 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3498 self.selections
3499 .all_adjusted(cx)
3500 .iter()
3501 .any(|selection| !selection.is_empty())
3502 }
3503
3504 pub fn has_pending_nonempty_selection(&self) -> bool {
3505 let pending_nonempty_selection = match self.selections.pending_anchor() {
3506 Some(Selection { start, end, .. }) => start != end,
3507 None => false,
3508 };
3509
3510 pending_nonempty_selection
3511 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3512 }
3513
3514 pub fn has_pending_selection(&self) -> bool {
3515 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3516 }
3517
3518 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3519 self.selection_mark_mode = false;
3520
3521 if self.clear_expanded_diff_hunks(cx) {
3522 cx.notify();
3523 return;
3524 }
3525 if self.dismiss_menus_and_popups(true, window, cx) {
3526 return;
3527 }
3528
3529 if self.mode.is_full()
3530 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3531 {
3532 return;
3533 }
3534
3535 cx.propagate();
3536 }
3537
3538 pub fn dismiss_menus_and_popups(
3539 &mut self,
3540 is_user_requested: bool,
3541 window: &mut Window,
3542 cx: &mut Context<Self>,
3543 ) -> bool {
3544 if self.take_rename(false, window, cx).is_some() {
3545 return true;
3546 }
3547
3548 if hide_hover(self, cx) {
3549 return true;
3550 }
3551
3552 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3553 return true;
3554 }
3555
3556 if self.hide_context_menu(window, cx).is_some() {
3557 return true;
3558 }
3559
3560 if self.mouse_context_menu.take().is_some() {
3561 return true;
3562 }
3563
3564 if is_user_requested && self.discard_inline_completion(true, cx) {
3565 return true;
3566 }
3567
3568 if self.snippet_stack.pop().is_some() {
3569 return true;
3570 }
3571
3572 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3573 self.dismiss_diagnostics(cx);
3574 return true;
3575 }
3576
3577 false
3578 }
3579
3580 fn linked_editing_ranges_for(
3581 &self,
3582 selection: Range<text::Anchor>,
3583 cx: &App,
3584 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3585 if self.linked_edit_ranges.is_empty() {
3586 return None;
3587 }
3588 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3589 selection.end.buffer_id.and_then(|end_buffer_id| {
3590 if selection.start.buffer_id != Some(end_buffer_id) {
3591 return None;
3592 }
3593 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3594 let snapshot = buffer.read(cx).snapshot();
3595 self.linked_edit_ranges
3596 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3597 .map(|ranges| (ranges, snapshot, buffer))
3598 })?;
3599 use text::ToOffset as TO;
3600 // find offset from the start of current range to current cursor position
3601 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3602
3603 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3604 let start_difference = start_offset - start_byte_offset;
3605 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3606 let end_difference = end_offset - start_byte_offset;
3607 // Current range has associated linked ranges.
3608 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3609 for range in linked_ranges.iter() {
3610 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3611 let end_offset = start_offset + end_difference;
3612 let start_offset = start_offset + start_difference;
3613 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3614 continue;
3615 }
3616 if self.selections.disjoint_anchor_ranges().any(|s| {
3617 if s.start.buffer_id != selection.start.buffer_id
3618 || s.end.buffer_id != selection.end.buffer_id
3619 {
3620 return false;
3621 }
3622 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3623 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3624 }) {
3625 continue;
3626 }
3627 let start = buffer_snapshot.anchor_after(start_offset);
3628 let end = buffer_snapshot.anchor_after(end_offset);
3629 linked_edits
3630 .entry(buffer.clone())
3631 .or_default()
3632 .push(start..end);
3633 }
3634 Some(linked_edits)
3635 }
3636
3637 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3638 let text: Arc<str> = text.into();
3639
3640 if self.read_only(cx) {
3641 return;
3642 }
3643
3644 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3645
3646 let selections = self.selections.all_adjusted(cx);
3647 let mut bracket_inserted = false;
3648 let mut edits = Vec::new();
3649 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3650 let mut new_selections = Vec::with_capacity(selections.len());
3651 let mut new_autoclose_regions = Vec::new();
3652 let snapshot = self.buffer.read(cx).read(cx);
3653 let mut clear_linked_edit_ranges = false;
3654
3655 for (selection, autoclose_region) in
3656 self.selections_with_autoclose_regions(selections, &snapshot)
3657 {
3658 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3659 // Determine if the inserted text matches the opening or closing
3660 // bracket of any of this language's bracket pairs.
3661 let mut bracket_pair = None;
3662 let mut is_bracket_pair_start = false;
3663 let mut is_bracket_pair_end = false;
3664 if !text.is_empty() {
3665 let mut bracket_pair_matching_end = None;
3666 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3667 // and they are removing the character that triggered IME popup.
3668 for (pair, enabled) in scope.brackets() {
3669 if !pair.close && !pair.surround {
3670 continue;
3671 }
3672
3673 if enabled && pair.start.ends_with(text.as_ref()) {
3674 let prefix_len = pair.start.len() - text.len();
3675 let preceding_text_matches_prefix = prefix_len == 0
3676 || (selection.start.column >= (prefix_len as u32)
3677 && snapshot.contains_str_at(
3678 Point::new(
3679 selection.start.row,
3680 selection.start.column - (prefix_len as u32),
3681 ),
3682 &pair.start[..prefix_len],
3683 ));
3684 if preceding_text_matches_prefix {
3685 bracket_pair = Some(pair.clone());
3686 is_bracket_pair_start = true;
3687 break;
3688 }
3689 }
3690 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3691 {
3692 // take first bracket pair matching end, but don't break in case a later bracket
3693 // pair matches start
3694 bracket_pair_matching_end = Some(pair.clone());
3695 }
3696 }
3697 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3698 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3699 is_bracket_pair_end = true;
3700 }
3701 }
3702
3703 if let Some(bracket_pair) = bracket_pair {
3704 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3705 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3706 let auto_surround =
3707 self.use_auto_surround && snapshot_settings.use_auto_surround;
3708 if selection.is_empty() {
3709 if is_bracket_pair_start {
3710 // If the inserted text is a suffix of an opening bracket and the
3711 // selection is preceded by the rest of the opening bracket, then
3712 // insert the closing bracket.
3713 let following_text_allows_autoclose = snapshot
3714 .chars_at(selection.start)
3715 .next()
3716 .map_or(true, |c| scope.should_autoclose_before(c));
3717
3718 let preceding_text_allows_autoclose = selection.start.column == 0
3719 || snapshot.reversed_chars_at(selection.start).next().map_or(
3720 true,
3721 |c| {
3722 bracket_pair.start != bracket_pair.end
3723 || !snapshot
3724 .char_classifier_at(selection.start)
3725 .is_word(c)
3726 },
3727 );
3728
3729 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3730 && bracket_pair.start.len() == 1
3731 {
3732 let target = bracket_pair.start.chars().next().unwrap();
3733 let current_line_count = snapshot
3734 .reversed_chars_at(selection.start)
3735 .take_while(|&c| c != '\n')
3736 .filter(|&c| c == target)
3737 .count();
3738 current_line_count % 2 == 1
3739 } else {
3740 false
3741 };
3742
3743 if autoclose
3744 && bracket_pair.close
3745 && following_text_allows_autoclose
3746 && preceding_text_allows_autoclose
3747 && !is_closing_quote
3748 {
3749 let anchor = snapshot.anchor_before(selection.end);
3750 new_selections.push((selection.map(|_| anchor), text.len()));
3751 new_autoclose_regions.push((
3752 anchor,
3753 text.len(),
3754 selection.id,
3755 bracket_pair.clone(),
3756 ));
3757 edits.push((
3758 selection.range(),
3759 format!("{}{}", text, bracket_pair.end).into(),
3760 ));
3761 bracket_inserted = true;
3762 continue;
3763 }
3764 }
3765
3766 if let Some(region) = autoclose_region {
3767 // If the selection is followed by an auto-inserted closing bracket,
3768 // then don't insert that closing bracket again; just move the selection
3769 // past the closing bracket.
3770 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3771 && text.as_ref() == region.pair.end.as_str();
3772 if should_skip {
3773 let anchor = snapshot.anchor_after(selection.end);
3774 new_selections
3775 .push((selection.map(|_| anchor), region.pair.end.len()));
3776 continue;
3777 }
3778 }
3779
3780 let always_treat_brackets_as_autoclosed = snapshot
3781 .language_settings_at(selection.start, cx)
3782 .always_treat_brackets_as_autoclosed;
3783 if always_treat_brackets_as_autoclosed
3784 && is_bracket_pair_end
3785 && snapshot.contains_str_at(selection.end, text.as_ref())
3786 {
3787 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3788 // and the inserted text is a closing bracket and the selection is followed
3789 // by the closing bracket then move the selection past the closing bracket.
3790 let anchor = snapshot.anchor_after(selection.end);
3791 new_selections.push((selection.map(|_| anchor), text.len()));
3792 continue;
3793 }
3794 }
3795 // If an opening bracket is 1 character long and is typed while
3796 // text is selected, then surround that text with the bracket pair.
3797 else if auto_surround
3798 && bracket_pair.surround
3799 && is_bracket_pair_start
3800 && bracket_pair.start.chars().count() == 1
3801 {
3802 edits.push((selection.start..selection.start, text.clone()));
3803 edits.push((
3804 selection.end..selection.end,
3805 bracket_pair.end.as_str().into(),
3806 ));
3807 bracket_inserted = true;
3808 new_selections.push((
3809 Selection {
3810 id: selection.id,
3811 start: snapshot.anchor_after(selection.start),
3812 end: snapshot.anchor_before(selection.end),
3813 reversed: selection.reversed,
3814 goal: selection.goal,
3815 },
3816 0,
3817 ));
3818 continue;
3819 }
3820 }
3821 }
3822
3823 if self.auto_replace_emoji_shortcode
3824 && selection.is_empty()
3825 && text.as_ref().ends_with(':')
3826 {
3827 if let Some(possible_emoji_short_code) =
3828 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3829 {
3830 if !possible_emoji_short_code.is_empty() {
3831 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3832 let emoji_shortcode_start = Point::new(
3833 selection.start.row,
3834 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3835 );
3836
3837 // Remove shortcode from buffer
3838 edits.push((
3839 emoji_shortcode_start..selection.start,
3840 "".to_string().into(),
3841 ));
3842 new_selections.push((
3843 Selection {
3844 id: selection.id,
3845 start: snapshot.anchor_after(emoji_shortcode_start),
3846 end: snapshot.anchor_before(selection.start),
3847 reversed: selection.reversed,
3848 goal: selection.goal,
3849 },
3850 0,
3851 ));
3852
3853 // Insert emoji
3854 let selection_start_anchor = snapshot.anchor_after(selection.start);
3855 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3856 edits.push((selection.start..selection.end, emoji.to_string().into()));
3857
3858 continue;
3859 }
3860 }
3861 }
3862 }
3863
3864 // If not handling any auto-close operation, then just replace the selected
3865 // text with the given input and move the selection to the end of the
3866 // newly inserted text.
3867 let anchor = snapshot.anchor_after(selection.end);
3868 if !self.linked_edit_ranges.is_empty() {
3869 let start_anchor = snapshot.anchor_before(selection.start);
3870
3871 let is_word_char = text.chars().next().map_or(true, |char| {
3872 let classifier = snapshot
3873 .char_classifier_at(start_anchor.to_offset(&snapshot))
3874 .ignore_punctuation(true);
3875 classifier.is_word(char)
3876 });
3877
3878 if is_word_char {
3879 if let Some(ranges) = self
3880 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3881 {
3882 for (buffer, edits) in ranges {
3883 linked_edits
3884 .entry(buffer.clone())
3885 .or_default()
3886 .extend(edits.into_iter().map(|range| (range, text.clone())));
3887 }
3888 }
3889 } else {
3890 clear_linked_edit_ranges = true;
3891 }
3892 }
3893
3894 new_selections.push((selection.map(|_| anchor), 0));
3895 edits.push((selection.start..selection.end, text.clone()));
3896 }
3897
3898 drop(snapshot);
3899
3900 self.transact(window, cx, |this, window, cx| {
3901 if clear_linked_edit_ranges {
3902 this.linked_edit_ranges.clear();
3903 }
3904 let initial_buffer_versions =
3905 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3906
3907 this.buffer.update(cx, |buffer, cx| {
3908 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3909 });
3910 for (buffer, edits) in linked_edits {
3911 buffer.update(cx, |buffer, cx| {
3912 let snapshot = buffer.snapshot();
3913 let edits = edits
3914 .into_iter()
3915 .map(|(range, text)| {
3916 use text::ToPoint as TP;
3917 let end_point = TP::to_point(&range.end, &snapshot);
3918 let start_point = TP::to_point(&range.start, &snapshot);
3919 (start_point..end_point, text)
3920 })
3921 .sorted_by_key(|(range, _)| range.start);
3922 buffer.edit(edits, None, cx);
3923 })
3924 }
3925 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3926 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3927 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3928 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3929 .zip(new_selection_deltas)
3930 .map(|(selection, delta)| Selection {
3931 id: selection.id,
3932 start: selection.start + delta,
3933 end: selection.end + delta,
3934 reversed: selection.reversed,
3935 goal: SelectionGoal::None,
3936 })
3937 .collect::<Vec<_>>();
3938
3939 let mut i = 0;
3940 for (position, delta, selection_id, pair) in new_autoclose_regions {
3941 let position = position.to_offset(&map.buffer_snapshot) + delta;
3942 let start = map.buffer_snapshot.anchor_before(position);
3943 let end = map.buffer_snapshot.anchor_after(position);
3944 while let Some(existing_state) = this.autoclose_regions.get(i) {
3945 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3946 Ordering::Less => i += 1,
3947 Ordering::Greater => break,
3948 Ordering::Equal => {
3949 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3950 Ordering::Less => i += 1,
3951 Ordering::Equal => break,
3952 Ordering::Greater => break,
3953 }
3954 }
3955 }
3956 }
3957 this.autoclose_regions.insert(
3958 i,
3959 AutocloseRegion {
3960 selection_id,
3961 range: start..end,
3962 pair,
3963 },
3964 );
3965 }
3966
3967 let had_active_inline_completion = this.has_active_inline_completion();
3968 this.change_selections_without_updating_completions(
3969 Some(Autoscroll::fit()),
3970 window,
3971 cx,
3972 |s| s.select(new_selections),
3973 );
3974
3975 if !bracket_inserted {
3976 if let Some(on_type_format_task) =
3977 this.trigger_on_type_formatting(text.to_string(), window, cx)
3978 {
3979 on_type_format_task.detach_and_log_err(cx);
3980 }
3981 }
3982
3983 let editor_settings = EditorSettings::get_global(cx);
3984 if bracket_inserted
3985 && (editor_settings.auto_signature_help
3986 || editor_settings.show_signature_help_after_edits)
3987 {
3988 this.show_signature_help(&ShowSignatureHelp, window, cx);
3989 }
3990
3991 let trigger_in_words =
3992 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3993 if this.hard_wrap.is_some() {
3994 let latest: Range<Point> = this.selections.newest(cx).range();
3995 if latest.is_empty()
3996 && this
3997 .buffer()
3998 .read(cx)
3999 .snapshot(cx)
4000 .line_len(MultiBufferRow(latest.start.row))
4001 == latest.start.column
4002 {
4003 this.rewrap_impl(
4004 RewrapOptions {
4005 override_language_settings: true,
4006 preserve_existing_whitespace: true,
4007 },
4008 cx,
4009 )
4010 }
4011 }
4012 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4013 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4014 this.refresh_inline_completion(true, false, window, cx);
4015 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4016 });
4017 }
4018
4019 fn find_possible_emoji_shortcode_at_position(
4020 snapshot: &MultiBufferSnapshot,
4021 position: Point,
4022 ) -> Option<String> {
4023 let mut chars = Vec::new();
4024 let mut found_colon = false;
4025 for char in snapshot.reversed_chars_at(position).take(100) {
4026 // Found a possible emoji shortcode in the middle of the buffer
4027 if found_colon {
4028 if char.is_whitespace() {
4029 chars.reverse();
4030 return Some(chars.iter().collect());
4031 }
4032 // If the previous character is not a whitespace, we are in the middle of a word
4033 // and we only want to complete the shortcode if the word is made up of other emojis
4034 let mut containing_word = String::new();
4035 for ch in snapshot
4036 .reversed_chars_at(position)
4037 .skip(chars.len() + 1)
4038 .take(100)
4039 {
4040 if ch.is_whitespace() {
4041 break;
4042 }
4043 containing_word.push(ch);
4044 }
4045 let containing_word = containing_word.chars().rev().collect::<String>();
4046 if util::word_consists_of_emojis(containing_word.as_str()) {
4047 chars.reverse();
4048 return Some(chars.iter().collect());
4049 }
4050 }
4051
4052 if char.is_whitespace() || !char.is_ascii() {
4053 return None;
4054 }
4055 if char == ':' {
4056 found_colon = true;
4057 } else {
4058 chars.push(char);
4059 }
4060 }
4061 // Found a possible emoji shortcode at the beginning of the buffer
4062 chars.reverse();
4063 Some(chars.iter().collect())
4064 }
4065
4066 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4068 self.transact(window, cx, |this, window, cx| {
4069 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4070 let selections = this.selections.all::<usize>(cx);
4071 let multi_buffer = this.buffer.read(cx);
4072 let buffer = multi_buffer.snapshot(cx);
4073 selections
4074 .iter()
4075 .map(|selection| {
4076 let start_point = selection.start.to_point(&buffer);
4077 let mut existing_indent =
4078 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4079 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4080 let start = selection.start;
4081 let end = selection.end;
4082 let selection_is_empty = start == end;
4083 let language_scope = buffer.language_scope_at(start);
4084 let (
4085 comment_delimiter,
4086 doc_delimiter,
4087 insert_extra_newline,
4088 indent_on_newline,
4089 indent_on_extra_newline,
4090 ) = if let Some(language) = &language_scope {
4091 let mut insert_extra_newline =
4092 insert_extra_newline_brackets(&buffer, start..end, language)
4093 || insert_extra_newline_tree_sitter(&buffer, start..end);
4094
4095 // Comment extension on newline is allowed only for cursor selections
4096 let comment_delimiter = maybe!({
4097 if !selection_is_empty {
4098 return None;
4099 }
4100
4101 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4102 return None;
4103 }
4104
4105 let delimiters = language.line_comment_prefixes();
4106 let max_len_of_delimiter =
4107 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4108 let (snapshot, range) =
4109 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4110
4111 let num_of_whitespaces = snapshot
4112 .chars_for_range(range.clone())
4113 .take_while(|c| c.is_whitespace())
4114 .count();
4115 let comment_candidate = snapshot
4116 .chars_for_range(range)
4117 .skip(num_of_whitespaces)
4118 .take(max_len_of_delimiter)
4119 .collect::<String>();
4120 let (delimiter, trimmed_len) = delimiters
4121 .iter()
4122 .filter_map(|delimiter| {
4123 let prefix = delimiter.trim_end();
4124 if comment_candidate.starts_with(prefix) {
4125 Some((delimiter, prefix.len()))
4126 } else {
4127 None
4128 }
4129 })
4130 .max_by_key(|(_, len)| *len)?;
4131
4132 let cursor_is_placed_after_comment_marker =
4133 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4134 if cursor_is_placed_after_comment_marker {
4135 Some(delimiter.clone())
4136 } else {
4137 None
4138 }
4139 });
4140
4141 let mut indent_on_newline = IndentSize::spaces(0);
4142 let mut indent_on_extra_newline = IndentSize::spaces(0);
4143
4144 let doc_delimiter = maybe!({
4145 if !selection_is_empty {
4146 return None;
4147 }
4148
4149 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4150 return None;
4151 }
4152
4153 let DocumentationConfig {
4154 start: start_tag,
4155 end: end_tag,
4156 prefix: delimiter,
4157 tab_size: len,
4158 } = language.documentation()?;
4159
4160 let is_within_block_comment = buffer
4161 .language_scope_at(start_point)
4162 .is_some_and(|scope| scope.override_name() == Some("comment"));
4163 if !is_within_block_comment {
4164 return None;
4165 }
4166
4167 let (snapshot, range) =
4168 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4169
4170 let num_of_whitespaces = snapshot
4171 .chars_for_range(range.clone())
4172 .take_while(|c| c.is_whitespace())
4173 .count();
4174
4175 // 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.
4176 let column = start_point.column;
4177 let cursor_is_after_start_tag = {
4178 let start_tag_len = start_tag.len();
4179 let start_tag_line = snapshot
4180 .chars_for_range(range.clone())
4181 .skip(num_of_whitespaces)
4182 .take(start_tag_len)
4183 .collect::<String>();
4184 if start_tag_line.starts_with(start_tag.as_ref()) {
4185 num_of_whitespaces + start_tag_len <= column as usize
4186 } else {
4187 false
4188 }
4189 };
4190
4191 let cursor_is_after_delimiter = {
4192 let delimiter_trim = delimiter.trim_end();
4193 let delimiter_line = snapshot
4194 .chars_for_range(range.clone())
4195 .skip(num_of_whitespaces)
4196 .take(delimiter_trim.len())
4197 .collect::<String>();
4198 if delimiter_line.starts_with(delimiter_trim) {
4199 num_of_whitespaces + delimiter_trim.len() <= column as usize
4200 } else {
4201 false
4202 }
4203 };
4204
4205 let cursor_is_before_end_tag_if_exists = {
4206 let mut char_position = 0u32;
4207 let mut end_tag_offset = None;
4208
4209 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4210 if let Some(byte_pos) = chunk.find(&**end_tag) {
4211 let chars_before_match =
4212 chunk[..byte_pos].chars().count() as u32;
4213 end_tag_offset =
4214 Some(char_position + chars_before_match);
4215 break 'outer;
4216 }
4217 char_position += chunk.chars().count() as u32;
4218 }
4219
4220 if let Some(end_tag_offset) = end_tag_offset {
4221 let cursor_is_before_end_tag = column <= end_tag_offset;
4222 if cursor_is_after_start_tag {
4223 if cursor_is_before_end_tag {
4224 insert_extra_newline = true;
4225 }
4226 let cursor_is_at_start_of_end_tag =
4227 column == end_tag_offset;
4228 if cursor_is_at_start_of_end_tag {
4229 indent_on_extra_newline.len = (*len).into();
4230 }
4231 }
4232 cursor_is_before_end_tag
4233 } else {
4234 true
4235 }
4236 };
4237
4238 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4239 && cursor_is_before_end_tag_if_exists
4240 {
4241 if cursor_is_after_start_tag {
4242 indent_on_newline.len = (*len).into();
4243 }
4244 Some(delimiter.clone())
4245 } else {
4246 None
4247 }
4248 });
4249
4250 (
4251 comment_delimiter,
4252 doc_delimiter,
4253 insert_extra_newline,
4254 indent_on_newline,
4255 indent_on_extra_newline,
4256 )
4257 } else {
4258 (
4259 None,
4260 None,
4261 false,
4262 IndentSize::default(),
4263 IndentSize::default(),
4264 )
4265 };
4266
4267 let prevent_auto_indent = doc_delimiter.is_some();
4268 let delimiter = comment_delimiter.or(doc_delimiter);
4269
4270 let capacity_for_delimiter =
4271 delimiter.as_deref().map(str::len).unwrap_or_default();
4272 let mut new_text = String::with_capacity(
4273 1 + capacity_for_delimiter
4274 + existing_indent.len as usize
4275 + indent_on_newline.len as usize
4276 + indent_on_extra_newline.len as usize,
4277 );
4278 new_text.push('\n');
4279 new_text.extend(existing_indent.chars());
4280 new_text.extend(indent_on_newline.chars());
4281
4282 if let Some(delimiter) = &delimiter {
4283 new_text.push_str(delimiter);
4284 }
4285
4286 if insert_extra_newline {
4287 new_text.push('\n');
4288 new_text.extend(existing_indent.chars());
4289 new_text.extend(indent_on_extra_newline.chars());
4290 }
4291
4292 let anchor = buffer.anchor_after(end);
4293 let new_selection = selection.map(|_| anchor);
4294 (
4295 ((start..end, new_text), prevent_auto_indent),
4296 (insert_extra_newline, new_selection),
4297 )
4298 })
4299 .unzip()
4300 };
4301
4302 let mut auto_indent_edits = Vec::new();
4303 let mut edits = Vec::new();
4304 for (edit, prevent_auto_indent) in edits_with_flags {
4305 if prevent_auto_indent {
4306 edits.push(edit);
4307 } else {
4308 auto_indent_edits.push(edit);
4309 }
4310 }
4311 if !edits.is_empty() {
4312 this.edit(edits, cx);
4313 }
4314 if !auto_indent_edits.is_empty() {
4315 this.edit_with_autoindent(auto_indent_edits, cx);
4316 }
4317
4318 let buffer = this.buffer.read(cx).snapshot(cx);
4319 let new_selections = selection_info
4320 .into_iter()
4321 .map(|(extra_newline_inserted, new_selection)| {
4322 let mut cursor = new_selection.end.to_point(&buffer);
4323 if extra_newline_inserted {
4324 cursor.row -= 1;
4325 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4326 }
4327 new_selection.map(|_| cursor)
4328 })
4329 .collect();
4330
4331 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4332 s.select(new_selections)
4333 });
4334 this.refresh_inline_completion(true, false, window, cx);
4335 });
4336 }
4337
4338 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4339 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4340
4341 let buffer = self.buffer.read(cx);
4342 let snapshot = buffer.snapshot(cx);
4343
4344 let mut edits = Vec::new();
4345 let mut rows = Vec::new();
4346
4347 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4348 let cursor = selection.head();
4349 let row = cursor.row;
4350
4351 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4352
4353 let newline = "\n".to_string();
4354 edits.push((start_of_line..start_of_line, newline));
4355
4356 rows.push(row + rows_inserted as u32);
4357 }
4358
4359 self.transact(window, cx, |editor, window, cx| {
4360 editor.edit(edits, cx);
4361
4362 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4363 let mut index = 0;
4364 s.move_cursors_with(|map, _, _| {
4365 let row = rows[index];
4366 index += 1;
4367
4368 let point = Point::new(row, 0);
4369 let boundary = map.next_line_boundary(point).1;
4370 let clipped = map.clip_point(boundary, Bias::Left);
4371
4372 (clipped, SelectionGoal::None)
4373 });
4374 });
4375
4376 let mut indent_edits = Vec::new();
4377 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4378 for row in rows {
4379 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4380 for (row, indent) in indents {
4381 if indent.len == 0 {
4382 continue;
4383 }
4384
4385 let text = match indent.kind {
4386 IndentKind::Space => " ".repeat(indent.len as usize),
4387 IndentKind::Tab => "\t".repeat(indent.len as usize),
4388 };
4389 let point = Point::new(row.0, 0);
4390 indent_edits.push((point..point, text));
4391 }
4392 }
4393 editor.edit(indent_edits, cx);
4394 });
4395 }
4396
4397 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4399
4400 let buffer = self.buffer.read(cx);
4401 let snapshot = buffer.snapshot(cx);
4402
4403 let mut edits = Vec::new();
4404 let mut rows = Vec::new();
4405 let mut rows_inserted = 0;
4406
4407 for selection in self.selections.all_adjusted(cx) {
4408 let cursor = selection.head();
4409 let row = cursor.row;
4410
4411 let point = Point::new(row + 1, 0);
4412 let start_of_line = snapshot.clip_point(point, Bias::Left);
4413
4414 let newline = "\n".to_string();
4415 edits.push((start_of_line..start_of_line, newline));
4416
4417 rows_inserted += 1;
4418 rows.push(row + rows_inserted);
4419 }
4420
4421 self.transact(window, cx, |editor, window, cx| {
4422 editor.edit(edits, cx);
4423
4424 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4425 let mut index = 0;
4426 s.move_cursors_with(|map, _, _| {
4427 let row = rows[index];
4428 index += 1;
4429
4430 let point = Point::new(row, 0);
4431 let boundary = map.next_line_boundary(point).1;
4432 let clipped = map.clip_point(boundary, Bias::Left);
4433
4434 (clipped, SelectionGoal::None)
4435 });
4436 });
4437
4438 let mut indent_edits = Vec::new();
4439 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4440 for row in rows {
4441 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4442 for (row, indent) in indents {
4443 if indent.len == 0 {
4444 continue;
4445 }
4446
4447 let text = match indent.kind {
4448 IndentKind::Space => " ".repeat(indent.len as usize),
4449 IndentKind::Tab => "\t".repeat(indent.len as usize),
4450 };
4451 let point = Point::new(row.0, 0);
4452 indent_edits.push((point..point, text));
4453 }
4454 }
4455 editor.edit(indent_edits, cx);
4456 });
4457 }
4458
4459 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4460 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4461 original_indent_columns: Vec::new(),
4462 });
4463 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4464 }
4465
4466 fn insert_with_autoindent_mode(
4467 &mut self,
4468 text: &str,
4469 autoindent_mode: Option<AutoindentMode>,
4470 window: &mut Window,
4471 cx: &mut Context<Self>,
4472 ) {
4473 if self.read_only(cx) {
4474 return;
4475 }
4476
4477 let text: Arc<str> = text.into();
4478 self.transact(window, cx, |this, window, cx| {
4479 let old_selections = this.selections.all_adjusted(cx);
4480 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4481 let anchors = {
4482 let snapshot = buffer.read(cx);
4483 old_selections
4484 .iter()
4485 .map(|s| {
4486 let anchor = snapshot.anchor_after(s.head());
4487 s.map(|_| anchor)
4488 })
4489 .collect::<Vec<_>>()
4490 };
4491 buffer.edit(
4492 old_selections
4493 .iter()
4494 .map(|s| (s.start..s.end, text.clone())),
4495 autoindent_mode,
4496 cx,
4497 );
4498 anchors
4499 });
4500
4501 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4502 s.select_anchors(selection_anchors);
4503 });
4504
4505 cx.notify();
4506 });
4507 }
4508
4509 fn trigger_completion_on_input(
4510 &mut self,
4511 text: &str,
4512 trigger_in_words: bool,
4513 window: &mut Window,
4514 cx: &mut Context<Self>,
4515 ) {
4516 let completions_source = self
4517 .context_menu
4518 .borrow()
4519 .as_ref()
4520 .and_then(|menu| match menu {
4521 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4522 CodeContextMenu::CodeActions(_) => None,
4523 });
4524
4525 match completions_source {
4526 Some(CompletionsMenuSource::Words) => {
4527 self.show_word_completions(&ShowWordCompletions, window, cx)
4528 }
4529 Some(CompletionsMenuSource::Normal)
4530 | Some(CompletionsMenuSource::SnippetChoices)
4531 | None
4532 if self.is_completion_trigger(
4533 text,
4534 trigger_in_words,
4535 completions_source.is_some(),
4536 cx,
4537 ) =>
4538 {
4539 self.show_completions(
4540 &ShowCompletions {
4541 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4542 },
4543 window,
4544 cx,
4545 )
4546 }
4547 _ => {
4548 self.hide_context_menu(window, cx);
4549 }
4550 }
4551 }
4552
4553 fn is_completion_trigger(
4554 &self,
4555 text: &str,
4556 trigger_in_words: bool,
4557 menu_is_open: bool,
4558 cx: &mut Context<Self>,
4559 ) -> bool {
4560 let position = self.selections.newest_anchor().head();
4561 let multibuffer = self.buffer.read(cx);
4562 let Some(buffer) = position
4563 .buffer_id
4564 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4565 else {
4566 return false;
4567 };
4568
4569 if let Some(completion_provider) = &self.completion_provider {
4570 completion_provider.is_completion_trigger(
4571 &buffer,
4572 position.text_anchor,
4573 text,
4574 trigger_in_words,
4575 menu_is_open,
4576 cx,
4577 )
4578 } else {
4579 false
4580 }
4581 }
4582
4583 /// If any empty selections is touching the start of its innermost containing autoclose
4584 /// region, expand it to select the brackets.
4585 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4586 let selections = self.selections.all::<usize>(cx);
4587 let buffer = self.buffer.read(cx).read(cx);
4588 let new_selections = self
4589 .selections_with_autoclose_regions(selections, &buffer)
4590 .map(|(mut selection, region)| {
4591 if !selection.is_empty() {
4592 return selection;
4593 }
4594
4595 if let Some(region) = region {
4596 let mut range = region.range.to_offset(&buffer);
4597 if selection.start == range.start && range.start >= region.pair.start.len() {
4598 range.start -= region.pair.start.len();
4599 if buffer.contains_str_at(range.start, ®ion.pair.start)
4600 && buffer.contains_str_at(range.end, ®ion.pair.end)
4601 {
4602 range.end += region.pair.end.len();
4603 selection.start = range.start;
4604 selection.end = range.end;
4605
4606 return selection;
4607 }
4608 }
4609 }
4610
4611 let always_treat_brackets_as_autoclosed = buffer
4612 .language_settings_at(selection.start, cx)
4613 .always_treat_brackets_as_autoclosed;
4614
4615 if !always_treat_brackets_as_autoclosed {
4616 return selection;
4617 }
4618
4619 if let Some(scope) = buffer.language_scope_at(selection.start) {
4620 for (pair, enabled) in scope.brackets() {
4621 if !enabled || !pair.close {
4622 continue;
4623 }
4624
4625 if buffer.contains_str_at(selection.start, &pair.end) {
4626 let pair_start_len = pair.start.len();
4627 if buffer.contains_str_at(
4628 selection.start.saturating_sub(pair_start_len),
4629 &pair.start,
4630 ) {
4631 selection.start -= pair_start_len;
4632 selection.end += pair.end.len();
4633
4634 return selection;
4635 }
4636 }
4637 }
4638 }
4639
4640 selection
4641 })
4642 .collect();
4643
4644 drop(buffer);
4645 self.change_selections(None, window, cx, |selections| {
4646 selections.select(new_selections)
4647 });
4648 }
4649
4650 /// Iterate the given selections, and for each one, find the smallest surrounding
4651 /// autoclose region. This uses the ordering of the selections and the autoclose
4652 /// regions to avoid repeated comparisons.
4653 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4654 &'a self,
4655 selections: impl IntoIterator<Item = Selection<D>>,
4656 buffer: &'a MultiBufferSnapshot,
4657 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4658 let mut i = 0;
4659 let mut regions = self.autoclose_regions.as_slice();
4660 selections.into_iter().map(move |selection| {
4661 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4662
4663 let mut enclosing = None;
4664 while let Some(pair_state) = regions.get(i) {
4665 if pair_state.range.end.to_offset(buffer) < range.start {
4666 regions = ®ions[i + 1..];
4667 i = 0;
4668 } else if pair_state.range.start.to_offset(buffer) > range.end {
4669 break;
4670 } else {
4671 if pair_state.selection_id == selection.id {
4672 enclosing = Some(pair_state);
4673 }
4674 i += 1;
4675 }
4676 }
4677
4678 (selection, enclosing)
4679 })
4680 }
4681
4682 /// Remove any autoclose regions that no longer contain their selection.
4683 fn invalidate_autoclose_regions(
4684 &mut self,
4685 mut selections: &[Selection<Anchor>],
4686 buffer: &MultiBufferSnapshot,
4687 ) {
4688 self.autoclose_regions.retain(|state| {
4689 let mut i = 0;
4690 while let Some(selection) = selections.get(i) {
4691 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4692 selections = &selections[1..];
4693 continue;
4694 }
4695 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4696 break;
4697 }
4698 if selection.id == state.selection_id {
4699 return true;
4700 } else {
4701 i += 1;
4702 }
4703 }
4704 false
4705 });
4706 }
4707
4708 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4709 let offset = position.to_offset(buffer);
4710 let (word_range, kind) = buffer.surrounding_word(offset, true);
4711 if offset > word_range.start && kind == Some(CharKind::Word) {
4712 Some(
4713 buffer
4714 .text_for_range(word_range.start..offset)
4715 .collect::<String>(),
4716 )
4717 } else {
4718 None
4719 }
4720 }
4721
4722 pub fn toggle_inline_values(
4723 &mut self,
4724 _: &ToggleInlineValues,
4725 _: &mut Window,
4726 cx: &mut Context<Self>,
4727 ) {
4728 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4729
4730 self.refresh_inline_values(cx);
4731 }
4732
4733 pub fn toggle_inlay_hints(
4734 &mut self,
4735 _: &ToggleInlayHints,
4736 _: &mut Window,
4737 cx: &mut Context<Self>,
4738 ) {
4739 self.refresh_inlay_hints(
4740 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4741 cx,
4742 );
4743 }
4744
4745 pub fn inlay_hints_enabled(&self) -> bool {
4746 self.inlay_hint_cache.enabled
4747 }
4748
4749 pub fn inline_values_enabled(&self) -> bool {
4750 self.inline_value_cache.enabled
4751 }
4752
4753 #[cfg(any(test, feature = "test-support"))]
4754 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4755 self.display_map
4756 .read(cx)
4757 .current_inlays()
4758 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4759 .cloned()
4760 .collect()
4761 }
4762
4763 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4764 if self.semantics_provider.is_none() || !self.mode.is_full() {
4765 return;
4766 }
4767
4768 let reason_description = reason.description();
4769 let ignore_debounce = matches!(
4770 reason,
4771 InlayHintRefreshReason::SettingsChange(_)
4772 | InlayHintRefreshReason::Toggle(_)
4773 | InlayHintRefreshReason::ExcerptsRemoved(_)
4774 | InlayHintRefreshReason::ModifiersChanged(_)
4775 );
4776 let (invalidate_cache, required_languages) = match reason {
4777 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4778 match self.inlay_hint_cache.modifiers_override(enabled) {
4779 Some(enabled) => {
4780 if enabled {
4781 (InvalidationStrategy::RefreshRequested, None)
4782 } else {
4783 self.splice_inlays(
4784 &self
4785 .visible_inlay_hints(cx)
4786 .iter()
4787 .map(|inlay| inlay.id)
4788 .collect::<Vec<InlayId>>(),
4789 Vec::new(),
4790 cx,
4791 );
4792 return;
4793 }
4794 }
4795 None => return,
4796 }
4797 }
4798 InlayHintRefreshReason::Toggle(enabled) => {
4799 if self.inlay_hint_cache.toggle(enabled) {
4800 if enabled {
4801 (InvalidationStrategy::RefreshRequested, None)
4802 } else {
4803 self.splice_inlays(
4804 &self
4805 .visible_inlay_hints(cx)
4806 .iter()
4807 .map(|inlay| inlay.id)
4808 .collect::<Vec<InlayId>>(),
4809 Vec::new(),
4810 cx,
4811 );
4812 return;
4813 }
4814 } else {
4815 return;
4816 }
4817 }
4818 InlayHintRefreshReason::SettingsChange(new_settings) => {
4819 match self.inlay_hint_cache.update_settings(
4820 &self.buffer,
4821 new_settings,
4822 self.visible_inlay_hints(cx),
4823 cx,
4824 ) {
4825 ControlFlow::Break(Some(InlaySplice {
4826 to_remove,
4827 to_insert,
4828 })) => {
4829 self.splice_inlays(&to_remove, to_insert, cx);
4830 return;
4831 }
4832 ControlFlow::Break(None) => return,
4833 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4834 }
4835 }
4836 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4837 if let Some(InlaySplice {
4838 to_remove,
4839 to_insert,
4840 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4841 {
4842 self.splice_inlays(&to_remove, to_insert, cx);
4843 }
4844 self.display_map.update(cx, |display_map, _| {
4845 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4846 });
4847 return;
4848 }
4849 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4850 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4851 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4852 }
4853 InlayHintRefreshReason::RefreshRequested => {
4854 (InvalidationStrategy::RefreshRequested, None)
4855 }
4856 };
4857
4858 if let Some(InlaySplice {
4859 to_remove,
4860 to_insert,
4861 }) = self.inlay_hint_cache.spawn_hint_refresh(
4862 reason_description,
4863 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4864 invalidate_cache,
4865 ignore_debounce,
4866 cx,
4867 ) {
4868 self.splice_inlays(&to_remove, to_insert, cx);
4869 }
4870 }
4871
4872 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4873 self.display_map
4874 .read(cx)
4875 .current_inlays()
4876 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4877 .cloned()
4878 .collect()
4879 }
4880
4881 pub fn excerpts_for_inlay_hints_query(
4882 &self,
4883 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4884 cx: &mut Context<Editor>,
4885 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4886 let Some(project) = self.project.as_ref() else {
4887 return HashMap::default();
4888 };
4889 let project = project.read(cx);
4890 let multi_buffer = self.buffer().read(cx);
4891 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4892 let multi_buffer_visible_start = self
4893 .scroll_manager
4894 .anchor()
4895 .anchor
4896 .to_point(&multi_buffer_snapshot);
4897 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4898 multi_buffer_visible_start
4899 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4900 Bias::Left,
4901 );
4902 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4903 multi_buffer_snapshot
4904 .range_to_buffer_ranges(multi_buffer_visible_range)
4905 .into_iter()
4906 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4907 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4908 let buffer_file = project::File::from_dyn(buffer.file())?;
4909 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4910 let worktree_entry = buffer_worktree
4911 .read(cx)
4912 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4913 if worktree_entry.is_ignored {
4914 return None;
4915 }
4916
4917 let language = buffer.language()?;
4918 if let Some(restrict_to_languages) = restrict_to_languages {
4919 if !restrict_to_languages.contains(language) {
4920 return None;
4921 }
4922 }
4923 Some((
4924 excerpt_id,
4925 (
4926 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4927 buffer.version().clone(),
4928 excerpt_visible_range,
4929 ),
4930 ))
4931 })
4932 .collect()
4933 }
4934
4935 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4936 TextLayoutDetails {
4937 text_system: window.text_system().clone(),
4938 editor_style: self.style.clone().unwrap(),
4939 rem_size: window.rem_size(),
4940 scroll_anchor: self.scroll_manager.anchor(),
4941 visible_rows: self.visible_line_count(),
4942 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4943 }
4944 }
4945
4946 pub fn splice_inlays(
4947 &self,
4948 to_remove: &[InlayId],
4949 to_insert: Vec<Inlay>,
4950 cx: &mut Context<Self>,
4951 ) {
4952 self.display_map.update(cx, |display_map, cx| {
4953 display_map.splice_inlays(to_remove, to_insert, cx)
4954 });
4955 cx.notify();
4956 }
4957
4958 fn trigger_on_type_formatting(
4959 &self,
4960 input: String,
4961 window: &mut Window,
4962 cx: &mut Context<Self>,
4963 ) -> Option<Task<Result<()>>> {
4964 if input.len() != 1 {
4965 return None;
4966 }
4967
4968 let project = self.project.as_ref()?;
4969 let position = self.selections.newest_anchor().head();
4970 let (buffer, buffer_position) = self
4971 .buffer
4972 .read(cx)
4973 .text_anchor_for_position(position, cx)?;
4974
4975 let settings = language_settings::language_settings(
4976 buffer
4977 .read(cx)
4978 .language_at(buffer_position)
4979 .map(|l| l.name()),
4980 buffer.read(cx).file(),
4981 cx,
4982 );
4983 if !settings.use_on_type_format {
4984 return None;
4985 }
4986
4987 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4988 // hence we do LSP request & edit on host side only — add formats to host's history.
4989 let push_to_lsp_host_history = true;
4990 // If this is not the host, append its history with new edits.
4991 let push_to_client_history = project.read(cx).is_via_collab();
4992
4993 let on_type_formatting = project.update(cx, |project, cx| {
4994 project.on_type_format(
4995 buffer.clone(),
4996 buffer_position,
4997 input,
4998 push_to_lsp_host_history,
4999 cx,
5000 )
5001 });
5002 Some(cx.spawn_in(window, async move |editor, cx| {
5003 if let Some(transaction) = on_type_formatting.await? {
5004 if push_to_client_history {
5005 buffer
5006 .update(cx, |buffer, _| {
5007 buffer.push_transaction(transaction, Instant::now());
5008 buffer.finalize_last_transaction();
5009 })
5010 .ok();
5011 }
5012 editor.update(cx, |editor, cx| {
5013 editor.refresh_document_highlights(cx);
5014 })?;
5015 }
5016 Ok(())
5017 }))
5018 }
5019
5020 pub fn show_word_completions(
5021 &mut self,
5022 _: &ShowWordCompletions,
5023 window: &mut Window,
5024 cx: &mut Context<Self>,
5025 ) {
5026 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5027 }
5028
5029 pub fn show_completions(
5030 &mut self,
5031 options: &ShowCompletions,
5032 window: &mut Window,
5033 cx: &mut Context<Self>,
5034 ) {
5035 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5036 }
5037
5038 fn open_or_update_completions_menu(
5039 &mut self,
5040 requested_source: Option<CompletionsMenuSource>,
5041 trigger: Option<&str>,
5042 window: &mut Window,
5043 cx: &mut Context<Self>,
5044 ) {
5045 if self.pending_rename.is_some() {
5046 return;
5047 }
5048
5049 let position = self.selections.newest_anchor().head();
5050 if position.diff_base_anchor.is_some() {
5051 return;
5052 }
5053 let (buffer, buffer_position) =
5054 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5055 output
5056 } else {
5057 return;
5058 };
5059 let buffer_snapshot = buffer.read(cx).snapshot();
5060
5061 let query: Option<Arc<String>> =
5062 Self::completion_query(&self.buffer.read(cx).read(cx), position)
5063 .map(|query| query.into());
5064
5065 let provider = match requested_source {
5066 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5067 Some(CompletionsMenuSource::Words) => None,
5068 Some(CompletionsMenuSource::SnippetChoices) => {
5069 log::error!("bug: SnippetChoices requested_source is not handled");
5070 None
5071 }
5072 };
5073
5074 let sort_completions = provider
5075 .as_ref()
5076 .map_or(false, |provider| provider.sort_completions());
5077
5078 let filter_completions = provider
5079 .as_ref()
5080 .map_or(true, |provider| provider.filter_completions());
5081
5082 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5083 // current query is a suffix of the initial query.
5084 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5085 if !menu.is_incomplete && filter_completions {
5086 // If the new query is a suffix of the old query (typing more characters) and
5087 // the previous result was complete, the existing completions can be filtered.
5088 //
5089 // Note that this is always true for snippet completions.
5090 let query_matches = match (&menu.initial_query, &query) {
5091 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5092 (None, _) => true,
5093 _ => false,
5094 };
5095 if query_matches {
5096 let position_matches = if menu.initial_position == position {
5097 true
5098 } else {
5099 let snapshot = self.buffer.read(cx).read(cx);
5100 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5101 };
5102 if position_matches {
5103 menu.filter(query.clone(), provider.clone(), window, cx);
5104 return;
5105 }
5106 }
5107 }
5108 };
5109
5110 let trigger_kind = match trigger {
5111 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5112 CompletionTriggerKind::TRIGGER_CHARACTER
5113 }
5114 _ => CompletionTriggerKind::INVOKED,
5115 };
5116 let completion_context = CompletionContext {
5117 trigger_character: trigger.and_then(|trigger| {
5118 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5119 Some(String::from(trigger))
5120 } else {
5121 None
5122 }
5123 }),
5124 trigger_kind,
5125 };
5126
5127 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5128 buffer_snapshot.surrounding_word(buffer_position)
5129 {
5130 let word_to_exclude = buffer_snapshot
5131 .text_for_range(word_range.clone())
5132 .collect::<String>();
5133 (
5134 buffer_snapshot.anchor_before(word_range.start)
5135 ..buffer_snapshot.anchor_after(buffer_position),
5136 Some(word_to_exclude),
5137 )
5138 } else {
5139 (buffer_position..buffer_position, None)
5140 };
5141
5142 let language = buffer_snapshot
5143 .language_at(buffer_position)
5144 .map(|language| language.name());
5145
5146 let completion_settings =
5147 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5148
5149 let show_completion_documentation = buffer_snapshot
5150 .settings_at(buffer_position, cx)
5151 .show_completion_documentation;
5152
5153 // The document can be large, so stay in reasonable bounds when searching for words,
5154 // otherwise completion pop-up might be slow to appear.
5155 const WORD_LOOKUP_ROWS: u32 = 5_000;
5156 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5157 let min_word_search = buffer_snapshot.clip_point(
5158 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5159 Bias::Left,
5160 );
5161 let max_word_search = buffer_snapshot.clip_point(
5162 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5163 Bias::Right,
5164 );
5165 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5166 ..buffer_snapshot.point_to_offset(max_word_search);
5167
5168 let skip_digits = query
5169 .as_ref()
5170 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5171
5172 let (mut words, provider_responses) = match &provider {
5173 Some(provider) => {
5174 let provider_responses = provider.completions(
5175 position.excerpt_id,
5176 &buffer,
5177 buffer_position,
5178 completion_context,
5179 window,
5180 cx,
5181 );
5182
5183 let words = match completion_settings.words {
5184 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5185 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5186 .background_spawn(async move {
5187 buffer_snapshot.words_in_range(WordsQuery {
5188 fuzzy_contents: None,
5189 range: word_search_range,
5190 skip_digits,
5191 })
5192 }),
5193 };
5194
5195 (words, provider_responses)
5196 }
5197 None => (
5198 cx.background_spawn(async move {
5199 buffer_snapshot.words_in_range(WordsQuery {
5200 fuzzy_contents: None,
5201 range: word_search_range,
5202 skip_digits,
5203 })
5204 }),
5205 Task::ready(Ok(Vec::new())),
5206 ),
5207 };
5208
5209 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5210
5211 let id = post_inc(&mut self.next_completion_id);
5212 let task = cx.spawn_in(window, async move |editor, cx| {
5213 let Ok(()) = editor.update(cx, |this, _| {
5214 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5215 }) else {
5216 return;
5217 };
5218
5219 // TODO: Ideally completions from different sources would be selectively re-queried, so
5220 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5221 let mut completions = Vec::new();
5222 let mut is_incomplete = false;
5223 if let Some(provider_responses) = provider_responses.await.log_err() {
5224 if !provider_responses.is_empty() {
5225 for response in provider_responses {
5226 completions.extend(response.completions);
5227 is_incomplete = is_incomplete || response.is_incomplete;
5228 }
5229 if completion_settings.words == WordsCompletionMode::Fallback {
5230 words = Task::ready(BTreeMap::default());
5231 }
5232 }
5233 }
5234
5235 let mut words = words.await;
5236 if let Some(word_to_exclude) = &word_to_exclude {
5237 words.remove(word_to_exclude);
5238 }
5239 for lsp_completion in &completions {
5240 words.remove(&lsp_completion.new_text);
5241 }
5242 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5243 replace_range: word_replace_range.clone(),
5244 new_text: word.clone(),
5245 label: CodeLabel::plain(word, None),
5246 icon_path: None,
5247 documentation: None,
5248 source: CompletionSource::BufferWord {
5249 word_range,
5250 resolved: false,
5251 },
5252 insert_text_mode: Some(InsertTextMode::AS_IS),
5253 confirm: None,
5254 }));
5255
5256 let menu = if completions.is_empty() {
5257 None
5258 } else {
5259 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5260 let languages = editor
5261 .workspace
5262 .as_ref()
5263 .and_then(|(workspace, _)| workspace.upgrade())
5264 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5265 let menu = CompletionsMenu::new(
5266 id,
5267 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5268 sort_completions,
5269 show_completion_documentation,
5270 position,
5271 query.clone(),
5272 is_incomplete,
5273 buffer.clone(),
5274 completions.into(),
5275 snippet_sort_order,
5276 languages,
5277 language,
5278 cx,
5279 );
5280
5281 let query = if filter_completions { query } else { None };
5282 let matches_task = if let Some(query) = query {
5283 menu.do_async_filtering(query, cx)
5284 } else {
5285 Task::ready(menu.unfiltered_matches())
5286 };
5287 (menu, matches_task)
5288 }) else {
5289 return;
5290 };
5291
5292 let matches = matches_task.await;
5293
5294 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5295 // Newer menu already set, so exit.
5296 match editor.context_menu.borrow().as_ref() {
5297 Some(CodeContextMenu::Completions(prev_menu)) => {
5298 if prev_menu.id > id {
5299 return;
5300 }
5301 }
5302 _ => {}
5303 };
5304
5305 // Only valid to take prev_menu because it the new menu is immediately set
5306 // below, or the menu is hidden.
5307 match editor.context_menu.borrow_mut().take() {
5308 Some(CodeContextMenu::Completions(prev_menu)) => {
5309 let position_matches =
5310 if prev_menu.initial_position == menu.initial_position {
5311 true
5312 } else {
5313 let snapshot = editor.buffer.read(cx).read(cx);
5314 prev_menu.initial_position.to_offset(&snapshot)
5315 == menu.initial_position.to_offset(&snapshot)
5316 };
5317 if position_matches {
5318 // Preserve markdown cache before `set_filter_results` because it will
5319 // try to populate the documentation cache.
5320 menu.preserve_markdown_cache(prev_menu);
5321 }
5322 }
5323 _ => {}
5324 };
5325
5326 menu.set_filter_results(matches, provider, window, cx);
5327 }) else {
5328 return;
5329 };
5330
5331 menu.visible().then_some(menu)
5332 };
5333
5334 editor
5335 .update_in(cx, |editor, window, cx| {
5336 if editor.focus_handle.is_focused(window) {
5337 if let Some(menu) = menu {
5338 *editor.context_menu.borrow_mut() =
5339 Some(CodeContextMenu::Completions(menu));
5340
5341 crate::hover_popover::hide_hover(editor, cx);
5342 if editor.show_edit_predictions_in_menu() {
5343 editor.update_visible_inline_completion(window, cx);
5344 } else {
5345 editor.discard_inline_completion(false, cx);
5346 }
5347
5348 cx.notify();
5349 return;
5350 }
5351 }
5352
5353 if editor.completion_tasks.len() <= 1 {
5354 // If there are no more completion tasks and the last menu was empty, we should hide it.
5355 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5356 // If it was already hidden and we don't show inline completions in the menu, we should
5357 // also show the inline-completion when available.
5358 if was_hidden && editor.show_edit_predictions_in_menu() {
5359 editor.update_visible_inline_completion(window, cx);
5360 }
5361 }
5362 })
5363 .ok();
5364 });
5365
5366 self.completion_tasks.push((id, task));
5367 }
5368
5369 #[cfg(feature = "test-support")]
5370 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5371 let menu = self.context_menu.borrow();
5372 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5373 let completions = menu.completions.borrow();
5374 Some(completions.to_vec())
5375 } else {
5376 None
5377 }
5378 }
5379
5380 pub fn with_completions_menu_matching_id<R>(
5381 &self,
5382 id: CompletionId,
5383 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5384 ) -> R {
5385 let mut context_menu = self.context_menu.borrow_mut();
5386 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5387 return f(None);
5388 };
5389 if completions_menu.id != id {
5390 return f(None);
5391 }
5392 f(Some(completions_menu))
5393 }
5394
5395 pub fn confirm_completion(
5396 &mut self,
5397 action: &ConfirmCompletion,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) -> Option<Task<Result<()>>> {
5401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5402 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5403 }
5404
5405 pub fn confirm_completion_insert(
5406 &mut self,
5407 _: &ConfirmCompletionInsert,
5408 window: &mut Window,
5409 cx: &mut Context<Self>,
5410 ) -> Option<Task<Result<()>>> {
5411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5412 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5413 }
5414
5415 pub fn confirm_completion_replace(
5416 &mut self,
5417 _: &ConfirmCompletionReplace,
5418 window: &mut Window,
5419 cx: &mut Context<Self>,
5420 ) -> Option<Task<Result<()>>> {
5421 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5422 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5423 }
5424
5425 pub fn compose_completion(
5426 &mut self,
5427 action: &ComposeCompletion,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) -> Option<Task<Result<()>>> {
5431 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5432 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5433 }
5434
5435 fn do_completion(
5436 &mut self,
5437 item_ix: Option<usize>,
5438 intent: CompletionIntent,
5439 window: &mut Window,
5440 cx: &mut Context<Editor>,
5441 ) -> Option<Task<Result<()>>> {
5442 use language::ToOffset as _;
5443
5444 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5445 else {
5446 return None;
5447 };
5448
5449 let candidate_id = {
5450 let entries = completions_menu.entries.borrow();
5451 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5452 if self.show_edit_predictions_in_menu() {
5453 self.discard_inline_completion(true, cx);
5454 }
5455 mat.candidate_id
5456 };
5457
5458 let completion = completions_menu
5459 .completions
5460 .borrow()
5461 .get(candidate_id)?
5462 .clone();
5463 cx.stop_propagation();
5464
5465 let buffer_handle = completions_menu.buffer.clone();
5466
5467 let CompletionEdit {
5468 new_text,
5469 snippet,
5470 replace_range,
5471 } = process_completion_for_edit(
5472 &completion,
5473 intent,
5474 &buffer_handle,
5475 &completions_menu.initial_position.text_anchor,
5476 cx,
5477 );
5478
5479 let buffer = buffer_handle.read(cx);
5480 let snapshot = self.buffer.read(cx).snapshot(cx);
5481 let newest_anchor = self.selections.newest_anchor();
5482 let replace_range_multibuffer = {
5483 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5484 let multibuffer_anchor = snapshot
5485 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5486 .unwrap()
5487 ..snapshot
5488 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5489 .unwrap();
5490 multibuffer_anchor.start.to_offset(&snapshot)
5491 ..multibuffer_anchor.end.to_offset(&snapshot)
5492 };
5493 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5494 return None;
5495 }
5496
5497 let old_text = buffer
5498 .text_for_range(replace_range.clone())
5499 .collect::<String>();
5500 let lookbehind = newest_anchor
5501 .start
5502 .text_anchor
5503 .to_offset(buffer)
5504 .saturating_sub(replace_range.start);
5505 let lookahead = replace_range
5506 .end
5507 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5508 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5509 let suffix = &old_text[lookbehind.min(old_text.len())..];
5510
5511 let selections = self.selections.all::<usize>(cx);
5512 let mut ranges = Vec::new();
5513 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5514
5515 for selection in &selections {
5516 let range = if selection.id == newest_anchor.id {
5517 replace_range_multibuffer.clone()
5518 } else {
5519 let mut range = selection.range();
5520
5521 // if prefix is present, don't duplicate it
5522 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5523 range.start = range.start.saturating_sub(lookbehind);
5524
5525 // if suffix is also present, mimic the newest cursor and replace it
5526 if selection.id != newest_anchor.id
5527 && snapshot.contains_str_at(range.end, suffix)
5528 {
5529 range.end += lookahead;
5530 }
5531 }
5532 range
5533 };
5534
5535 ranges.push(range.clone());
5536
5537 if !self.linked_edit_ranges.is_empty() {
5538 let start_anchor = snapshot.anchor_before(range.start);
5539 let end_anchor = snapshot.anchor_after(range.end);
5540 if let Some(ranges) = self
5541 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5542 {
5543 for (buffer, edits) in ranges {
5544 linked_edits
5545 .entry(buffer.clone())
5546 .or_default()
5547 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5548 }
5549 }
5550 }
5551 }
5552
5553 let common_prefix_len = old_text
5554 .chars()
5555 .zip(new_text.chars())
5556 .take_while(|(a, b)| a == b)
5557 .map(|(a, _)| a.len_utf8())
5558 .sum::<usize>();
5559
5560 cx.emit(EditorEvent::InputHandled {
5561 utf16_range_to_replace: None,
5562 text: new_text[common_prefix_len..].into(),
5563 });
5564
5565 self.transact(window, cx, |this, window, cx| {
5566 if let Some(mut snippet) = snippet {
5567 snippet.text = new_text.to_string();
5568 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5569 } else {
5570 this.buffer.update(cx, |buffer, cx| {
5571 let auto_indent = match completion.insert_text_mode {
5572 Some(InsertTextMode::AS_IS) => None,
5573 _ => this.autoindent_mode.clone(),
5574 };
5575 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5576 buffer.edit(edits, auto_indent, cx);
5577 });
5578 }
5579 for (buffer, edits) in linked_edits {
5580 buffer.update(cx, |buffer, cx| {
5581 let snapshot = buffer.snapshot();
5582 let edits = edits
5583 .into_iter()
5584 .map(|(range, text)| {
5585 use text::ToPoint as TP;
5586 let end_point = TP::to_point(&range.end, &snapshot);
5587 let start_point = TP::to_point(&range.start, &snapshot);
5588 (start_point..end_point, text)
5589 })
5590 .sorted_by_key(|(range, _)| range.start);
5591 buffer.edit(edits, None, cx);
5592 })
5593 }
5594
5595 this.refresh_inline_completion(true, false, window, cx);
5596 });
5597
5598 let show_new_completions_on_confirm = completion
5599 .confirm
5600 .as_ref()
5601 .map_or(false, |confirm| confirm(intent, window, cx));
5602 if show_new_completions_on_confirm {
5603 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5604 }
5605
5606 let provider = self.completion_provider.as_ref()?;
5607 drop(completion);
5608 let apply_edits = provider.apply_additional_edits_for_completion(
5609 buffer_handle,
5610 completions_menu.completions.clone(),
5611 candidate_id,
5612 true,
5613 cx,
5614 );
5615
5616 let editor_settings = EditorSettings::get_global(cx);
5617 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5618 // After the code completion is finished, users often want to know what signatures are needed.
5619 // so we should automatically call signature_help
5620 self.show_signature_help(&ShowSignatureHelp, window, cx);
5621 }
5622
5623 Some(cx.foreground_executor().spawn(async move {
5624 apply_edits.await?;
5625 Ok(())
5626 }))
5627 }
5628
5629 pub fn toggle_code_actions(
5630 &mut self,
5631 action: &ToggleCodeActions,
5632 window: &mut Window,
5633 cx: &mut Context<Self>,
5634 ) {
5635 let quick_launch = action.quick_launch;
5636 let mut context_menu = self.context_menu.borrow_mut();
5637 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5638 if code_actions.deployed_from == action.deployed_from {
5639 // Toggle if we're selecting the same one
5640 *context_menu = None;
5641 cx.notify();
5642 return;
5643 } else {
5644 // Otherwise, clear it and start a new one
5645 *context_menu = None;
5646 cx.notify();
5647 }
5648 }
5649 drop(context_menu);
5650 let snapshot = self.snapshot(window, cx);
5651 let deployed_from = action.deployed_from.clone();
5652 let mut task = self.code_actions_task.take();
5653 let action = action.clone();
5654 cx.spawn_in(window, async move |editor, cx| {
5655 while let Some(prev_task) = task {
5656 prev_task.await.log_err();
5657 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5658 }
5659
5660 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5661 if editor.focus_handle.is_focused(window) {
5662 let multibuffer_point = match &action.deployed_from {
5663 Some(CodeActionSource::Indicator(row)) => {
5664 DisplayPoint::new(*row, 0).to_point(&snapshot)
5665 }
5666 _ => editor.selections.newest::<Point>(cx).head(),
5667 };
5668 let (buffer, buffer_row) = snapshot
5669 .buffer_snapshot
5670 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5671 .and_then(|(buffer_snapshot, range)| {
5672 editor
5673 .buffer
5674 .read(cx)
5675 .buffer(buffer_snapshot.remote_id())
5676 .map(|buffer| (buffer, range.start.row))
5677 })?;
5678 let (_, code_actions) = editor
5679 .available_code_actions
5680 .clone()
5681 .and_then(|(location, code_actions)| {
5682 let snapshot = location.buffer.read(cx).snapshot();
5683 let point_range = location.range.to_point(&snapshot);
5684 let point_range = point_range.start.row..=point_range.end.row;
5685 if point_range.contains(&buffer_row) {
5686 Some((location, code_actions))
5687 } else {
5688 None
5689 }
5690 })
5691 .unzip();
5692 let buffer_id = buffer.read(cx).remote_id();
5693 let tasks = editor
5694 .tasks
5695 .get(&(buffer_id, buffer_row))
5696 .map(|t| Arc::new(t.to_owned()));
5697 if tasks.is_none() && code_actions.is_none() {
5698 return None;
5699 }
5700
5701 editor.completion_tasks.clear();
5702 editor.discard_inline_completion(false, cx);
5703 let task_context =
5704 tasks
5705 .as_ref()
5706 .zip(editor.project.clone())
5707 .map(|(tasks, project)| {
5708 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5709 });
5710
5711 Some(cx.spawn_in(window, async move |editor, cx| {
5712 let task_context = match task_context {
5713 Some(task_context) => task_context.await,
5714 None => None,
5715 };
5716 let resolved_tasks =
5717 tasks
5718 .zip(task_context.clone())
5719 .map(|(tasks, task_context)| ResolvedTasks {
5720 templates: tasks.resolve(&task_context).collect(),
5721 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5722 multibuffer_point.row,
5723 tasks.column,
5724 )),
5725 });
5726 let debug_scenarios = editor.update(cx, |editor, cx| {
5727 if cx.has_flag::<DebuggerFeatureFlag>() {
5728 maybe!({
5729 let project = editor.project.as_ref()?;
5730 let dap_store = project.read(cx).dap_store();
5731 let mut scenarios = vec![];
5732 let resolved_tasks = resolved_tasks.as_ref()?;
5733 let buffer = buffer.read(cx);
5734 let language = buffer.language()?;
5735 let file = buffer.file();
5736 let debug_adapter =
5737 language_settings(language.name().into(), file, cx)
5738 .debuggers
5739 .first()
5740 .map(SharedString::from)
5741 .or_else(|| {
5742 language
5743 .config()
5744 .debuggers
5745 .first()
5746 .map(SharedString::from)
5747 })?;
5748
5749 dap_store.update(cx, |dap_store, cx| {
5750 for (_, task) in &resolved_tasks.templates {
5751 if let Some(scenario) = dap_store
5752 .debug_scenario_for_build_task(
5753 task.original_task().clone(),
5754 debug_adapter.clone().into(),
5755 task.display_label().to_owned().into(),
5756 cx,
5757 )
5758 {
5759 scenarios.push(scenario);
5760 }
5761 }
5762 });
5763 Some(scenarios)
5764 })
5765 .unwrap_or_default()
5766 } else {
5767 vec![]
5768 }
5769 })?;
5770 let spawn_straight_away = quick_launch
5771 && resolved_tasks
5772 .as_ref()
5773 .map_or(false, |tasks| tasks.templates.len() == 1)
5774 && code_actions
5775 .as_ref()
5776 .map_or(true, |actions| actions.is_empty())
5777 && debug_scenarios.is_empty();
5778 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5779 crate::hover_popover::hide_hover(editor, cx);
5780 *editor.context_menu.borrow_mut() =
5781 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5782 buffer,
5783 actions: CodeActionContents::new(
5784 resolved_tasks,
5785 code_actions,
5786 debug_scenarios,
5787 task_context.unwrap_or_default(),
5788 ),
5789 selected_item: Default::default(),
5790 scroll_handle: UniformListScrollHandle::default(),
5791 deployed_from,
5792 }));
5793 if spawn_straight_away {
5794 if let Some(task) = editor.confirm_code_action(
5795 &ConfirmCodeAction { item_ix: Some(0) },
5796 window,
5797 cx,
5798 ) {
5799 cx.notify();
5800 return task;
5801 }
5802 }
5803 cx.notify();
5804 Task::ready(Ok(()))
5805 }) {
5806 task.await
5807 } else {
5808 Ok(())
5809 }
5810 }))
5811 } else {
5812 Some(Task::ready(Ok(())))
5813 }
5814 })?;
5815 if let Some(task) = spawned_test_task {
5816 task.await?;
5817 }
5818
5819 anyhow::Ok(())
5820 })
5821 .detach_and_log_err(cx);
5822 }
5823
5824 pub fn confirm_code_action(
5825 &mut self,
5826 action: &ConfirmCodeAction,
5827 window: &mut Window,
5828 cx: &mut Context<Self>,
5829 ) -> Option<Task<Result<()>>> {
5830 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5831
5832 let actions_menu =
5833 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5834 menu
5835 } else {
5836 return None;
5837 };
5838
5839 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5840 let action = actions_menu.actions.get(action_ix)?;
5841 let title = action.label();
5842 let buffer = actions_menu.buffer;
5843 let workspace = self.workspace()?;
5844
5845 match action {
5846 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5847 workspace.update(cx, |workspace, cx| {
5848 workspace.schedule_resolved_task(
5849 task_source_kind,
5850 resolved_task,
5851 false,
5852 window,
5853 cx,
5854 );
5855
5856 Some(Task::ready(Ok(())))
5857 })
5858 }
5859 CodeActionsItem::CodeAction {
5860 excerpt_id,
5861 action,
5862 provider,
5863 } => {
5864 let apply_code_action =
5865 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5866 let workspace = workspace.downgrade();
5867 Some(cx.spawn_in(window, async move |editor, cx| {
5868 let project_transaction = apply_code_action.await?;
5869 Self::open_project_transaction(
5870 &editor,
5871 workspace,
5872 project_transaction,
5873 title,
5874 cx,
5875 )
5876 .await
5877 }))
5878 }
5879 CodeActionsItem::DebugScenario(scenario) => {
5880 let context = actions_menu.actions.context.clone();
5881
5882 workspace.update(cx, |workspace, cx| {
5883 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5884 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5885 });
5886 Some(Task::ready(Ok(())))
5887 }
5888 }
5889 }
5890
5891 pub async fn open_project_transaction(
5892 this: &WeakEntity<Editor>,
5893 workspace: WeakEntity<Workspace>,
5894 transaction: ProjectTransaction,
5895 title: String,
5896 cx: &mut AsyncWindowContext,
5897 ) -> Result<()> {
5898 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5899 cx.update(|_, cx| {
5900 entries.sort_unstable_by_key(|(buffer, _)| {
5901 buffer.read(cx).file().map(|f| f.path().clone())
5902 });
5903 })?;
5904
5905 // If the project transaction's edits are all contained within this editor, then
5906 // avoid opening a new editor to display them.
5907
5908 if let Some((buffer, transaction)) = entries.first() {
5909 if entries.len() == 1 {
5910 let excerpt = this.update(cx, |editor, cx| {
5911 editor
5912 .buffer()
5913 .read(cx)
5914 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5915 })?;
5916 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5917 if excerpted_buffer == *buffer {
5918 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5919 let excerpt_range = excerpt_range.to_offset(buffer);
5920 buffer
5921 .edited_ranges_for_transaction::<usize>(transaction)
5922 .all(|range| {
5923 excerpt_range.start <= range.start
5924 && excerpt_range.end >= range.end
5925 })
5926 })?;
5927
5928 if all_edits_within_excerpt {
5929 return Ok(());
5930 }
5931 }
5932 }
5933 }
5934 } else {
5935 return Ok(());
5936 }
5937
5938 let mut ranges_to_highlight = Vec::new();
5939 let excerpt_buffer = cx.new(|cx| {
5940 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5941 for (buffer_handle, transaction) in &entries {
5942 let edited_ranges = buffer_handle
5943 .read(cx)
5944 .edited_ranges_for_transaction::<Point>(transaction)
5945 .collect::<Vec<_>>();
5946 let (ranges, _) = multibuffer.set_excerpts_for_path(
5947 PathKey::for_buffer(buffer_handle, cx),
5948 buffer_handle.clone(),
5949 edited_ranges,
5950 DEFAULT_MULTIBUFFER_CONTEXT,
5951 cx,
5952 );
5953
5954 ranges_to_highlight.extend(ranges);
5955 }
5956 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5957 multibuffer
5958 })?;
5959
5960 workspace.update_in(cx, |workspace, window, cx| {
5961 let project = workspace.project().clone();
5962 let editor =
5963 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5964 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5965 editor.update(cx, |editor, cx| {
5966 editor.highlight_background::<Self>(
5967 &ranges_to_highlight,
5968 |theme| theme.editor_highlighted_line_background,
5969 cx,
5970 );
5971 });
5972 })?;
5973
5974 Ok(())
5975 }
5976
5977 pub fn clear_code_action_providers(&mut self) {
5978 self.code_action_providers.clear();
5979 self.available_code_actions.take();
5980 }
5981
5982 pub fn add_code_action_provider(
5983 &mut self,
5984 provider: Rc<dyn CodeActionProvider>,
5985 window: &mut Window,
5986 cx: &mut Context<Self>,
5987 ) {
5988 if self
5989 .code_action_providers
5990 .iter()
5991 .any(|existing_provider| existing_provider.id() == provider.id())
5992 {
5993 return;
5994 }
5995
5996 self.code_action_providers.push(provider);
5997 self.refresh_code_actions(window, cx);
5998 }
5999
6000 pub fn remove_code_action_provider(
6001 &mut self,
6002 id: Arc<str>,
6003 window: &mut Window,
6004 cx: &mut Context<Self>,
6005 ) {
6006 self.code_action_providers
6007 .retain(|provider| provider.id() != id);
6008 self.refresh_code_actions(window, cx);
6009 }
6010
6011 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6012 !self.code_action_providers.is_empty()
6013 && EditorSettings::get_global(cx).toolbar.code_actions
6014 }
6015
6016 pub fn has_available_code_actions(&self) -> bool {
6017 self.available_code_actions
6018 .as_ref()
6019 .is_some_and(|(_, actions)| !actions.is_empty())
6020 }
6021
6022 fn render_inline_code_actions(
6023 &self,
6024 icon_size: ui::IconSize,
6025 display_row: DisplayRow,
6026 is_active: bool,
6027 cx: &mut Context<Self>,
6028 ) -> AnyElement {
6029 let show_tooltip = !self.context_menu_visible();
6030 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6031 .icon_size(icon_size)
6032 .shape(ui::IconButtonShape::Square)
6033 .style(ButtonStyle::Transparent)
6034 .icon_color(ui::Color::Hidden)
6035 .toggle_state(is_active)
6036 .when(show_tooltip, |this| {
6037 this.tooltip({
6038 let focus_handle = self.focus_handle.clone();
6039 move |window, cx| {
6040 Tooltip::for_action_in(
6041 "Toggle Code Actions",
6042 &ToggleCodeActions {
6043 deployed_from: None,
6044 quick_launch: false,
6045 },
6046 &focus_handle,
6047 window,
6048 cx,
6049 )
6050 }
6051 })
6052 })
6053 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6054 window.focus(&editor.focus_handle(cx));
6055 editor.toggle_code_actions(
6056 &crate::actions::ToggleCodeActions {
6057 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6058 display_row,
6059 )),
6060 quick_launch: false,
6061 },
6062 window,
6063 cx,
6064 );
6065 }))
6066 .into_any_element()
6067 }
6068
6069 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6070 &self.context_menu
6071 }
6072
6073 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6074 let newest_selection = self.selections.newest_anchor().clone();
6075 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6076 let buffer = self.buffer.read(cx);
6077 if newest_selection.head().diff_base_anchor.is_some() {
6078 return None;
6079 }
6080 let (start_buffer, start) =
6081 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6082 let (end_buffer, end) =
6083 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6084 if start_buffer != end_buffer {
6085 return None;
6086 }
6087
6088 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6089 cx.background_executor()
6090 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6091 .await;
6092
6093 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6094 let providers = this.code_action_providers.clone();
6095 let tasks = this
6096 .code_action_providers
6097 .iter()
6098 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6099 .collect::<Vec<_>>();
6100 (providers, tasks)
6101 })?;
6102
6103 let mut actions = Vec::new();
6104 for (provider, provider_actions) in
6105 providers.into_iter().zip(future::join_all(tasks).await)
6106 {
6107 if let Some(provider_actions) = provider_actions.log_err() {
6108 actions.extend(provider_actions.into_iter().map(|action| {
6109 AvailableCodeAction {
6110 excerpt_id: newest_selection.start.excerpt_id,
6111 action,
6112 provider: provider.clone(),
6113 }
6114 }));
6115 }
6116 }
6117
6118 this.update(cx, |this, cx| {
6119 this.available_code_actions = if actions.is_empty() {
6120 None
6121 } else {
6122 Some((
6123 Location {
6124 buffer: start_buffer,
6125 range: start..end,
6126 },
6127 actions.into(),
6128 ))
6129 };
6130 cx.notify();
6131 })
6132 }));
6133 None
6134 }
6135
6136 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6137 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6138 self.show_git_blame_inline = false;
6139
6140 self.show_git_blame_inline_delay_task =
6141 Some(cx.spawn_in(window, async move |this, cx| {
6142 cx.background_executor().timer(delay).await;
6143
6144 this.update(cx, |this, cx| {
6145 this.show_git_blame_inline = true;
6146 cx.notify();
6147 })
6148 .log_err();
6149 }));
6150 }
6151 }
6152
6153 fn show_blame_popover(
6154 &mut self,
6155 blame_entry: &BlameEntry,
6156 position: gpui::Point<Pixels>,
6157 cx: &mut Context<Self>,
6158 ) {
6159 if let Some(state) = &mut self.inline_blame_popover {
6160 state.hide_task.take();
6161 cx.notify();
6162 } else {
6163 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6164 let show_task = cx.spawn(async move |editor, cx| {
6165 cx.background_executor()
6166 .timer(std::time::Duration::from_millis(delay))
6167 .await;
6168 editor
6169 .update(cx, |editor, cx| {
6170 if let Some(state) = &mut editor.inline_blame_popover {
6171 state.show_task = None;
6172 cx.notify();
6173 }
6174 })
6175 .ok();
6176 });
6177 let Some(blame) = self.blame.as_ref() else {
6178 return;
6179 };
6180 let blame = blame.read(cx);
6181 let details = blame.details_for_entry(&blame_entry);
6182 let markdown = cx.new(|cx| {
6183 Markdown::new(
6184 details
6185 .as_ref()
6186 .map(|message| message.message.clone())
6187 .unwrap_or_default(),
6188 None,
6189 None,
6190 cx,
6191 )
6192 });
6193 self.inline_blame_popover = Some(InlineBlamePopover {
6194 position,
6195 show_task: Some(show_task),
6196 hide_task: None,
6197 popover_bounds: None,
6198 popover_state: InlineBlamePopoverState {
6199 scroll_handle: ScrollHandle::new(),
6200 commit_message: details,
6201 markdown,
6202 },
6203 });
6204 }
6205 }
6206
6207 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6208 if let Some(state) = &mut self.inline_blame_popover {
6209 if state.show_task.is_some() {
6210 self.inline_blame_popover.take();
6211 cx.notify();
6212 } else {
6213 let hide_task = cx.spawn(async move |editor, cx| {
6214 cx.background_executor()
6215 .timer(std::time::Duration::from_millis(100))
6216 .await;
6217 editor
6218 .update(cx, |editor, cx| {
6219 editor.inline_blame_popover.take();
6220 cx.notify();
6221 })
6222 .ok();
6223 });
6224 state.hide_task = Some(hide_task);
6225 }
6226 }
6227 }
6228
6229 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6230 if self.pending_rename.is_some() {
6231 return None;
6232 }
6233
6234 let provider = self.semantics_provider.clone()?;
6235 let buffer = self.buffer.read(cx);
6236 let newest_selection = self.selections.newest_anchor().clone();
6237 let cursor_position = newest_selection.head();
6238 let (cursor_buffer, cursor_buffer_position) =
6239 buffer.text_anchor_for_position(cursor_position, cx)?;
6240 let (tail_buffer, tail_buffer_position) =
6241 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6242 if cursor_buffer != tail_buffer {
6243 return None;
6244 }
6245
6246 let snapshot = cursor_buffer.read(cx).snapshot();
6247 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6248 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6249 if start_word_range != end_word_range {
6250 self.document_highlights_task.take();
6251 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6252 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6253 return None;
6254 }
6255
6256 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6257 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6258 cx.background_executor()
6259 .timer(Duration::from_millis(debounce))
6260 .await;
6261
6262 let highlights = if let Some(highlights) = cx
6263 .update(|cx| {
6264 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6265 })
6266 .ok()
6267 .flatten()
6268 {
6269 highlights.await.log_err()
6270 } else {
6271 None
6272 };
6273
6274 if let Some(highlights) = highlights {
6275 this.update(cx, |this, cx| {
6276 if this.pending_rename.is_some() {
6277 return;
6278 }
6279
6280 let buffer_id = cursor_position.buffer_id;
6281 let buffer = this.buffer.read(cx);
6282 if !buffer
6283 .text_anchor_for_position(cursor_position, cx)
6284 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6285 {
6286 return;
6287 }
6288
6289 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6290 let mut write_ranges = Vec::new();
6291 let mut read_ranges = Vec::new();
6292 for highlight in highlights {
6293 for (excerpt_id, excerpt_range) in
6294 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6295 {
6296 let start = highlight
6297 .range
6298 .start
6299 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6300 let end = highlight
6301 .range
6302 .end
6303 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6304 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6305 continue;
6306 }
6307
6308 let range = Anchor {
6309 buffer_id,
6310 excerpt_id,
6311 text_anchor: start,
6312 diff_base_anchor: None,
6313 }..Anchor {
6314 buffer_id,
6315 excerpt_id,
6316 text_anchor: end,
6317 diff_base_anchor: None,
6318 };
6319 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6320 write_ranges.push(range);
6321 } else {
6322 read_ranges.push(range);
6323 }
6324 }
6325 }
6326
6327 this.highlight_background::<DocumentHighlightRead>(
6328 &read_ranges,
6329 |theme| theme.editor_document_highlight_read_background,
6330 cx,
6331 );
6332 this.highlight_background::<DocumentHighlightWrite>(
6333 &write_ranges,
6334 |theme| theme.editor_document_highlight_write_background,
6335 cx,
6336 );
6337 cx.notify();
6338 })
6339 .log_err();
6340 }
6341 }));
6342 None
6343 }
6344
6345 fn prepare_highlight_query_from_selection(
6346 &mut self,
6347 cx: &mut Context<Editor>,
6348 ) -> Option<(String, Range<Anchor>)> {
6349 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6350 return None;
6351 }
6352 if !EditorSettings::get_global(cx).selection_highlight {
6353 return None;
6354 }
6355 if self.selections.count() != 1 || self.selections.line_mode {
6356 return None;
6357 }
6358 let selection = self.selections.newest::<Point>(cx);
6359 if selection.is_empty() || selection.start.row != selection.end.row {
6360 return None;
6361 }
6362 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6363 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6364 let query = multi_buffer_snapshot
6365 .text_for_range(selection_anchor_range.clone())
6366 .collect::<String>();
6367 if query.trim().is_empty() {
6368 return None;
6369 }
6370 Some((query, selection_anchor_range))
6371 }
6372
6373 fn update_selection_occurrence_highlights(
6374 &mut self,
6375 query_text: String,
6376 query_range: Range<Anchor>,
6377 multi_buffer_range_to_query: Range<Point>,
6378 use_debounce: bool,
6379 window: &mut Window,
6380 cx: &mut Context<Editor>,
6381 ) -> Task<()> {
6382 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6383 cx.spawn_in(window, async move |editor, cx| {
6384 if use_debounce {
6385 cx.background_executor()
6386 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6387 .await;
6388 }
6389 let match_task = cx.background_spawn(async move {
6390 let buffer_ranges = multi_buffer_snapshot
6391 .range_to_buffer_ranges(multi_buffer_range_to_query)
6392 .into_iter()
6393 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6394 let mut match_ranges = Vec::new();
6395 let Ok(regex) = project::search::SearchQuery::text(
6396 query_text.clone(),
6397 false,
6398 false,
6399 false,
6400 Default::default(),
6401 Default::default(),
6402 false,
6403 None,
6404 ) else {
6405 return Vec::default();
6406 };
6407 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6408 match_ranges.extend(
6409 regex
6410 .search(&buffer_snapshot, Some(search_range.clone()))
6411 .await
6412 .into_iter()
6413 .filter_map(|match_range| {
6414 let match_start = buffer_snapshot
6415 .anchor_after(search_range.start + match_range.start);
6416 let match_end = buffer_snapshot
6417 .anchor_before(search_range.start + match_range.end);
6418 let match_anchor_range = Anchor::range_in_buffer(
6419 excerpt_id,
6420 buffer_snapshot.remote_id(),
6421 match_start..match_end,
6422 );
6423 (match_anchor_range != query_range).then_some(match_anchor_range)
6424 }),
6425 );
6426 }
6427 match_ranges
6428 });
6429 let match_ranges = match_task.await;
6430 editor
6431 .update_in(cx, |editor, _, cx| {
6432 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6433 if !match_ranges.is_empty() {
6434 editor.highlight_background::<SelectedTextHighlight>(
6435 &match_ranges,
6436 |theme| theme.editor_document_highlight_bracket_background,
6437 cx,
6438 )
6439 }
6440 })
6441 .log_err();
6442 })
6443 }
6444
6445 fn refresh_selected_text_highlights(
6446 &mut self,
6447 on_buffer_edit: bool,
6448 window: &mut Window,
6449 cx: &mut Context<Editor>,
6450 ) {
6451 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6452 else {
6453 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6454 self.quick_selection_highlight_task.take();
6455 self.debounced_selection_highlight_task.take();
6456 return;
6457 };
6458 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6459 if on_buffer_edit
6460 || self
6461 .quick_selection_highlight_task
6462 .as_ref()
6463 .map_or(true, |(prev_anchor_range, _)| {
6464 prev_anchor_range != &query_range
6465 })
6466 {
6467 let multi_buffer_visible_start = self
6468 .scroll_manager
6469 .anchor()
6470 .anchor
6471 .to_point(&multi_buffer_snapshot);
6472 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6473 multi_buffer_visible_start
6474 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6475 Bias::Left,
6476 );
6477 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6478 self.quick_selection_highlight_task = Some((
6479 query_range.clone(),
6480 self.update_selection_occurrence_highlights(
6481 query_text.clone(),
6482 query_range.clone(),
6483 multi_buffer_visible_range,
6484 false,
6485 window,
6486 cx,
6487 ),
6488 ));
6489 }
6490 if on_buffer_edit
6491 || self
6492 .debounced_selection_highlight_task
6493 .as_ref()
6494 .map_or(true, |(prev_anchor_range, _)| {
6495 prev_anchor_range != &query_range
6496 })
6497 {
6498 let multi_buffer_start = multi_buffer_snapshot
6499 .anchor_before(0)
6500 .to_point(&multi_buffer_snapshot);
6501 let multi_buffer_end = multi_buffer_snapshot
6502 .anchor_after(multi_buffer_snapshot.len())
6503 .to_point(&multi_buffer_snapshot);
6504 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6505 self.debounced_selection_highlight_task = Some((
6506 query_range.clone(),
6507 self.update_selection_occurrence_highlights(
6508 query_text,
6509 query_range,
6510 multi_buffer_full_range,
6511 true,
6512 window,
6513 cx,
6514 ),
6515 ));
6516 }
6517 }
6518
6519 pub fn refresh_inline_completion(
6520 &mut self,
6521 debounce: bool,
6522 user_requested: bool,
6523 window: &mut Window,
6524 cx: &mut Context<Self>,
6525 ) -> Option<()> {
6526 let provider = self.edit_prediction_provider()?;
6527 let cursor = self.selections.newest_anchor().head();
6528 let (buffer, cursor_buffer_position) =
6529 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6530
6531 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6532 self.discard_inline_completion(false, cx);
6533 return None;
6534 }
6535
6536 if !user_requested
6537 && (!self.should_show_edit_predictions()
6538 || !self.is_focused(window)
6539 || buffer.read(cx).is_empty())
6540 {
6541 self.discard_inline_completion(false, cx);
6542 return None;
6543 }
6544
6545 self.update_visible_inline_completion(window, cx);
6546 provider.refresh(
6547 self.project.clone(),
6548 buffer,
6549 cursor_buffer_position,
6550 debounce,
6551 cx,
6552 );
6553 Some(())
6554 }
6555
6556 fn show_edit_predictions_in_menu(&self) -> bool {
6557 match self.edit_prediction_settings {
6558 EditPredictionSettings::Disabled => false,
6559 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6560 }
6561 }
6562
6563 pub fn edit_predictions_enabled(&self) -> bool {
6564 match self.edit_prediction_settings {
6565 EditPredictionSettings::Disabled => false,
6566 EditPredictionSettings::Enabled { .. } => true,
6567 }
6568 }
6569
6570 fn edit_prediction_requires_modifier(&self) -> bool {
6571 match self.edit_prediction_settings {
6572 EditPredictionSettings::Disabled => false,
6573 EditPredictionSettings::Enabled {
6574 preview_requires_modifier,
6575 ..
6576 } => preview_requires_modifier,
6577 }
6578 }
6579
6580 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6581 if self.edit_prediction_provider.is_none() {
6582 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6583 } else {
6584 let selection = self.selections.newest_anchor();
6585 let cursor = selection.head();
6586
6587 if let Some((buffer, cursor_buffer_position)) =
6588 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6589 {
6590 self.edit_prediction_settings =
6591 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6592 }
6593 }
6594 }
6595
6596 fn edit_prediction_settings_at_position(
6597 &self,
6598 buffer: &Entity<Buffer>,
6599 buffer_position: language::Anchor,
6600 cx: &App,
6601 ) -> EditPredictionSettings {
6602 if !self.mode.is_full()
6603 || !self.show_inline_completions_override.unwrap_or(true)
6604 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6605 {
6606 return EditPredictionSettings::Disabled;
6607 }
6608
6609 let buffer = buffer.read(cx);
6610
6611 let file = buffer.file();
6612
6613 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6614 return EditPredictionSettings::Disabled;
6615 };
6616
6617 let by_provider = matches!(
6618 self.menu_inline_completions_policy,
6619 MenuInlineCompletionsPolicy::ByProvider
6620 );
6621
6622 let show_in_menu = by_provider
6623 && self
6624 .edit_prediction_provider
6625 .as_ref()
6626 .map_or(false, |provider| {
6627 provider.provider.show_completions_in_menu()
6628 });
6629
6630 let preview_requires_modifier =
6631 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6632
6633 EditPredictionSettings::Enabled {
6634 show_in_menu,
6635 preview_requires_modifier,
6636 }
6637 }
6638
6639 fn should_show_edit_predictions(&self) -> bool {
6640 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6641 }
6642
6643 pub fn edit_prediction_preview_is_active(&self) -> bool {
6644 matches!(
6645 self.edit_prediction_preview,
6646 EditPredictionPreview::Active { .. }
6647 )
6648 }
6649
6650 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6651 let cursor = self.selections.newest_anchor().head();
6652 if let Some((buffer, cursor_position)) =
6653 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6654 {
6655 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6656 } else {
6657 false
6658 }
6659 }
6660
6661 pub fn supports_minimap(&self, cx: &App) -> bool {
6662 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6663 }
6664
6665 fn edit_predictions_enabled_in_buffer(
6666 &self,
6667 buffer: &Entity<Buffer>,
6668 buffer_position: language::Anchor,
6669 cx: &App,
6670 ) -> bool {
6671 maybe!({
6672 if self.read_only(cx) {
6673 return Some(false);
6674 }
6675 let provider = self.edit_prediction_provider()?;
6676 if !provider.is_enabled(&buffer, buffer_position, cx) {
6677 return Some(false);
6678 }
6679 let buffer = buffer.read(cx);
6680 let Some(file) = buffer.file() else {
6681 return Some(true);
6682 };
6683 let settings = all_language_settings(Some(file), cx);
6684 Some(settings.edit_predictions_enabled_for_file(file, cx))
6685 })
6686 .unwrap_or(false)
6687 }
6688
6689 fn cycle_inline_completion(
6690 &mut self,
6691 direction: Direction,
6692 window: &mut Window,
6693 cx: &mut Context<Self>,
6694 ) -> Option<()> {
6695 let provider = self.edit_prediction_provider()?;
6696 let cursor = self.selections.newest_anchor().head();
6697 let (buffer, cursor_buffer_position) =
6698 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6699 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6700 return None;
6701 }
6702
6703 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6704 self.update_visible_inline_completion(window, cx);
6705
6706 Some(())
6707 }
6708
6709 pub fn show_inline_completion(
6710 &mut self,
6711 _: &ShowEditPrediction,
6712 window: &mut Window,
6713 cx: &mut Context<Self>,
6714 ) {
6715 if !self.has_active_inline_completion() {
6716 self.refresh_inline_completion(false, true, window, cx);
6717 return;
6718 }
6719
6720 self.update_visible_inline_completion(window, cx);
6721 }
6722
6723 pub fn display_cursor_names(
6724 &mut self,
6725 _: &DisplayCursorNames,
6726 window: &mut Window,
6727 cx: &mut Context<Self>,
6728 ) {
6729 self.show_cursor_names(window, cx);
6730 }
6731
6732 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6733 self.show_cursor_names = true;
6734 cx.notify();
6735 cx.spawn_in(window, async move |this, cx| {
6736 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6737 this.update(cx, |this, cx| {
6738 this.show_cursor_names = false;
6739 cx.notify()
6740 })
6741 .ok()
6742 })
6743 .detach();
6744 }
6745
6746 pub fn next_edit_prediction(
6747 &mut self,
6748 _: &NextEditPrediction,
6749 window: &mut Window,
6750 cx: &mut Context<Self>,
6751 ) {
6752 if self.has_active_inline_completion() {
6753 self.cycle_inline_completion(Direction::Next, window, cx);
6754 } else {
6755 let is_copilot_disabled = self
6756 .refresh_inline_completion(false, true, window, cx)
6757 .is_none();
6758 if is_copilot_disabled {
6759 cx.propagate();
6760 }
6761 }
6762 }
6763
6764 pub fn previous_edit_prediction(
6765 &mut self,
6766 _: &PreviousEditPrediction,
6767 window: &mut Window,
6768 cx: &mut Context<Self>,
6769 ) {
6770 if self.has_active_inline_completion() {
6771 self.cycle_inline_completion(Direction::Prev, window, cx);
6772 } else {
6773 let is_copilot_disabled = self
6774 .refresh_inline_completion(false, true, window, cx)
6775 .is_none();
6776 if is_copilot_disabled {
6777 cx.propagate();
6778 }
6779 }
6780 }
6781
6782 pub fn accept_edit_prediction(
6783 &mut self,
6784 _: &AcceptEditPrediction,
6785 window: &mut Window,
6786 cx: &mut Context<Self>,
6787 ) {
6788 if self.show_edit_predictions_in_menu() {
6789 self.hide_context_menu(window, cx);
6790 }
6791
6792 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6793 return;
6794 };
6795
6796 self.report_inline_completion_event(
6797 active_inline_completion.completion_id.clone(),
6798 true,
6799 cx,
6800 );
6801
6802 match &active_inline_completion.completion {
6803 InlineCompletion::Move { target, .. } => {
6804 let target = *target;
6805
6806 if let Some(position_map) = &self.last_position_map {
6807 if position_map
6808 .visible_row_range
6809 .contains(&target.to_display_point(&position_map.snapshot).row())
6810 || !self.edit_prediction_requires_modifier()
6811 {
6812 self.unfold_ranges(&[target..target], true, false, cx);
6813 // Note that this is also done in vim's handler of the Tab action.
6814 self.change_selections(
6815 Some(Autoscroll::newest()),
6816 window,
6817 cx,
6818 |selections| {
6819 selections.select_anchor_ranges([target..target]);
6820 },
6821 );
6822 self.clear_row_highlights::<EditPredictionPreview>();
6823
6824 self.edit_prediction_preview
6825 .set_previous_scroll_position(None);
6826 } else {
6827 self.edit_prediction_preview
6828 .set_previous_scroll_position(Some(
6829 position_map.snapshot.scroll_anchor,
6830 ));
6831
6832 self.highlight_rows::<EditPredictionPreview>(
6833 target..target,
6834 cx.theme().colors().editor_highlighted_line_background,
6835 RowHighlightOptions {
6836 autoscroll: true,
6837 ..Default::default()
6838 },
6839 cx,
6840 );
6841 self.request_autoscroll(Autoscroll::fit(), cx);
6842 }
6843 }
6844 }
6845 InlineCompletion::Edit { edits, .. } => {
6846 if let Some(provider) = self.edit_prediction_provider() {
6847 provider.accept(cx);
6848 }
6849
6850 // Store the transaction ID and selections before applying the edit
6851 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6852
6853 let snapshot = self.buffer.read(cx).snapshot(cx);
6854 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6855
6856 self.buffer.update(cx, |buffer, cx| {
6857 buffer.edit(edits.iter().cloned(), None, cx)
6858 });
6859
6860 self.change_selections(None, window, cx, |s| {
6861 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6862 });
6863
6864 let selections = self.selections.disjoint_anchors();
6865 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6866 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6867 if has_new_transaction {
6868 self.selection_history
6869 .insert_transaction(transaction_id_now, selections);
6870 }
6871 }
6872
6873 self.update_visible_inline_completion(window, cx);
6874 if self.active_inline_completion.is_none() {
6875 self.refresh_inline_completion(true, true, window, cx);
6876 }
6877
6878 cx.notify();
6879 }
6880 }
6881
6882 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6883 }
6884
6885 pub fn accept_partial_inline_completion(
6886 &mut self,
6887 _: &AcceptPartialEditPrediction,
6888 window: &mut Window,
6889 cx: &mut Context<Self>,
6890 ) {
6891 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6892 return;
6893 };
6894 if self.selections.count() != 1 {
6895 return;
6896 }
6897
6898 self.report_inline_completion_event(
6899 active_inline_completion.completion_id.clone(),
6900 true,
6901 cx,
6902 );
6903
6904 match &active_inline_completion.completion {
6905 InlineCompletion::Move { target, .. } => {
6906 let target = *target;
6907 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6908 selections.select_anchor_ranges([target..target]);
6909 });
6910 }
6911 InlineCompletion::Edit { edits, .. } => {
6912 // Find an insertion that starts at the cursor position.
6913 let snapshot = self.buffer.read(cx).snapshot(cx);
6914 let cursor_offset = self.selections.newest::<usize>(cx).head();
6915 let insertion = edits.iter().find_map(|(range, text)| {
6916 let range = range.to_offset(&snapshot);
6917 if range.is_empty() && range.start == cursor_offset {
6918 Some(text)
6919 } else {
6920 None
6921 }
6922 });
6923
6924 if let Some(text) = insertion {
6925 let mut partial_completion = text
6926 .chars()
6927 .by_ref()
6928 .take_while(|c| c.is_alphabetic())
6929 .collect::<String>();
6930 if partial_completion.is_empty() {
6931 partial_completion = text
6932 .chars()
6933 .by_ref()
6934 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6935 .collect::<String>();
6936 }
6937
6938 cx.emit(EditorEvent::InputHandled {
6939 utf16_range_to_replace: None,
6940 text: partial_completion.clone().into(),
6941 });
6942
6943 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6944
6945 self.refresh_inline_completion(true, true, window, cx);
6946 cx.notify();
6947 } else {
6948 self.accept_edit_prediction(&Default::default(), window, cx);
6949 }
6950 }
6951 }
6952 }
6953
6954 fn discard_inline_completion(
6955 &mut self,
6956 should_report_inline_completion_event: bool,
6957 cx: &mut Context<Self>,
6958 ) -> bool {
6959 if should_report_inline_completion_event {
6960 let completion_id = self
6961 .active_inline_completion
6962 .as_ref()
6963 .and_then(|active_completion| active_completion.completion_id.clone());
6964
6965 self.report_inline_completion_event(completion_id, false, cx);
6966 }
6967
6968 if let Some(provider) = self.edit_prediction_provider() {
6969 provider.discard(cx);
6970 }
6971
6972 self.take_active_inline_completion(cx)
6973 }
6974
6975 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6976 let Some(provider) = self.edit_prediction_provider() else {
6977 return;
6978 };
6979
6980 let Some((_, buffer, _)) = self
6981 .buffer
6982 .read(cx)
6983 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6984 else {
6985 return;
6986 };
6987
6988 let extension = buffer
6989 .read(cx)
6990 .file()
6991 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6992
6993 let event_type = match accepted {
6994 true => "Edit Prediction Accepted",
6995 false => "Edit Prediction Discarded",
6996 };
6997 telemetry::event!(
6998 event_type,
6999 provider = provider.name(),
7000 prediction_id = id,
7001 suggestion_accepted = accepted,
7002 file_extension = extension,
7003 );
7004 }
7005
7006 pub fn has_active_inline_completion(&self) -> bool {
7007 self.active_inline_completion.is_some()
7008 }
7009
7010 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7011 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7012 return false;
7013 };
7014
7015 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7016 self.clear_highlights::<InlineCompletionHighlight>(cx);
7017 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7018 true
7019 }
7020
7021 /// Returns true when we're displaying the edit prediction popover below the cursor
7022 /// like we are not previewing and the LSP autocomplete menu is visible
7023 /// or we are in `when_holding_modifier` mode.
7024 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7025 if self.edit_prediction_preview_is_active()
7026 || !self.show_edit_predictions_in_menu()
7027 || !self.edit_predictions_enabled()
7028 {
7029 return false;
7030 }
7031
7032 if self.has_visible_completions_menu() {
7033 return true;
7034 }
7035
7036 has_completion && self.edit_prediction_requires_modifier()
7037 }
7038
7039 fn handle_modifiers_changed(
7040 &mut self,
7041 modifiers: Modifiers,
7042 position_map: &PositionMap,
7043 window: &mut Window,
7044 cx: &mut Context<Self>,
7045 ) {
7046 if self.show_edit_predictions_in_menu() {
7047 self.update_edit_prediction_preview(&modifiers, window, cx);
7048 }
7049
7050 self.update_selection_mode(&modifiers, position_map, window, cx);
7051
7052 let mouse_position = window.mouse_position();
7053 if !position_map.text_hitbox.is_hovered(window) {
7054 return;
7055 }
7056
7057 self.update_hovered_link(
7058 position_map.point_for_position(mouse_position),
7059 &position_map.snapshot,
7060 modifiers,
7061 window,
7062 cx,
7063 )
7064 }
7065
7066 fn update_selection_mode(
7067 &mut self,
7068 modifiers: &Modifiers,
7069 position_map: &PositionMap,
7070 window: &mut Window,
7071 cx: &mut Context<Self>,
7072 ) {
7073 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7074 return;
7075 }
7076
7077 let mouse_position = window.mouse_position();
7078 let point_for_position = position_map.point_for_position(mouse_position);
7079 let position = point_for_position.previous_valid;
7080
7081 self.select(
7082 SelectPhase::BeginColumnar {
7083 position,
7084 reset: false,
7085 goal_column: point_for_position.exact_unclipped.column(),
7086 },
7087 window,
7088 cx,
7089 );
7090 }
7091
7092 fn update_edit_prediction_preview(
7093 &mut self,
7094 modifiers: &Modifiers,
7095 window: &mut Window,
7096 cx: &mut Context<Self>,
7097 ) {
7098 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7099 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7100 return;
7101 };
7102
7103 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7104 if matches!(
7105 self.edit_prediction_preview,
7106 EditPredictionPreview::Inactive { .. }
7107 ) {
7108 self.edit_prediction_preview = EditPredictionPreview::Active {
7109 previous_scroll_position: None,
7110 since: Instant::now(),
7111 };
7112
7113 self.update_visible_inline_completion(window, cx);
7114 cx.notify();
7115 }
7116 } else if let EditPredictionPreview::Active {
7117 previous_scroll_position,
7118 since,
7119 } = self.edit_prediction_preview
7120 {
7121 if let (Some(previous_scroll_position), Some(position_map)) =
7122 (previous_scroll_position, self.last_position_map.as_ref())
7123 {
7124 self.set_scroll_position(
7125 previous_scroll_position
7126 .scroll_position(&position_map.snapshot.display_snapshot),
7127 window,
7128 cx,
7129 );
7130 }
7131
7132 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7133 released_too_fast: since.elapsed() < Duration::from_millis(200),
7134 };
7135 self.clear_row_highlights::<EditPredictionPreview>();
7136 self.update_visible_inline_completion(window, cx);
7137 cx.notify();
7138 }
7139 }
7140
7141 fn update_visible_inline_completion(
7142 &mut self,
7143 _window: &mut Window,
7144 cx: &mut Context<Self>,
7145 ) -> Option<()> {
7146 let selection = self.selections.newest_anchor();
7147 let cursor = selection.head();
7148 let multibuffer = self.buffer.read(cx).snapshot(cx);
7149 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7150 let excerpt_id = cursor.excerpt_id;
7151
7152 let show_in_menu = self.show_edit_predictions_in_menu();
7153 let completions_menu_has_precedence = !show_in_menu
7154 && (self.context_menu.borrow().is_some()
7155 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7156
7157 if completions_menu_has_precedence
7158 || !offset_selection.is_empty()
7159 || self
7160 .active_inline_completion
7161 .as_ref()
7162 .map_or(false, |completion| {
7163 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7164 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7165 !invalidation_range.contains(&offset_selection.head())
7166 })
7167 {
7168 self.discard_inline_completion(false, cx);
7169 return None;
7170 }
7171
7172 self.take_active_inline_completion(cx);
7173 let Some(provider) = self.edit_prediction_provider() else {
7174 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7175 return None;
7176 };
7177
7178 let (buffer, cursor_buffer_position) =
7179 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7180
7181 self.edit_prediction_settings =
7182 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7183
7184 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7185
7186 if self.edit_prediction_indent_conflict {
7187 let cursor_point = cursor.to_point(&multibuffer);
7188
7189 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7190
7191 if let Some((_, indent)) = indents.iter().next() {
7192 if indent.len == cursor_point.column {
7193 self.edit_prediction_indent_conflict = false;
7194 }
7195 }
7196 }
7197
7198 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7199 let edits = inline_completion
7200 .edits
7201 .into_iter()
7202 .flat_map(|(range, new_text)| {
7203 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7204 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7205 Some((start..end, new_text))
7206 })
7207 .collect::<Vec<_>>();
7208 if edits.is_empty() {
7209 return None;
7210 }
7211
7212 let first_edit_start = edits.first().unwrap().0.start;
7213 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7214 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7215
7216 let last_edit_end = edits.last().unwrap().0.end;
7217 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7218 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7219
7220 let cursor_row = cursor.to_point(&multibuffer).row;
7221
7222 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7223
7224 let mut inlay_ids = Vec::new();
7225 let invalidation_row_range;
7226 let move_invalidation_row_range = if cursor_row < edit_start_row {
7227 Some(cursor_row..edit_end_row)
7228 } else if cursor_row > edit_end_row {
7229 Some(edit_start_row..cursor_row)
7230 } else {
7231 None
7232 };
7233 let is_move =
7234 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7235 let completion = if is_move {
7236 invalidation_row_range =
7237 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7238 let target = first_edit_start;
7239 InlineCompletion::Move { target, snapshot }
7240 } else {
7241 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7242 && !self.inline_completions_hidden_for_vim_mode;
7243
7244 if show_completions_in_buffer {
7245 if edits
7246 .iter()
7247 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7248 {
7249 let mut inlays = Vec::new();
7250 for (range, new_text) in &edits {
7251 let inlay = Inlay::inline_completion(
7252 post_inc(&mut self.next_inlay_id),
7253 range.start,
7254 new_text.as_str(),
7255 );
7256 inlay_ids.push(inlay.id);
7257 inlays.push(inlay);
7258 }
7259
7260 self.splice_inlays(&[], inlays, cx);
7261 } else {
7262 let background_color = cx.theme().status().deleted_background;
7263 self.highlight_text::<InlineCompletionHighlight>(
7264 edits.iter().map(|(range, _)| range.clone()).collect(),
7265 HighlightStyle {
7266 background_color: Some(background_color),
7267 ..Default::default()
7268 },
7269 cx,
7270 );
7271 }
7272 }
7273
7274 invalidation_row_range = edit_start_row..edit_end_row;
7275
7276 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7277 if provider.show_tab_accept_marker() {
7278 EditDisplayMode::TabAccept
7279 } else {
7280 EditDisplayMode::Inline
7281 }
7282 } else {
7283 EditDisplayMode::DiffPopover
7284 };
7285
7286 InlineCompletion::Edit {
7287 edits,
7288 edit_preview: inline_completion.edit_preview,
7289 display_mode,
7290 snapshot,
7291 }
7292 };
7293
7294 let invalidation_range = multibuffer
7295 .anchor_before(Point::new(invalidation_row_range.start, 0))
7296 ..multibuffer.anchor_after(Point::new(
7297 invalidation_row_range.end,
7298 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7299 ));
7300
7301 self.stale_inline_completion_in_menu = None;
7302 self.active_inline_completion = Some(InlineCompletionState {
7303 inlay_ids,
7304 completion,
7305 completion_id: inline_completion.id,
7306 invalidation_range,
7307 });
7308
7309 cx.notify();
7310
7311 Some(())
7312 }
7313
7314 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7315 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7316 }
7317
7318 fn clear_tasks(&mut self) {
7319 self.tasks.clear()
7320 }
7321
7322 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7323 if self.tasks.insert(key, value).is_some() {
7324 // This case should hopefully be rare, but just in case...
7325 log::error!(
7326 "multiple different run targets found on a single line, only the last target will be rendered"
7327 )
7328 }
7329 }
7330
7331 /// Get all display points of breakpoints that will be rendered within editor
7332 ///
7333 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7334 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7335 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7336 fn active_breakpoints(
7337 &self,
7338 range: Range<DisplayRow>,
7339 window: &mut Window,
7340 cx: &mut Context<Self>,
7341 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7342 let mut breakpoint_display_points = HashMap::default();
7343
7344 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7345 return breakpoint_display_points;
7346 };
7347
7348 let snapshot = self.snapshot(window, cx);
7349
7350 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7351 let Some(project) = self.project.as_ref() else {
7352 return breakpoint_display_points;
7353 };
7354
7355 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7356 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7357
7358 for (buffer_snapshot, range, excerpt_id) in
7359 multi_buffer_snapshot.range_to_buffer_ranges(range)
7360 {
7361 let Some(buffer) = project
7362 .read(cx)
7363 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7364 else {
7365 continue;
7366 };
7367 let breakpoints = breakpoint_store.read(cx).breakpoints(
7368 &buffer,
7369 Some(
7370 buffer_snapshot.anchor_before(range.start)
7371 ..buffer_snapshot.anchor_after(range.end),
7372 ),
7373 buffer_snapshot,
7374 cx,
7375 );
7376 for (breakpoint, state) in breakpoints {
7377 let multi_buffer_anchor =
7378 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7379 let position = multi_buffer_anchor
7380 .to_point(&multi_buffer_snapshot)
7381 .to_display_point(&snapshot);
7382
7383 breakpoint_display_points.insert(
7384 position.row(),
7385 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7386 );
7387 }
7388 }
7389
7390 breakpoint_display_points
7391 }
7392
7393 fn breakpoint_context_menu(
7394 &self,
7395 anchor: Anchor,
7396 window: &mut Window,
7397 cx: &mut Context<Self>,
7398 ) -> Entity<ui::ContextMenu> {
7399 let weak_editor = cx.weak_entity();
7400 let focus_handle = self.focus_handle(cx);
7401
7402 let row = self
7403 .buffer
7404 .read(cx)
7405 .snapshot(cx)
7406 .summary_for_anchor::<Point>(&anchor)
7407 .row;
7408
7409 let breakpoint = self
7410 .breakpoint_at_row(row, window, cx)
7411 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7412
7413 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7414 "Edit Log Breakpoint"
7415 } else {
7416 "Set Log Breakpoint"
7417 };
7418
7419 let condition_breakpoint_msg = if breakpoint
7420 .as_ref()
7421 .is_some_and(|bp| bp.1.condition.is_some())
7422 {
7423 "Edit Condition Breakpoint"
7424 } else {
7425 "Set Condition Breakpoint"
7426 };
7427
7428 let hit_condition_breakpoint_msg = if breakpoint
7429 .as_ref()
7430 .is_some_and(|bp| bp.1.hit_condition.is_some())
7431 {
7432 "Edit Hit Condition Breakpoint"
7433 } else {
7434 "Set Hit Condition Breakpoint"
7435 };
7436
7437 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7438 "Unset Breakpoint"
7439 } else {
7440 "Set Breakpoint"
7441 };
7442
7443 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7444 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7445
7446 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7447 BreakpointState::Enabled => Some("Disable"),
7448 BreakpointState::Disabled => Some("Enable"),
7449 });
7450
7451 let (anchor, breakpoint) =
7452 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7453
7454 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7455 menu.on_blur_subscription(Subscription::new(|| {}))
7456 .context(focus_handle)
7457 .when(run_to_cursor, |this| {
7458 let weak_editor = weak_editor.clone();
7459 this.entry("Run to cursor", None, move |window, cx| {
7460 weak_editor
7461 .update(cx, |editor, cx| {
7462 editor.change_selections(None, window, cx, |s| {
7463 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7464 });
7465 })
7466 .ok();
7467
7468 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7469 })
7470 .separator()
7471 })
7472 .when_some(toggle_state_msg, |this, msg| {
7473 this.entry(msg, None, {
7474 let weak_editor = weak_editor.clone();
7475 let breakpoint = breakpoint.clone();
7476 move |_window, cx| {
7477 weak_editor
7478 .update(cx, |this, cx| {
7479 this.edit_breakpoint_at_anchor(
7480 anchor,
7481 breakpoint.as_ref().clone(),
7482 BreakpointEditAction::InvertState,
7483 cx,
7484 );
7485 })
7486 .log_err();
7487 }
7488 })
7489 })
7490 .entry(set_breakpoint_msg, None, {
7491 let weak_editor = weak_editor.clone();
7492 let breakpoint = breakpoint.clone();
7493 move |_window, cx| {
7494 weak_editor
7495 .update(cx, |this, cx| {
7496 this.edit_breakpoint_at_anchor(
7497 anchor,
7498 breakpoint.as_ref().clone(),
7499 BreakpointEditAction::Toggle,
7500 cx,
7501 );
7502 })
7503 .log_err();
7504 }
7505 })
7506 .entry(log_breakpoint_msg, None, {
7507 let breakpoint = breakpoint.clone();
7508 let weak_editor = weak_editor.clone();
7509 move |window, cx| {
7510 weak_editor
7511 .update(cx, |this, cx| {
7512 this.add_edit_breakpoint_block(
7513 anchor,
7514 breakpoint.as_ref(),
7515 BreakpointPromptEditAction::Log,
7516 window,
7517 cx,
7518 );
7519 })
7520 .log_err();
7521 }
7522 })
7523 .entry(condition_breakpoint_msg, None, {
7524 let breakpoint = breakpoint.clone();
7525 let weak_editor = weak_editor.clone();
7526 move |window, cx| {
7527 weak_editor
7528 .update(cx, |this, cx| {
7529 this.add_edit_breakpoint_block(
7530 anchor,
7531 breakpoint.as_ref(),
7532 BreakpointPromptEditAction::Condition,
7533 window,
7534 cx,
7535 );
7536 })
7537 .log_err();
7538 }
7539 })
7540 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7541 weak_editor
7542 .update(cx, |this, cx| {
7543 this.add_edit_breakpoint_block(
7544 anchor,
7545 breakpoint.as_ref(),
7546 BreakpointPromptEditAction::HitCondition,
7547 window,
7548 cx,
7549 );
7550 })
7551 .log_err();
7552 })
7553 })
7554 }
7555
7556 fn render_breakpoint(
7557 &self,
7558 position: Anchor,
7559 row: DisplayRow,
7560 breakpoint: &Breakpoint,
7561 state: Option<BreakpointSessionState>,
7562 cx: &mut Context<Self>,
7563 ) -> IconButton {
7564 let is_rejected = state.is_some_and(|s| !s.verified);
7565 // Is it a breakpoint that shows up when hovering over gutter?
7566 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7567 (false, false),
7568 |PhantomBreakpointIndicator {
7569 is_active,
7570 display_row,
7571 collides_with_existing_breakpoint,
7572 }| {
7573 (
7574 is_active && display_row == row,
7575 collides_with_existing_breakpoint,
7576 )
7577 },
7578 );
7579
7580 let (color, icon) = {
7581 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7582 (false, false) => ui::IconName::DebugBreakpoint,
7583 (true, false) => ui::IconName::DebugLogBreakpoint,
7584 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7585 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7586 };
7587
7588 let color = if is_phantom {
7589 Color::Hint
7590 } else if is_rejected {
7591 Color::Disabled
7592 } else {
7593 Color::Debugger
7594 };
7595
7596 (color, icon)
7597 };
7598
7599 let breakpoint = Arc::from(breakpoint.clone());
7600
7601 let alt_as_text = gpui::Keystroke {
7602 modifiers: Modifiers::secondary_key(),
7603 ..Default::default()
7604 };
7605 let primary_action_text = if breakpoint.is_disabled() {
7606 "Enable breakpoint"
7607 } else if is_phantom && !collides_with_existing {
7608 "Set breakpoint"
7609 } else {
7610 "Unset breakpoint"
7611 };
7612 let focus_handle = self.focus_handle.clone();
7613
7614 let meta = if is_rejected {
7615 SharedString::from("No executable code is associated with this line.")
7616 } else if collides_with_existing && !breakpoint.is_disabled() {
7617 SharedString::from(format!(
7618 "{alt_as_text}-click to disable,\nright-click for more options."
7619 ))
7620 } else {
7621 SharedString::from("Right-click for more options.")
7622 };
7623 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7624 .icon_size(IconSize::XSmall)
7625 .size(ui::ButtonSize::None)
7626 .when(is_rejected, |this| {
7627 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7628 })
7629 .icon_color(color)
7630 .style(ButtonStyle::Transparent)
7631 .on_click(cx.listener({
7632 let breakpoint = breakpoint.clone();
7633
7634 move |editor, event: &ClickEvent, window, cx| {
7635 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7636 BreakpointEditAction::InvertState
7637 } else {
7638 BreakpointEditAction::Toggle
7639 };
7640
7641 window.focus(&editor.focus_handle(cx));
7642 editor.edit_breakpoint_at_anchor(
7643 position,
7644 breakpoint.as_ref().clone(),
7645 edit_action,
7646 cx,
7647 );
7648 }
7649 }))
7650 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7651 editor.set_breakpoint_context_menu(
7652 row,
7653 Some(position),
7654 event.down.position,
7655 window,
7656 cx,
7657 );
7658 }))
7659 .tooltip(move |window, cx| {
7660 Tooltip::with_meta_in(
7661 primary_action_text,
7662 Some(&ToggleBreakpoint),
7663 meta.clone(),
7664 &focus_handle,
7665 window,
7666 cx,
7667 )
7668 })
7669 }
7670
7671 fn build_tasks_context(
7672 project: &Entity<Project>,
7673 buffer: &Entity<Buffer>,
7674 buffer_row: u32,
7675 tasks: &Arc<RunnableTasks>,
7676 cx: &mut Context<Self>,
7677 ) -> Task<Option<task::TaskContext>> {
7678 let position = Point::new(buffer_row, tasks.column);
7679 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7680 let location = Location {
7681 buffer: buffer.clone(),
7682 range: range_start..range_start,
7683 };
7684 // Fill in the environmental variables from the tree-sitter captures
7685 let mut captured_task_variables = TaskVariables::default();
7686 for (capture_name, value) in tasks.extra_variables.clone() {
7687 captured_task_variables.insert(
7688 task::VariableName::Custom(capture_name.into()),
7689 value.clone(),
7690 );
7691 }
7692 project.update(cx, |project, cx| {
7693 project.task_store().update(cx, |task_store, cx| {
7694 task_store.task_context_for_location(captured_task_variables, location, cx)
7695 })
7696 })
7697 }
7698
7699 pub fn spawn_nearest_task(
7700 &mut self,
7701 action: &SpawnNearestTask,
7702 window: &mut Window,
7703 cx: &mut Context<Self>,
7704 ) {
7705 let Some((workspace, _)) = self.workspace.clone() else {
7706 return;
7707 };
7708 let Some(project) = self.project.clone() else {
7709 return;
7710 };
7711
7712 // Try to find a closest, enclosing node using tree-sitter that has a
7713 // task
7714 let Some((buffer, buffer_row, tasks)) = self
7715 .find_enclosing_node_task(cx)
7716 // Or find the task that's closest in row-distance.
7717 .or_else(|| self.find_closest_task(cx))
7718 else {
7719 return;
7720 };
7721
7722 let reveal_strategy = action.reveal;
7723 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7724 cx.spawn_in(window, async move |_, cx| {
7725 let context = task_context.await?;
7726 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7727
7728 let resolved = &mut resolved_task.resolved;
7729 resolved.reveal = reveal_strategy;
7730
7731 workspace
7732 .update_in(cx, |workspace, window, cx| {
7733 workspace.schedule_resolved_task(
7734 task_source_kind,
7735 resolved_task,
7736 false,
7737 window,
7738 cx,
7739 );
7740 })
7741 .ok()
7742 })
7743 .detach();
7744 }
7745
7746 fn find_closest_task(
7747 &mut self,
7748 cx: &mut Context<Self>,
7749 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7750 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7751
7752 let ((buffer_id, row), tasks) = self
7753 .tasks
7754 .iter()
7755 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7756
7757 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7758 let tasks = Arc::new(tasks.to_owned());
7759 Some((buffer, *row, tasks))
7760 }
7761
7762 fn find_enclosing_node_task(
7763 &mut self,
7764 cx: &mut Context<Self>,
7765 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7766 let snapshot = self.buffer.read(cx).snapshot(cx);
7767 let offset = self.selections.newest::<usize>(cx).head();
7768 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7769 let buffer_id = excerpt.buffer().remote_id();
7770
7771 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7772 let mut cursor = layer.node().walk();
7773
7774 while cursor.goto_first_child_for_byte(offset).is_some() {
7775 if cursor.node().end_byte() == offset {
7776 cursor.goto_next_sibling();
7777 }
7778 }
7779
7780 // Ascend to the smallest ancestor that contains the range and has a task.
7781 loop {
7782 let node = cursor.node();
7783 let node_range = node.byte_range();
7784 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7785
7786 // Check if this node contains our offset
7787 if node_range.start <= offset && node_range.end >= offset {
7788 // If it contains offset, check for task
7789 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7790 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7791 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7792 }
7793 }
7794
7795 if !cursor.goto_parent() {
7796 break;
7797 }
7798 }
7799 None
7800 }
7801
7802 fn render_run_indicator(
7803 &self,
7804 _style: &EditorStyle,
7805 is_active: bool,
7806 row: DisplayRow,
7807 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7808 cx: &mut Context<Self>,
7809 ) -> IconButton {
7810 let color = Color::Muted;
7811 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7812
7813 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7814 .shape(ui::IconButtonShape::Square)
7815 .icon_size(IconSize::XSmall)
7816 .icon_color(color)
7817 .toggle_state(is_active)
7818 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7819 let quick_launch = e.down.button == MouseButton::Left;
7820 window.focus(&editor.focus_handle(cx));
7821 editor.toggle_code_actions(
7822 &ToggleCodeActions {
7823 deployed_from: Some(CodeActionSource::Indicator(row)),
7824 quick_launch,
7825 },
7826 window,
7827 cx,
7828 );
7829 }))
7830 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7831 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7832 }))
7833 }
7834
7835 pub fn context_menu_visible(&self) -> bool {
7836 !self.edit_prediction_preview_is_active()
7837 && self
7838 .context_menu
7839 .borrow()
7840 .as_ref()
7841 .map_or(false, |menu| menu.visible())
7842 }
7843
7844 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7845 self.context_menu
7846 .borrow()
7847 .as_ref()
7848 .map(|menu| menu.origin())
7849 }
7850
7851 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7852 self.context_menu_options = Some(options);
7853 }
7854
7855 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7856 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7857
7858 fn render_edit_prediction_popover(
7859 &mut self,
7860 text_bounds: &Bounds<Pixels>,
7861 content_origin: gpui::Point<Pixels>,
7862 right_margin: Pixels,
7863 editor_snapshot: &EditorSnapshot,
7864 visible_row_range: Range<DisplayRow>,
7865 scroll_top: f32,
7866 scroll_bottom: f32,
7867 line_layouts: &[LineWithInvisibles],
7868 line_height: Pixels,
7869 scroll_pixel_position: gpui::Point<Pixels>,
7870 newest_selection_head: Option<DisplayPoint>,
7871 editor_width: Pixels,
7872 style: &EditorStyle,
7873 window: &mut Window,
7874 cx: &mut App,
7875 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7876 if self.mode().is_minimap() {
7877 return None;
7878 }
7879 let active_inline_completion = self.active_inline_completion.as_ref()?;
7880
7881 if self.edit_prediction_visible_in_cursor_popover(true) {
7882 return None;
7883 }
7884
7885 match &active_inline_completion.completion {
7886 InlineCompletion::Move { target, .. } => {
7887 let target_display_point = target.to_display_point(editor_snapshot);
7888
7889 if self.edit_prediction_requires_modifier() {
7890 if !self.edit_prediction_preview_is_active() {
7891 return None;
7892 }
7893
7894 self.render_edit_prediction_modifier_jump_popover(
7895 text_bounds,
7896 content_origin,
7897 visible_row_range,
7898 line_layouts,
7899 line_height,
7900 scroll_pixel_position,
7901 newest_selection_head,
7902 target_display_point,
7903 window,
7904 cx,
7905 )
7906 } else {
7907 self.render_edit_prediction_eager_jump_popover(
7908 text_bounds,
7909 content_origin,
7910 editor_snapshot,
7911 visible_row_range,
7912 scroll_top,
7913 scroll_bottom,
7914 line_height,
7915 scroll_pixel_position,
7916 target_display_point,
7917 editor_width,
7918 window,
7919 cx,
7920 )
7921 }
7922 }
7923 InlineCompletion::Edit {
7924 display_mode: EditDisplayMode::Inline,
7925 ..
7926 } => None,
7927 InlineCompletion::Edit {
7928 display_mode: EditDisplayMode::TabAccept,
7929 edits,
7930 ..
7931 } => {
7932 let range = &edits.first()?.0;
7933 let target_display_point = range.end.to_display_point(editor_snapshot);
7934
7935 self.render_edit_prediction_end_of_line_popover(
7936 "Accept",
7937 editor_snapshot,
7938 visible_row_range,
7939 target_display_point,
7940 line_height,
7941 scroll_pixel_position,
7942 content_origin,
7943 editor_width,
7944 window,
7945 cx,
7946 )
7947 }
7948 InlineCompletion::Edit {
7949 edits,
7950 edit_preview,
7951 display_mode: EditDisplayMode::DiffPopover,
7952 snapshot,
7953 } => self.render_edit_prediction_diff_popover(
7954 text_bounds,
7955 content_origin,
7956 right_margin,
7957 editor_snapshot,
7958 visible_row_range,
7959 line_layouts,
7960 line_height,
7961 scroll_pixel_position,
7962 newest_selection_head,
7963 editor_width,
7964 style,
7965 edits,
7966 edit_preview,
7967 snapshot,
7968 window,
7969 cx,
7970 ),
7971 }
7972 }
7973
7974 fn render_edit_prediction_modifier_jump_popover(
7975 &mut self,
7976 text_bounds: &Bounds<Pixels>,
7977 content_origin: gpui::Point<Pixels>,
7978 visible_row_range: Range<DisplayRow>,
7979 line_layouts: &[LineWithInvisibles],
7980 line_height: Pixels,
7981 scroll_pixel_position: gpui::Point<Pixels>,
7982 newest_selection_head: Option<DisplayPoint>,
7983 target_display_point: DisplayPoint,
7984 window: &mut Window,
7985 cx: &mut App,
7986 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7987 let scrolled_content_origin =
7988 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7989
7990 const SCROLL_PADDING_Y: Pixels = px(12.);
7991
7992 if target_display_point.row() < visible_row_range.start {
7993 return self.render_edit_prediction_scroll_popover(
7994 |_| SCROLL_PADDING_Y,
7995 IconName::ArrowUp,
7996 visible_row_range,
7997 line_layouts,
7998 newest_selection_head,
7999 scrolled_content_origin,
8000 window,
8001 cx,
8002 );
8003 } else if target_display_point.row() >= visible_row_range.end {
8004 return self.render_edit_prediction_scroll_popover(
8005 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8006 IconName::ArrowDown,
8007 visible_row_range,
8008 line_layouts,
8009 newest_selection_head,
8010 scrolled_content_origin,
8011 window,
8012 cx,
8013 );
8014 }
8015
8016 const POLE_WIDTH: Pixels = px(2.);
8017
8018 let line_layout =
8019 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8020 let target_column = target_display_point.column() as usize;
8021
8022 let target_x = line_layout.x_for_index(target_column);
8023 let target_y =
8024 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8025
8026 let flag_on_right = target_x < text_bounds.size.width / 2.;
8027
8028 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8029 border_color.l += 0.001;
8030
8031 let mut element = v_flex()
8032 .items_end()
8033 .when(flag_on_right, |el| el.items_start())
8034 .child(if flag_on_right {
8035 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8036 .rounded_bl(px(0.))
8037 .rounded_tl(px(0.))
8038 .border_l_2()
8039 .border_color(border_color)
8040 } else {
8041 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8042 .rounded_br(px(0.))
8043 .rounded_tr(px(0.))
8044 .border_r_2()
8045 .border_color(border_color)
8046 })
8047 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8048 .into_any();
8049
8050 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8051
8052 let mut origin = scrolled_content_origin + point(target_x, target_y)
8053 - point(
8054 if flag_on_right {
8055 POLE_WIDTH
8056 } else {
8057 size.width - POLE_WIDTH
8058 },
8059 size.height - line_height,
8060 );
8061
8062 origin.x = origin.x.max(content_origin.x);
8063
8064 element.prepaint_at(origin, window, cx);
8065
8066 Some((element, origin))
8067 }
8068
8069 fn render_edit_prediction_scroll_popover(
8070 &mut self,
8071 to_y: impl Fn(Size<Pixels>) -> Pixels,
8072 scroll_icon: IconName,
8073 visible_row_range: Range<DisplayRow>,
8074 line_layouts: &[LineWithInvisibles],
8075 newest_selection_head: Option<DisplayPoint>,
8076 scrolled_content_origin: gpui::Point<Pixels>,
8077 window: &mut Window,
8078 cx: &mut App,
8079 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8080 let mut element = self
8081 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8082 .into_any();
8083
8084 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8085
8086 let cursor = newest_selection_head?;
8087 let cursor_row_layout =
8088 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8089 let cursor_column = cursor.column() as usize;
8090
8091 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8092
8093 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8094
8095 element.prepaint_at(origin, window, cx);
8096 Some((element, origin))
8097 }
8098
8099 fn render_edit_prediction_eager_jump_popover(
8100 &mut self,
8101 text_bounds: &Bounds<Pixels>,
8102 content_origin: gpui::Point<Pixels>,
8103 editor_snapshot: &EditorSnapshot,
8104 visible_row_range: Range<DisplayRow>,
8105 scroll_top: f32,
8106 scroll_bottom: f32,
8107 line_height: Pixels,
8108 scroll_pixel_position: gpui::Point<Pixels>,
8109 target_display_point: DisplayPoint,
8110 editor_width: Pixels,
8111 window: &mut Window,
8112 cx: &mut App,
8113 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8114 if target_display_point.row().as_f32() < scroll_top {
8115 let mut element = self
8116 .render_edit_prediction_line_popover(
8117 "Jump to Edit",
8118 Some(IconName::ArrowUp),
8119 window,
8120 cx,
8121 )?
8122 .into_any();
8123
8124 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8125 let offset = point(
8126 (text_bounds.size.width - size.width) / 2.,
8127 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8128 );
8129
8130 let origin = text_bounds.origin + offset;
8131 element.prepaint_at(origin, window, cx);
8132 Some((element, origin))
8133 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8134 let mut element = self
8135 .render_edit_prediction_line_popover(
8136 "Jump to Edit",
8137 Some(IconName::ArrowDown),
8138 window,
8139 cx,
8140 )?
8141 .into_any();
8142
8143 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8144 let offset = point(
8145 (text_bounds.size.width - size.width) / 2.,
8146 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8147 );
8148
8149 let origin = text_bounds.origin + offset;
8150 element.prepaint_at(origin, window, cx);
8151 Some((element, origin))
8152 } else {
8153 self.render_edit_prediction_end_of_line_popover(
8154 "Jump to Edit",
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 }
8167
8168 fn render_edit_prediction_end_of_line_popover(
8169 self: &mut Editor,
8170 label: &'static str,
8171 editor_snapshot: &EditorSnapshot,
8172 visible_row_range: Range<DisplayRow>,
8173 target_display_point: DisplayPoint,
8174 line_height: Pixels,
8175 scroll_pixel_position: gpui::Point<Pixels>,
8176 content_origin: gpui::Point<Pixels>,
8177 editor_width: Pixels,
8178 window: &mut Window,
8179 cx: &mut App,
8180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8181 let target_line_end = DisplayPoint::new(
8182 target_display_point.row(),
8183 editor_snapshot.line_len(target_display_point.row()),
8184 );
8185
8186 let mut element = self
8187 .render_edit_prediction_line_popover(label, None, window, cx)?
8188 .into_any();
8189
8190 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8191
8192 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8193
8194 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8195 let mut origin = start_point
8196 + line_origin
8197 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8198 origin.x = origin.x.max(content_origin.x);
8199
8200 let max_x = content_origin.x + editor_width - size.width;
8201
8202 if origin.x > max_x {
8203 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8204
8205 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8206 origin.y += offset;
8207 IconName::ArrowUp
8208 } else {
8209 origin.y -= offset;
8210 IconName::ArrowDown
8211 };
8212
8213 element = self
8214 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8215 .into_any();
8216
8217 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8218
8219 origin.x = content_origin.x + editor_width - size.width - px(2.);
8220 }
8221
8222 element.prepaint_at(origin, window, cx);
8223 Some((element, origin))
8224 }
8225
8226 fn render_edit_prediction_diff_popover(
8227 self: &Editor,
8228 text_bounds: &Bounds<Pixels>,
8229 content_origin: gpui::Point<Pixels>,
8230 right_margin: Pixels,
8231 editor_snapshot: &EditorSnapshot,
8232 visible_row_range: Range<DisplayRow>,
8233 line_layouts: &[LineWithInvisibles],
8234 line_height: Pixels,
8235 scroll_pixel_position: gpui::Point<Pixels>,
8236 newest_selection_head: Option<DisplayPoint>,
8237 editor_width: Pixels,
8238 style: &EditorStyle,
8239 edits: &Vec<(Range<Anchor>, String)>,
8240 edit_preview: &Option<language::EditPreview>,
8241 snapshot: &language::BufferSnapshot,
8242 window: &mut Window,
8243 cx: &mut App,
8244 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8245 let edit_start = edits
8246 .first()
8247 .unwrap()
8248 .0
8249 .start
8250 .to_display_point(editor_snapshot);
8251 let edit_end = edits
8252 .last()
8253 .unwrap()
8254 .0
8255 .end
8256 .to_display_point(editor_snapshot);
8257
8258 let is_visible = visible_row_range.contains(&edit_start.row())
8259 || visible_row_range.contains(&edit_end.row());
8260 if !is_visible {
8261 return None;
8262 }
8263
8264 let highlighted_edits =
8265 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8266
8267 let styled_text = highlighted_edits.to_styled_text(&style.text);
8268 let line_count = highlighted_edits.text.lines().count();
8269
8270 const BORDER_WIDTH: Pixels = px(1.);
8271
8272 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8273 let has_keybind = keybind.is_some();
8274
8275 let mut element = h_flex()
8276 .items_start()
8277 .child(
8278 h_flex()
8279 .bg(cx.theme().colors().editor_background)
8280 .border(BORDER_WIDTH)
8281 .shadow_sm()
8282 .border_color(cx.theme().colors().border)
8283 .rounded_l_lg()
8284 .when(line_count > 1, |el| el.rounded_br_lg())
8285 .pr_1()
8286 .child(styled_text),
8287 )
8288 .child(
8289 h_flex()
8290 .h(line_height + BORDER_WIDTH * 2.)
8291 .px_1p5()
8292 .gap_1()
8293 // Workaround: For some reason, there's a gap if we don't do this
8294 .ml(-BORDER_WIDTH)
8295 .shadow(vec![gpui::BoxShadow {
8296 color: gpui::black().opacity(0.05),
8297 offset: point(px(1.), px(1.)),
8298 blur_radius: px(2.),
8299 spread_radius: px(0.),
8300 }])
8301 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8302 .border(BORDER_WIDTH)
8303 .border_color(cx.theme().colors().border)
8304 .rounded_r_lg()
8305 .id("edit_prediction_diff_popover_keybind")
8306 .when(!has_keybind, |el| {
8307 let status_colors = cx.theme().status();
8308
8309 el.bg(status_colors.error_background)
8310 .border_color(status_colors.error.opacity(0.6))
8311 .child(Icon::new(IconName::Info).color(Color::Error))
8312 .cursor_default()
8313 .hoverable_tooltip(move |_window, cx| {
8314 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8315 })
8316 })
8317 .children(keybind),
8318 )
8319 .into_any();
8320
8321 let longest_row =
8322 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8323 let longest_line_width = if visible_row_range.contains(&longest_row) {
8324 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8325 } else {
8326 layout_line(
8327 longest_row,
8328 editor_snapshot,
8329 style,
8330 editor_width,
8331 |_| false,
8332 window,
8333 cx,
8334 )
8335 .width
8336 };
8337
8338 let viewport_bounds =
8339 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8340 right: -right_margin,
8341 ..Default::default()
8342 });
8343
8344 let x_after_longest =
8345 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8346 - scroll_pixel_position.x;
8347
8348 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8349
8350 // Fully visible if it can be displayed within the window (allow overlapping other
8351 // panes). However, this is only allowed if the popover starts within text_bounds.
8352 let can_position_to_the_right = x_after_longest < text_bounds.right()
8353 && x_after_longest + element_bounds.width < viewport_bounds.right();
8354
8355 let mut origin = if can_position_to_the_right {
8356 point(
8357 x_after_longest,
8358 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8359 - scroll_pixel_position.y,
8360 )
8361 } else {
8362 let cursor_row = newest_selection_head.map(|head| head.row());
8363 let above_edit = edit_start
8364 .row()
8365 .0
8366 .checked_sub(line_count as u32)
8367 .map(DisplayRow);
8368 let below_edit = Some(edit_end.row() + 1);
8369 let above_cursor =
8370 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8371 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8372
8373 // Place the edit popover adjacent to the edit if there is a location
8374 // available that is onscreen and does not obscure the cursor. Otherwise,
8375 // place it adjacent to the cursor.
8376 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8377 .into_iter()
8378 .flatten()
8379 .find(|&start_row| {
8380 let end_row = start_row + line_count as u32;
8381 visible_row_range.contains(&start_row)
8382 && visible_row_range.contains(&end_row)
8383 && cursor_row.map_or(true, |cursor_row| {
8384 !((start_row..end_row).contains(&cursor_row))
8385 })
8386 })?;
8387
8388 content_origin
8389 + point(
8390 -scroll_pixel_position.x,
8391 row_target.as_f32() * line_height - scroll_pixel_position.y,
8392 )
8393 };
8394
8395 origin.x -= BORDER_WIDTH;
8396
8397 window.defer_draw(element, origin, 1);
8398
8399 // Do not return an element, since it will already be drawn due to defer_draw.
8400 None
8401 }
8402
8403 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8404 px(30.)
8405 }
8406
8407 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8408 if self.read_only(cx) {
8409 cx.theme().players().read_only()
8410 } else {
8411 self.style.as_ref().unwrap().local_player
8412 }
8413 }
8414
8415 fn render_edit_prediction_accept_keybind(
8416 &self,
8417 window: &mut Window,
8418 cx: &App,
8419 ) -> Option<AnyElement> {
8420 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8421 let accept_keystroke = accept_binding.keystroke()?;
8422
8423 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8424
8425 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8426 Color::Accent
8427 } else {
8428 Color::Muted
8429 };
8430
8431 h_flex()
8432 .px_0p5()
8433 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8434 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8435 .text_size(TextSize::XSmall.rems(cx))
8436 .child(h_flex().children(ui::render_modifiers(
8437 &accept_keystroke.modifiers,
8438 PlatformStyle::platform(),
8439 Some(modifiers_color),
8440 Some(IconSize::XSmall.rems().into()),
8441 true,
8442 )))
8443 .when(is_platform_style_mac, |parent| {
8444 parent.child(accept_keystroke.key.clone())
8445 })
8446 .when(!is_platform_style_mac, |parent| {
8447 parent.child(
8448 Key::new(
8449 util::capitalize(&accept_keystroke.key),
8450 Some(Color::Default),
8451 )
8452 .size(Some(IconSize::XSmall.rems().into())),
8453 )
8454 })
8455 .into_any()
8456 .into()
8457 }
8458
8459 fn render_edit_prediction_line_popover(
8460 &self,
8461 label: impl Into<SharedString>,
8462 icon: Option<IconName>,
8463 window: &mut Window,
8464 cx: &App,
8465 ) -> Option<Stateful<Div>> {
8466 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8467
8468 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8469 let has_keybind = keybind.is_some();
8470
8471 let result = h_flex()
8472 .id("ep-line-popover")
8473 .py_0p5()
8474 .pl_1()
8475 .pr(padding_right)
8476 .gap_1()
8477 .rounded_md()
8478 .border_1()
8479 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8480 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8481 .shadow_sm()
8482 .when(!has_keybind, |el| {
8483 let status_colors = cx.theme().status();
8484
8485 el.bg(status_colors.error_background)
8486 .border_color(status_colors.error.opacity(0.6))
8487 .pl_2()
8488 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8489 .cursor_default()
8490 .hoverable_tooltip(move |_window, cx| {
8491 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8492 })
8493 })
8494 .children(keybind)
8495 .child(
8496 Label::new(label)
8497 .size(LabelSize::Small)
8498 .when(!has_keybind, |el| {
8499 el.color(cx.theme().status().error.into()).strikethrough()
8500 }),
8501 )
8502 .when(!has_keybind, |el| {
8503 el.child(
8504 h_flex().ml_1().child(
8505 Icon::new(IconName::Info)
8506 .size(IconSize::Small)
8507 .color(cx.theme().status().error.into()),
8508 ),
8509 )
8510 })
8511 .when_some(icon, |element, icon| {
8512 element.child(
8513 div()
8514 .mt(px(1.5))
8515 .child(Icon::new(icon).size(IconSize::Small)),
8516 )
8517 });
8518
8519 Some(result)
8520 }
8521
8522 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8523 let accent_color = cx.theme().colors().text_accent;
8524 let editor_bg_color = cx.theme().colors().editor_background;
8525 editor_bg_color.blend(accent_color.opacity(0.1))
8526 }
8527
8528 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8529 let accent_color = cx.theme().colors().text_accent;
8530 let editor_bg_color = cx.theme().colors().editor_background;
8531 editor_bg_color.blend(accent_color.opacity(0.6))
8532 }
8533
8534 fn render_edit_prediction_cursor_popover(
8535 &self,
8536 min_width: Pixels,
8537 max_width: Pixels,
8538 cursor_point: Point,
8539 style: &EditorStyle,
8540 accept_keystroke: Option<&gpui::Keystroke>,
8541 _window: &Window,
8542 cx: &mut Context<Editor>,
8543 ) -> Option<AnyElement> {
8544 let provider = self.edit_prediction_provider.as_ref()?;
8545
8546 if provider.provider.needs_terms_acceptance(cx) {
8547 return Some(
8548 h_flex()
8549 .min_w(min_width)
8550 .flex_1()
8551 .px_2()
8552 .py_1()
8553 .gap_3()
8554 .elevation_2(cx)
8555 .hover(|style| style.bg(cx.theme().colors().element_hover))
8556 .id("accept-terms")
8557 .cursor_pointer()
8558 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8559 .on_click(cx.listener(|this, _event, window, cx| {
8560 cx.stop_propagation();
8561 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8562 window.dispatch_action(
8563 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8564 cx,
8565 );
8566 }))
8567 .child(
8568 h_flex()
8569 .flex_1()
8570 .gap_2()
8571 .child(Icon::new(IconName::ZedPredict))
8572 .child(Label::new("Accept Terms of Service"))
8573 .child(div().w_full())
8574 .child(
8575 Icon::new(IconName::ArrowUpRight)
8576 .color(Color::Muted)
8577 .size(IconSize::Small),
8578 )
8579 .into_any_element(),
8580 )
8581 .into_any(),
8582 );
8583 }
8584
8585 let is_refreshing = provider.provider.is_refreshing(cx);
8586
8587 fn pending_completion_container() -> Div {
8588 h_flex()
8589 .h_full()
8590 .flex_1()
8591 .gap_2()
8592 .child(Icon::new(IconName::ZedPredict))
8593 }
8594
8595 let completion = match &self.active_inline_completion {
8596 Some(prediction) => {
8597 if !self.has_visible_completions_menu() {
8598 const RADIUS: Pixels = px(6.);
8599 const BORDER_WIDTH: Pixels = px(1.);
8600
8601 return Some(
8602 h_flex()
8603 .elevation_2(cx)
8604 .border(BORDER_WIDTH)
8605 .border_color(cx.theme().colors().border)
8606 .when(accept_keystroke.is_none(), |el| {
8607 el.border_color(cx.theme().status().error)
8608 })
8609 .rounded(RADIUS)
8610 .rounded_tl(px(0.))
8611 .overflow_hidden()
8612 .child(div().px_1p5().child(match &prediction.completion {
8613 InlineCompletion::Move { target, snapshot } => {
8614 use text::ToPoint as _;
8615 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8616 {
8617 Icon::new(IconName::ZedPredictDown)
8618 } else {
8619 Icon::new(IconName::ZedPredictUp)
8620 }
8621 }
8622 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8623 }))
8624 .child(
8625 h_flex()
8626 .gap_1()
8627 .py_1()
8628 .px_2()
8629 .rounded_r(RADIUS - BORDER_WIDTH)
8630 .border_l_1()
8631 .border_color(cx.theme().colors().border)
8632 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8633 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8634 el.child(
8635 Label::new("Hold")
8636 .size(LabelSize::Small)
8637 .when(accept_keystroke.is_none(), |el| {
8638 el.strikethrough()
8639 })
8640 .line_height_style(LineHeightStyle::UiLabel),
8641 )
8642 })
8643 .id("edit_prediction_cursor_popover_keybind")
8644 .when(accept_keystroke.is_none(), |el| {
8645 let status_colors = cx.theme().status();
8646
8647 el.bg(status_colors.error_background)
8648 .border_color(status_colors.error.opacity(0.6))
8649 .child(Icon::new(IconName::Info).color(Color::Error))
8650 .cursor_default()
8651 .hoverable_tooltip(move |_window, cx| {
8652 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8653 .into()
8654 })
8655 })
8656 .when_some(
8657 accept_keystroke.as_ref(),
8658 |el, accept_keystroke| {
8659 el.child(h_flex().children(ui::render_modifiers(
8660 &accept_keystroke.modifiers,
8661 PlatformStyle::platform(),
8662 Some(Color::Default),
8663 Some(IconSize::XSmall.rems().into()),
8664 false,
8665 )))
8666 },
8667 ),
8668 )
8669 .into_any(),
8670 );
8671 }
8672
8673 self.render_edit_prediction_cursor_popover_preview(
8674 prediction,
8675 cursor_point,
8676 style,
8677 cx,
8678 )?
8679 }
8680
8681 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8682 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8683 stale_completion,
8684 cursor_point,
8685 style,
8686 cx,
8687 )?,
8688
8689 None => {
8690 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8691 }
8692 },
8693
8694 None => pending_completion_container().child(Label::new("No Prediction")),
8695 };
8696
8697 let completion = if is_refreshing {
8698 completion
8699 .with_animation(
8700 "loading-completion",
8701 Animation::new(Duration::from_secs(2))
8702 .repeat()
8703 .with_easing(pulsating_between(0.4, 0.8)),
8704 |label, delta| label.opacity(delta),
8705 )
8706 .into_any_element()
8707 } else {
8708 completion.into_any_element()
8709 };
8710
8711 let has_completion = self.active_inline_completion.is_some();
8712
8713 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8714 Some(
8715 h_flex()
8716 .min_w(min_width)
8717 .max_w(max_width)
8718 .flex_1()
8719 .elevation_2(cx)
8720 .border_color(cx.theme().colors().border)
8721 .child(
8722 div()
8723 .flex_1()
8724 .py_1()
8725 .px_2()
8726 .overflow_hidden()
8727 .child(completion),
8728 )
8729 .when_some(accept_keystroke, |el, accept_keystroke| {
8730 if !accept_keystroke.modifiers.modified() {
8731 return el;
8732 }
8733
8734 el.child(
8735 h_flex()
8736 .h_full()
8737 .border_l_1()
8738 .rounded_r_lg()
8739 .border_color(cx.theme().colors().border)
8740 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8741 .gap_1()
8742 .py_1()
8743 .px_2()
8744 .child(
8745 h_flex()
8746 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8747 .when(is_platform_style_mac, |parent| parent.gap_1())
8748 .child(h_flex().children(ui::render_modifiers(
8749 &accept_keystroke.modifiers,
8750 PlatformStyle::platform(),
8751 Some(if !has_completion {
8752 Color::Muted
8753 } else {
8754 Color::Default
8755 }),
8756 None,
8757 false,
8758 ))),
8759 )
8760 .child(Label::new("Preview").into_any_element())
8761 .opacity(if has_completion { 1.0 } else { 0.4 }),
8762 )
8763 })
8764 .into_any(),
8765 )
8766 }
8767
8768 fn render_edit_prediction_cursor_popover_preview(
8769 &self,
8770 completion: &InlineCompletionState,
8771 cursor_point: Point,
8772 style: &EditorStyle,
8773 cx: &mut Context<Editor>,
8774 ) -> Option<Div> {
8775 use text::ToPoint as _;
8776
8777 fn render_relative_row_jump(
8778 prefix: impl Into<String>,
8779 current_row: u32,
8780 target_row: u32,
8781 ) -> Div {
8782 let (row_diff, arrow) = if target_row < current_row {
8783 (current_row - target_row, IconName::ArrowUp)
8784 } else {
8785 (target_row - current_row, IconName::ArrowDown)
8786 };
8787
8788 h_flex()
8789 .child(
8790 Label::new(format!("{}{}", prefix.into(), row_diff))
8791 .color(Color::Muted)
8792 .size(LabelSize::Small),
8793 )
8794 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8795 }
8796
8797 match &completion.completion {
8798 InlineCompletion::Move {
8799 target, snapshot, ..
8800 } => Some(
8801 h_flex()
8802 .px_2()
8803 .gap_2()
8804 .flex_1()
8805 .child(
8806 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8807 Icon::new(IconName::ZedPredictDown)
8808 } else {
8809 Icon::new(IconName::ZedPredictUp)
8810 },
8811 )
8812 .child(Label::new("Jump to Edit")),
8813 ),
8814
8815 InlineCompletion::Edit {
8816 edits,
8817 edit_preview,
8818 snapshot,
8819 display_mode: _,
8820 } => {
8821 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8822
8823 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8824 &snapshot,
8825 &edits,
8826 edit_preview.as_ref()?,
8827 true,
8828 cx,
8829 )
8830 .first_line_preview();
8831
8832 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8833 .with_default_highlights(&style.text, highlighted_edits.highlights);
8834
8835 let preview = h_flex()
8836 .gap_1()
8837 .min_w_16()
8838 .child(styled_text)
8839 .when(has_more_lines, |parent| parent.child("…"));
8840
8841 let left = if first_edit_row != cursor_point.row {
8842 render_relative_row_jump("", cursor_point.row, first_edit_row)
8843 .into_any_element()
8844 } else {
8845 Icon::new(IconName::ZedPredict).into_any_element()
8846 };
8847
8848 Some(
8849 h_flex()
8850 .h_full()
8851 .flex_1()
8852 .gap_2()
8853 .pr_1()
8854 .overflow_x_hidden()
8855 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8856 .child(left)
8857 .child(preview),
8858 )
8859 }
8860 }
8861 }
8862
8863 pub fn render_context_menu(
8864 &self,
8865 style: &EditorStyle,
8866 max_height_in_lines: u32,
8867 window: &mut Window,
8868 cx: &mut Context<Editor>,
8869 ) -> Option<AnyElement> {
8870 let menu = self.context_menu.borrow();
8871 let menu = menu.as_ref()?;
8872 if !menu.visible() {
8873 return None;
8874 };
8875 Some(menu.render(style, max_height_in_lines, window, cx))
8876 }
8877
8878 fn render_context_menu_aside(
8879 &mut self,
8880 max_size: Size<Pixels>,
8881 window: &mut Window,
8882 cx: &mut Context<Editor>,
8883 ) -> Option<AnyElement> {
8884 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8885 if menu.visible() {
8886 menu.render_aside(max_size, window, cx)
8887 } else {
8888 None
8889 }
8890 })
8891 }
8892
8893 fn hide_context_menu(
8894 &mut self,
8895 window: &mut Window,
8896 cx: &mut Context<Self>,
8897 ) -> Option<CodeContextMenu> {
8898 cx.notify();
8899 self.completion_tasks.clear();
8900 let context_menu = self.context_menu.borrow_mut().take();
8901 self.stale_inline_completion_in_menu.take();
8902 self.update_visible_inline_completion(window, cx);
8903 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8904 if let Some(completion_provider) = &self.completion_provider {
8905 completion_provider.selection_changed(None, window, cx);
8906 }
8907 }
8908 context_menu
8909 }
8910
8911 fn show_snippet_choices(
8912 &mut self,
8913 choices: &Vec<String>,
8914 selection: Range<Anchor>,
8915 cx: &mut Context<Self>,
8916 ) {
8917 if selection.start.buffer_id.is_none() {
8918 return;
8919 }
8920 let buffer_id = selection.start.buffer_id.unwrap();
8921 let buffer = self.buffer().read(cx).buffer(buffer_id);
8922 let id = post_inc(&mut self.next_completion_id);
8923 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8924
8925 if let Some(buffer) = buffer {
8926 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8927 CompletionsMenu::new_snippet_choices(
8928 id,
8929 true,
8930 choices,
8931 selection,
8932 buffer,
8933 snippet_sort_order,
8934 ),
8935 ));
8936 }
8937 }
8938
8939 pub fn insert_snippet(
8940 &mut self,
8941 insertion_ranges: &[Range<usize>],
8942 snippet: Snippet,
8943 window: &mut Window,
8944 cx: &mut Context<Self>,
8945 ) -> Result<()> {
8946 struct Tabstop<T> {
8947 is_end_tabstop: bool,
8948 ranges: Vec<Range<T>>,
8949 choices: Option<Vec<String>>,
8950 }
8951
8952 let tabstops = self.buffer.update(cx, |buffer, cx| {
8953 let snippet_text: Arc<str> = snippet.text.clone().into();
8954 let edits = insertion_ranges
8955 .iter()
8956 .cloned()
8957 .map(|range| (range, snippet_text.clone()));
8958 let autoindent_mode = AutoindentMode::Block {
8959 original_indent_columns: Vec::new(),
8960 };
8961 buffer.edit(edits, Some(autoindent_mode), cx);
8962
8963 let snapshot = &*buffer.read(cx);
8964 let snippet = &snippet;
8965 snippet
8966 .tabstops
8967 .iter()
8968 .map(|tabstop| {
8969 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8970 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8971 });
8972 let mut tabstop_ranges = tabstop
8973 .ranges
8974 .iter()
8975 .flat_map(|tabstop_range| {
8976 let mut delta = 0_isize;
8977 insertion_ranges.iter().map(move |insertion_range| {
8978 let insertion_start = insertion_range.start as isize + delta;
8979 delta +=
8980 snippet.text.len() as isize - insertion_range.len() as isize;
8981
8982 let start = ((insertion_start + tabstop_range.start) as usize)
8983 .min(snapshot.len());
8984 let end = ((insertion_start + tabstop_range.end) as usize)
8985 .min(snapshot.len());
8986 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8987 })
8988 })
8989 .collect::<Vec<_>>();
8990 // Sort in reverse order so that the first range is the newest created
8991 // selection. Completions will use it and autoscroll will prioritize it.
8992 tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
8993
8994 Tabstop {
8995 is_end_tabstop,
8996 ranges: tabstop_ranges,
8997 choices: tabstop.choices.clone(),
8998 }
8999 })
9000 .collect::<Vec<_>>()
9001 });
9002 if let Some(tabstop) = tabstops.first() {
9003 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9004 s.select_ranges(tabstop.ranges.iter().cloned());
9005 });
9006
9007 if let Some(choices) = &tabstop.choices {
9008 if let Some(selection) = tabstop.ranges.first() {
9009 self.show_snippet_choices(choices, selection.clone(), cx)
9010 }
9011 }
9012
9013 // If we're already at the last tabstop and it's at the end of the snippet,
9014 // we're done, we don't need to keep the state around.
9015 if !tabstop.is_end_tabstop {
9016 let choices = tabstops
9017 .iter()
9018 .map(|tabstop| tabstop.choices.clone())
9019 .collect();
9020
9021 let ranges = tabstops
9022 .into_iter()
9023 .map(|tabstop| tabstop.ranges)
9024 .collect::<Vec<_>>();
9025
9026 self.snippet_stack.push(SnippetState {
9027 active_index: 0,
9028 ranges,
9029 choices,
9030 });
9031 }
9032
9033 // Check whether the just-entered snippet ends with an auto-closable bracket.
9034 if self.autoclose_regions.is_empty() {
9035 let snapshot = self.buffer.read(cx).snapshot(cx);
9036 for selection in &mut self.selections.all::<Point>(cx) {
9037 let selection_head = selection.head();
9038 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9039 continue;
9040 };
9041
9042 let mut bracket_pair = None;
9043 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9044 let prev_chars = snapshot
9045 .reversed_chars_at(selection_head)
9046 .collect::<String>();
9047 for (pair, enabled) in scope.brackets() {
9048 if enabled
9049 && pair.close
9050 && prev_chars.starts_with(pair.start.as_str())
9051 && next_chars.starts_with(pair.end.as_str())
9052 {
9053 bracket_pair = Some(pair.clone());
9054 break;
9055 }
9056 }
9057 if let Some(pair) = bracket_pair {
9058 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9059 let autoclose_enabled =
9060 self.use_autoclose && snapshot_settings.use_autoclose;
9061 if autoclose_enabled {
9062 let start = snapshot.anchor_after(selection_head);
9063 let end = snapshot.anchor_after(selection_head);
9064 self.autoclose_regions.push(AutocloseRegion {
9065 selection_id: selection.id,
9066 range: start..end,
9067 pair,
9068 });
9069 }
9070 }
9071 }
9072 }
9073 }
9074 Ok(())
9075 }
9076
9077 pub fn move_to_next_snippet_tabstop(
9078 &mut self,
9079 window: &mut Window,
9080 cx: &mut Context<Self>,
9081 ) -> bool {
9082 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9083 }
9084
9085 pub fn move_to_prev_snippet_tabstop(
9086 &mut self,
9087 window: &mut Window,
9088 cx: &mut Context<Self>,
9089 ) -> bool {
9090 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9091 }
9092
9093 pub fn move_to_snippet_tabstop(
9094 &mut self,
9095 bias: Bias,
9096 window: &mut Window,
9097 cx: &mut Context<Self>,
9098 ) -> bool {
9099 if let Some(mut snippet) = self.snippet_stack.pop() {
9100 match bias {
9101 Bias::Left => {
9102 if snippet.active_index > 0 {
9103 snippet.active_index -= 1;
9104 } else {
9105 self.snippet_stack.push(snippet);
9106 return false;
9107 }
9108 }
9109 Bias::Right => {
9110 if snippet.active_index + 1 < snippet.ranges.len() {
9111 snippet.active_index += 1;
9112 } else {
9113 self.snippet_stack.push(snippet);
9114 return false;
9115 }
9116 }
9117 }
9118 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9119 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9120 s.select_ranges(current_ranges.iter().cloned())
9121 });
9122
9123 if let Some(choices) = &snippet.choices[snippet.active_index] {
9124 if let Some(selection) = current_ranges.first() {
9125 self.show_snippet_choices(&choices, selection.clone(), cx);
9126 }
9127 }
9128
9129 // If snippet state is not at the last tabstop, push it back on the stack
9130 if snippet.active_index + 1 < snippet.ranges.len() {
9131 self.snippet_stack.push(snippet);
9132 }
9133 return true;
9134 }
9135 }
9136
9137 false
9138 }
9139
9140 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9141 self.transact(window, cx, |this, window, cx| {
9142 this.select_all(&SelectAll, window, cx);
9143 this.insert("", window, cx);
9144 });
9145 }
9146
9147 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9148 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9149 self.transact(window, cx, |this, window, cx| {
9150 this.select_autoclose_pair(window, cx);
9151 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9152 if !this.linked_edit_ranges.is_empty() {
9153 let selections = this.selections.all::<MultiBufferPoint>(cx);
9154 let snapshot = this.buffer.read(cx).snapshot(cx);
9155
9156 for selection in selections.iter() {
9157 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9158 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9159 if selection_start.buffer_id != selection_end.buffer_id {
9160 continue;
9161 }
9162 if let Some(ranges) =
9163 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9164 {
9165 for (buffer, entries) in ranges {
9166 linked_ranges.entry(buffer).or_default().extend(entries);
9167 }
9168 }
9169 }
9170 }
9171
9172 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9173 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9174 for selection in &mut selections {
9175 if selection.is_empty() {
9176 let old_head = selection.head();
9177 let mut new_head =
9178 movement::left(&display_map, old_head.to_display_point(&display_map))
9179 .to_point(&display_map);
9180 if let Some((buffer, line_buffer_range)) = display_map
9181 .buffer_snapshot
9182 .buffer_line_for_row(MultiBufferRow(old_head.row))
9183 {
9184 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9185 let indent_len = match indent_size.kind {
9186 IndentKind::Space => {
9187 buffer.settings_at(line_buffer_range.start, cx).tab_size
9188 }
9189 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9190 };
9191 if old_head.column <= indent_size.len && old_head.column > 0 {
9192 let indent_len = indent_len.get();
9193 new_head = cmp::min(
9194 new_head,
9195 MultiBufferPoint::new(
9196 old_head.row,
9197 ((old_head.column - 1) / indent_len) * indent_len,
9198 ),
9199 );
9200 }
9201 }
9202
9203 selection.set_head(new_head, SelectionGoal::None);
9204 }
9205 }
9206
9207 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9208 s.select(selections)
9209 });
9210 this.insert("", window, cx);
9211 let empty_str: Arc<str> = Arc::from("");
9212 for (buffer, edits) in linked_ranges {
9213 let snapshot = buffer.read(cx).snapshot();
9214 use text::ToPoint as TP;
9215
9216 let edits = edits
9217 .into_iter()
9218 .map(|range| {
9219 let end_point = TP::to_point(&range.end, &snapshot);
9220 let mut start_point = TP::to_point(&range.start, &snapshot);
9221
9222 if end_point == start_point {
9223 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9224 .saturating_sub(1);
9225 start_point =
9226 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9227 };
9228
9229 (start_point..end_point, empty_str.clone())
9230 })
9231 .sorted_by_key(|(range, _)| range.start)
9232 .collect::<Vec<_>>();
9233 buffer.update(cx, |this, cx| {
9234 this.edit(edits, None, cx);
9235 })
9236 }
9237 this.refresh_inline_completion(true, false, window, cx);
9238 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9239 });
9240 }
9241
9242 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9244 self.transact(window, cx, |this, window, cx| {
9245 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9246 s.move_with(|map, selection| {
9247 if selection.is_empty() {
9248 let cursor = movement::right(map, selection.head());
9249 selection.end = cursor;
9250 selection.reversed = true;
9251 selection.goal = SelectionGoal::None;
9252 }
9253 })
9254 });
9255 this.insert("", window, cx);
9256 this.refresh_inline_completion(true, false, window, cx);
9257 });
9258 }
9259
9260 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9261 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9262 if self.move_to_prev_snippet_tabstop(window, cx) {
9263 return;
9264 }
9265 self.outdent(&Outdent, window, cx);
9266 }
9267
9268 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9269 if self.move_to_next_snippet_tabstop(window, cx) {
9270 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9271 return;
9272 }
9273 if self.read_only(cx) {
9274 return;
9275 }
9276 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9277 let mut selections = self.selections.all_adjusted(cx);
9278 let buffer = self.buffer.read(cx);
9279 let snapshot = buffer.snapshot(cx);
9280 let rows_iter = selections.iter().map(|s| s.head().row);
9281 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9282
9283 let has_some_cursor_in_whitespace = selections
9284 .iter()
9285 .filter(|selection| selection.is_empty())
9286 .any(|selection| {
9287 let cursor = selection.head();
9288 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9289 cursor.column < current_indent.len
9290 });
9291
9292 let mut edits = Vec::new();
9293 let mut prev_edited_row = 0;
9294 let mut row_delta = 0;
9295 for selection in &mut selections {
9296 if selection.start.row != prev_edited_row {
9297 row_delta = 0;
9298 }
9299 prev_edited_row = selection.end.row;
9300
9301 // If the selection is non-empty, then increase the indentation of the selected lines.
9302 if !selection.is_empty() {
9303 row_delta =
9304 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9305 continue;
9306 }
9307
9308 let cursor = selection.head();
9309 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9310 if let Some(suggested_indent) =
9311 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9312 {
9313 // Don't do anything if already at suggested indent
9314 // and there is any other cursor which is not
9315 if has_some_cursor_in_whitespace
9316 && cursor.column == current_indent.len
9317 && current_indent.len == suggested_indent.len
9318 {
9319 continue;
9320 }
9321
9322 // Adjust line and move cursor to suggested indent
9323 // if cursor is not at suggested indent
9324 if cursor.column < suggested_indent.len
9325 && cursor.column <= current_indent.len
9326 && current_indent.len <= suggested_indent.len
9327 {
9328 selection.start = Point::new(cursor.row, suggested_indent.len);
9329 selection.end = selection.start;
9330 if row_delta == 0 {
9331 edits.extend(Buffer::edit_for_indent_size_adjustment(
9332 cursor.row,
9333 current_indent,
9334 suggested_indent,
9335 ));
9336 row_delta = suggested_indent.len - current_indent.len;
9337 }
9338 continue;
9339 }
9340
9341 // If current indent is more than suggested indent
9342 // only move cursor to current indent and skip indent
9343 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9344 selection.start = Point::new(cursor.row, current_indent.len);
9345 selection.end = selection.start;
9346 continue;
9347 }
9348 }
9349
9350 // Otherwise, insert a hard or soft tab.
9351 let settings = buffer.language_settings_at(cursor, cx);
9352 let tab_size = if settings.hard_tabs {
9353 IndentSize::tab()
9354 } else {
9355 let tab_size = settings.tab_size.get();
9356 let indent_remainder = snapshot
9357 .text_for_range(Point::new(cursor.row, 0)..cursor)
9358 .flat_map(str::chars)
9359 .fold(row_delta % tab_size, |counter: u32, c| {
9360 if c == '\t' {
9361 0
9362 } else {
9363 (counter + 1) % tab_size
9364 }
9365 });
9366
9367 let chars_to_next_tab_stop = tab_size - indent_remainder;
9368 IndentSize::spaces(chars_to_next_tab_stop)
9369 };
9370 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9371 selection.end = selection.start;
9372 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9373 row_delta += tab_size.len;
9374 }
9375
9376 self.transact(window, cx, |this, window, cx| {
9377 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9378 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9379 s.select(selections)
9380 });
9381 this.refresh_inline_completion(true, false, window, cx);
9382 });
9383 }
9384
9385 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9386 if self.read_only(cx) {
9387 return;
9388 }
9389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9390 let mut selections = self.selections.all::<Point>(cx);
9391 let mut prev_edited_row = 0;
9392 let mut row_delta = 0;
9393 let mut edits = Vec::new();
9394 let buffer = self.buffer.read(cx);
9395 let snapshot = buffer.snapshot(cx);
9396 for selection in &mut selections {
9397 if selection.start.row != prev_edited_row {
9398 row_delta = 0;
9399 }
9400 prev_edited_row = selection.end.row;
9401
9402 row_delta =
9403 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9404 }
9405
9406 self.transact(window, cx, |this, window, cx| {
9407 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9409 s.select(selections)
9410 });
9411 });
9412 }
9413
9414 fn indent_selection(
9415 buffer: &MultiBuffer,
9416 snapshot: &MultiBufferSnapshot,
9417 selection: &mut Selection<Point>,
9418 edits: &mut Vec<(Range<Point>, String)>,
9419 delta_for_start_row: u32,
9420 cx: &App,
9421 ) -> u32 {
9422 let settings = buffer.language_settings_at(selection.start, cx);
9423 let tab_size = settings.tab_size.get();
9424 let indent_kind = if settings.hard_tabs {
9425 IndentKind::Tab
9426 } else {
9427 IndentKind::Space
9428 };
9429 let mut start_row = selection.start.row;
9430 let mut end_row = selection.end.row + 1;
9431
9432 // If a selection ends at the beginning of a line, don't indent
9433 // that last line.
9434 if selection.end.column == 0 && selection.end.row > selection.start.row {
9435 end_row -= 1;
9436 }
9437
9438 // Avoid re-indenting a row that has already been indented by a
9439 // previous selection, but still update this selection's column
9440 // to reflect that indentation.
9441 if delta_for_start_row > 0 {
9442 start_row += 1;
9443 selection.start.column += delta_for_start_row;
9444 if selection.end.row == selection.start.row {
9445 selection.end.column += delta_for_start_row;
9446 }
9447 }
9448
9449 let mut delta_for_end_row = 0;
9450 let has_multiple_rows = start_row + 1 != end_row;
9451 for row in start_row..end_row {
9452 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9453 let indent_delta = match (current_indent.kind, indent_kind) {
9454 (IndentKind::Space, IndentKind::Space) => {
9455 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9456 IndentSize::spaces(columns_to_next_tab_stop)
9457 }
9458 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9459 (_, IndentKind::Tab) => IndentSize::tab(),
9460 };
9461
9462 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9463 0
9464 } else {
9465 selection.start.column
9466 };
9467 let row_start = Point::new(row, start);
9468 edits.push((
9469 row_start..row_start,
9470 indent_delta.chars().collect::<String>(),
9471 ));
9472
9473 // Update this selection's endpoints to reflect the indentation.
9474 if row == selection.start.row {
9475 selection.start.column += indent_delta.len;
9476 }
9477 if row == selection.end.row {
9478 selection.end.column += indent_delta.len;
9479 delta_for_end_row = indent_delta.len;
9480 }
9481 }
9482
9483 if selection.start.row == selection.end.row {
9484 delta_for_start_row + delta_for_end_row
9485 } else {
9486 delta_for_end_row
9487 }
9488 }
9489
9490 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9491 if self.read_only(cx) {
9492 return;
9493 }
9494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9496 let selections = self.selections.all::<Point>(cx);
9497 let mut deletion_ranges = Vec::new();
9498 let mut last_outdent = None;
9499 {
9500 let buffer = self.buffer.read(cx);
9501 let snapshot = buffer.snapshot(cx);
9502 for selection in &selections {
9503 let settings = buffer.language_settings_at(selection.start, cx);
9504 let tab_size = settings.tab_size.get();
9505 let mut rows = selection.spanned_rows(false, &display_map);
9506
9507 // Avoid re-outdenting a row that has already been outdented by a
9508 // previous selection.
9509 if let Some(last_row) = last_outdent {
9510 if last_row == rows.start {
9511 rows.start = rows.start.next_row();
9512 }
9513 }
9514 let has_multiple_rows = rows.len() > 1;
9515 for row in rows.iter_rows() {
9516 let indent_size = snapshot.indent_size_for_line(row);
9517 if indent_size.len > 0 {
9518 let deletion_len = match indent_size.kind {
9519 IndentKind::Space => {
9520 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9521 if columns_to_prev_tab_stop == 0 {
9522 tab_size
9523 } else {
9524 columns_to_prev_tab_stop
9525 }
9526 }
9527 IndentKind::Tab => 1,
9528 };
9529 let start = if has_multiple_rows
9530 || deletion_len > selection.start.column
9531 || indent_size.len < selection.start.column
9532 {
9533 0
9534 } else {
9535 selection.start.column - deletion_len
9536 };
9537 deletion_ranges.push(
9538 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9539 );
9540 last_outdent = Some(row);
9541 }
9542 }
9543 }
9544 }
9545
9546 self.transact(window, cx, |this, window, cx| {
9547 this.buffer.update(cx, |buffer, cx| {
9548 let empty_str: Arc<str> = Arc::default();
9549 buffer.edit(
9550 deletion_ranges
9551 .into_iter()
9552 .map(|range| (range, empty_str.clone())),
9553 None,
9554 cx,
9555 );
9556 });
9557 let selections = this.selections.all::<usize>(cx);
9558 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9559 s.select(selections)
9560 });
9561 });
9562 }
9563
9564 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9565 if self.read_only(cx) {
9566 return;
9567 }
9568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9569 let selections = self
9570 .selections
9571 .all::<usize>(cx)
9572 .into_iter()
9573 .map(|s| s.range());
9574
9575 self.transact(window, cx, |this, window, cx| {
9576 this.buffer.update(cx, |buffer, cx| {
9577 buffer.autoindent_ranges(selections, cx);
9578 });
9579 let selections = this.selections.all::<usize>(cx);
9580 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9581 s.select(selections)
9582 });
9583 });
9584 }
9585
9586 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9587 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9589 let selections = self.selections.all::<Point>(cx);
9590
9591 let mut new_cursors = Vec::new();
9592 let mut edit_ranges = Vec::new();
9593 let mut selections = selections.iter().peekable();
9594 while let Some(selection) = selections.next() {
9595 let mut rows = selection.spanned_rows(false, &display_map);
9596 let goal_display_column = selection.head().to_display_point(&display_map).column();
9597
9598 // Accumulate contiguous regions of rows that we want to delete.
9599 while let Some(next_selection) = selections.peek() {
9600 let next_rows = next_selection.spanned_rows(false, &display_map);
9601 if next_rows.start <= rows.end {
9602 rows.end = next_rows.end;
9603 selections.next().unwrap();
9604 } else {
9605 break;
9606 }
9607 }
9608
9609 let buffer = &display_map.buffer_snapshot;
9610 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9611 let edit_end;
9612 let cursor_buffer_row;
9613 if buffer.max_point().row >= rows.end.0 {
9614 // If there's a line after the range, delete the \n from the end of the row range
9615 // and position the cursor on the next line.
9616 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9617 cursor_buffer_row = rows.end;
9618 } else {
9619 // If there isn't a line after the range, delete the \n from the line before the
9620 // start of the row range and position the cursor there.
9621 edit_start = edit_start.saturating_sub(1);
9622 edit_end = buffer.len();
9623 cursor_buffer_row = rows.start.previous_row();
9624 }
9625
9626 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9627 *cursor.column_mut() =
9628 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9629
9630 new_cursors.push((
9631 selection.id,
9632 buffer.anchor_after(cursor.to_point(&display_map)),
9633 ));
9634 edit_ranges.push(edit_start..edit_end);
9635 }
9636
9637 self.transact(window, cx, |this, window, cx| {
9638 let buffer = this.buffer.update(cx, |buffer, cx| {
9639 let empty_str: Arc<str> = Arc::default();
9640 buffer.edit(
9641 edit_ranges
9642 .into_iter()
9643 .map(|range| (range, empty_str.clone())),
9644 None,
9645 cx,
9646 );
9647 buffer.snapshot(cx)
9648 });
9649 let new_selections = new_cursors
9650 .into_iter()
9651 .map(|(id, cursor)| {
9652 let cursor = cursor.to_point(&buffer);
9653 Selection {
9654 id,
9655 start: cursor,
9656 end: cursor,
9657 reversed: false,
9658 goal: SelectionGoal::None,
9659 }
9660 })
9661 .collect();
9662
9663 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9664 s.select(new_selections);
9665 });
9666 });
9667 }
9668
9669 pub fn join_lines_impl(
9670 &mut self,
9671 insert_whitespace: bool,
9672 window: &mut Window,
9673 cx: &mut Context<Self>,
9674 ) {
9675 if self.read_only(cx) {
9676 return;
9677 }
9678 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9679 for selection in self.selections.all::<Point>(cx) {
9680 let start = MultiBufferRow(selection.start.row);
9681 // Treat single line selections as if they include the next line. Otherwise this action
9682 // would do nothing for single line selections individual cursors.
9683 let end = if selection.start.row == selection.end.row {
9684 MultiBufferRow(selection.start.row + 1)
9685 } else {
9686 MultiBufferRow(selection.end.row)
9687 };
9688
9689 if let Some(last_row_range) = row_ranges.last_mut() {
9690 if start <= last_row_range.end {
9691 last_row_range.end = end;
9692 continue;
9693 }
9694 }
9695 row_ranges.push(start..end);
9696 }
9697
9698 let snapshot = self.buffer.read(cx).snapshot(cx);
9699 let mut cursor_positions = Vec::new();
9700 for row_range in &row_ranges {
9701 let anchor = snapshot.anchor_before(Point::new(
9702 row_range.end.previous_row().0,
9703 snapshot.line_len(row_range.end.previous_row()),
9704 ));
9705 cursor_positions.push(anchor..anchor);
9706 }
9707
9708 self.transact(window, cx, |this, window, cx| {
9709 for row_range in row_ranges.into_iter().rev() {
9710 for row in row_range.iter_rows().rev() {
9711 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9712 let next_line_row = row.next_row();
9713 let indent = snapshot.indent_size_for_line(next_line_row);
9714 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9715
9716 let replace =
9717 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9718 " "
9719 } else {
9720 ""
9721 };
9722
9723 this.buffer.update(cx, |buffer, cx| {
9724 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9725 });
9726 }
9727 }
9728
9729 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9730 s.select_anchor_ranges(cursor_positions)
9731 });
9732 });
9733 }
9734
9735 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9737 self.join_lines_impl(true, window, cx);
9738 }
9739
9740 pub fn sort_lines_case_sensitive(
9741 &mut self,
9742 _: &SortLinesCaseSensitive,
9743 window: &mut Window,
9744 cx: &mut Context<Self>,
9745 ) {
9746 self.manipulate_lines(window, cx, |lines| lines.sort())
9747 }
9748
9749 pub fn sort_lines_case_insensitive(
9750 &mut self,
9751 _: &SortLinesCaseInsensitive,
9752 window: &mut Window,
9753 cx: &mut Context<Self>,
9754 ) {
9755 self.manipulate_lines(window, cx, |lines| {
9756 lines.sort_by_key(|line| line.to_lowercase())
9757 })
9758 }
9759
9760 pub fn unique_lines_case_insensitive(
9761 &mut self,
9762 _: &UniqueLinesCaseInsensitive,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) {
9766 self.manipulate_lines(window, cx, |lines| {
9767 let mut seen = HashSet::default();
9768 lines.retain(|line| seen.insert(line.to_lowercase()));
9769 })
9770 }
9771
9772 pub fn unique_lines_case_sensitive(
9773 &mut self,
9774 _: &UniqueLinesCaseSensitive,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) {
9778 self.manipulate_lines(window, cx, |lines| {
9779 let mut seen = HashSet::default();
9780 lines.retain(|line| seen.insert(*line));
9781 })
9782 }
9783
9784 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9785 let Some(project) = self.project.clone() else {
9786 return;
9787 };
9788 self.reload(project, window, cx)
9789 .detach_and_notify_err(window, cx);
9790 }
9791
9792 pub fn restore_file(
9793 &mut self,
9794 _: &::git::RestoreFile,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) {
9798 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9799 let mut buffer_ids = HashSet::default();
9800 let snapshot = self.buffer().read(cx).snapshot(cx);
9801 for selection in self.selections.all::<usize>(cx) {
9802 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9803 }
9804
9805 let buffer = self.buffer().read(cx);
9806 let ranges = buffer_ids
9807 .into_iter()
9808 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9809 .collect::<Vec<_>>();
9810
9811 self.restore_hunks_in_ranges(ranges, window, cx);
9812 }
9813
9814 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9815 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9816 let selections = self
9817 .selections
9818 .all(cx)
9819 .into_iter()
9820 .map(|s| s.range())
9821 .collect();
9822 self.restore_hunks_in_ranges(selections, window, cx);
9823 }
9824
9825 pub fn restore_hunks_in_ranges(
9826 &mut self,
9827 ranges: Vec<Range<Point>>,
9828 window: &mut Window,
9829 cx: &mut Context<Editor>,
9830 ) {
9831 let mut revert_changes = HashMap::default();
9832 let chunk_by = self
9833 .snapshot(window, cx)
9834 .hunks_for_ranges(ranges)
9835 .into_iter()
9836 .chunk_by(|hunk| hunk.buffer_id);
9837 for (buffer_id, hunks) in &chunk_by {
9838 let hunks = hunks.collect::<Vec<_>>();
9839 for hunk in &hunks {
9840 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9841 }
9842 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9843 }
9844 drop(chunk_by);
9845 if !revert_changes.is_empty() {
9846 self.transact(window, cx, |editor, window, cx| {
9847 editor.restore(revert_changes, window, cx);
9848 });
9849 }
9850 }
9851
9852 pub fn open_active_item_in_terminal(
9853 &mut self,
9854 _: &OpenInTerminal,
9855 window: &mut Window,
9856 cx: &mut Context<Self>,
9857 ) {
9858 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9859 let project_path = buffer.read(cx).project_path(cx)?;
9860 let project = self.project.as_ref()?.read(cx);
9861 let entry = project.entry_for_path(&project_path, cx)?;
9862 let parent = match &entry.canonical_path {
9863 Some(canonical_path) => canonical_path.to_path_buf(),
9864 None => project.absolute_path(&project_path, cx)?,
9865 }
9866 .parent()?
9867 .to_path_buf();
9868 Some(parent)
9869 }) {
9870 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9871 }
9872 }
9873
9874 fn set_breakpoint_context_menu(
9875 &mut self,
9876 display_row: DisplayRow,
9877 position: Option<Anchor>,
9878 clicked_point: gpui::Point<Pixels>,
9879 window: &mut Window,
9880 cx: &mut Context<Self>,
9881 ) {
9882 if !cx.has_flag::<DebuggerFeatureFlag>() {
9883 return;
9884 }
9885 let source = self
9886 .buffer
9887 .read(cx)
9888 .snapshot(cx)
9889 .anchor_before(Point::new(display_row.0, 0u32));
9890
9891 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9892
9893 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9894 self,
9895 source,
9896 clicked_point,
9897 context_menu,
9898 window,
9899 cx,
9900 );
9901 }
9902
9903 fn add_edit_breakpoint_block(
9904 &mut self,
9905 anchor: Anchor,
9906 breakpoint: &Breakpoint,
9907 edit_action: BreakpointPromptEditAction,
9908 window: &mut Window,
9909 cx: &mut Context<Self>,
9910 ) {
9911 let weak_editor = cx.weak_entity();
9912 let bp_prompt = cx.new(|cx| {
9913 BreakpointPromptEditor::new(
9914 weak_editor,
9915 anchor,
9916 breakpoint.clone(),
9917 edit_action,
9918 window,
9919 cx,
9920 )
9921 });
9922
9923 let height = bp_prompt.update(cx, |this, cx| {
9924 this.prompt
9925 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9926 });
9927 let cloned_prompt = bp_prompt.clone();
9928 let blocks = vec![BlockProperties {
9929 style: BlockStyle::Sticky,
9930 placement: BlockPlacement::Above(anchor),
9931 height: Some(height),
9932 render: Arc::new(move |cx| {
9933 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9934 cloned_prompt.clone().into_any_element()
9935 }),
9936 priority: 0,
9937 render_in_minimap: true,
9938 }];
9939
9940 let focus_handle = bp_prompt.focus_handle(cx);
9941 window.focus(&focus_handle);
9942
9943 let block_ids = self.insert_blocks(blocks, None, cx);
9944 bp_prompt.update(cx, |prompt, _| {
9945 prompt.add_block_ids(block_ids);
9946 });
9947 }
9948
9949 pub(crate) fn breakpoint_at_row(
9950 &self,
9951 row: u32,
9952 window: &mut Window,
9953 cx: &mut Context<Self>,
9954 ) -> Option<(Anchor, Breakpoint)> {
9955 let snapshot = self.snapshot(window, cx);
9956 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9957
9958 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9959 }
9960
9961 pub(crate) fn breakpoint_at_anchor(
9962 &self,
9963 breakpoint_position: Anchor,
9964 snapshot: &EditorSnapshot,
9965 cx: &mut Context<Self>,
9966 ) -> Option<(Anchor, Breakpoint)> {
9967 let project = self.project.clone()?;
9968
9969 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9970 snapshot
9971 .buffer_snapshot
9972 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9973 })?;
9974
9975 let enclosing_excerpt = breakpoint_position.excerpt_id;
9976 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9977 let buffer_snapshot = buffer.read(cx).snapshot();
9978
9979 let row = buffer_snapshot
9980 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9981 .row;
9982
9983 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9984 let anchor_end = snapshot
9985 .buffer_snapshot
9986 .anchor_after(Point::new(row, line_len));
9987
9988 let bp = self
9989 .breakpoint_store
9990 .as_ref()?
9991 .read_with(cx, |breakpoint_store, cx| {
9992 breakpoint_store
9993 .breakpoints(
9994 &buffer,
9995 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9996 &buffer_snapshot,
9997 cx,
9998 )
9999 .next()
10000 .and_then(|(bp, _)| {
10001 let breakpoint_row = buffer_snapshot
10002 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10003 .row;
10004
10005 if breakpoint_row == row {
10006 snapshot
10007 .buffer_snapshot
10008 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10009 .map(|position| (position, bp.bp.clone()))
10010 } else {
10011 None
10012 }
10013 })
10014 });
10015 bp
10016 }
10017
10018 pub fn edit_log_breakpoint(
10019 &mut self,
10020 _: &EditLogBreakpoint,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) {
10024 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10025 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10026 message: None,
10027 state: BreakpointState::Enabled,
10028 condition: None,
10029 hit_condition: None,
10030 });
10031
10032 self.add_edit_breakpoint_block(
10033 anchor,
10034 &breakpoint,
10035 BreakpointPromptEditAction::Log,
10036 window,
10037 cx,
10038 );
10039 }
10040 }
10041
10042 fn breakpoints_at_cursors(
10043 &self,
10044 window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10047 let snapshot = self.snapshot(window, cx);
10048 let cursors = self
10049 .selections
10050 .disjoint_anchors()
10051 .into_iter()
10052 .map(|selection| {
10053 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10054
10055 let breakpoint_position = self
10056 .breakpoint_at_row(cursor_position.row, window, cx)
10057 .map(|bp| bp.0)
10058 .unwrap_or_else(|| {
10059 snapshot
10060 .display_snapshot
10061 .buffer_snapshot
10062 .anchor_after(Point::new(cursor_position.row, 0))
10063 });
10064
10065 let breakpoint = self
10066 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10067 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10068
10069 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10070 })
10071 // 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.
10072 .collect::<HashMap<Anchor, _>>();
10073
10074 cursors.into_iter().collect()
10075 }
10076
10077 pub fn enable_breakpoint(
10078 &mut self,
10079 _: &crate::actions::EnableBreakpoint,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10084 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10085 continue;
10086 };
10087 self.edit_breakpoint_at_anchor(
10088 anchor,
10089 breakpoint,
10090 BreakpointEditAction::InvertState,
10091 cx,
10092 );
10093 }
10094 }
10095
10096 pub fn disable_breakpoint(
10097 &mut self,
10098 _: &crate::actions::DisableBreakpoint,
10099 window: &mut Window,
10100 cx: &mut Context<Self>,
10101 ) {
10102 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10103 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10104 continue;
10105 };
10106 self.edit_breakpoint_at_anchor(
10107 anchor,
10108 breakpoint,
10109 BreakpointEditAction::InvertState,
10110 cx,
10111 );
10112 }
10113 }
10114
10115 pub fn toggle_breakpoint(
10116 &mut self,
10117 _: &crate::actions::ToggleBreakpoint,
10118 window: &mut Window,
10119 cx: &mut Context<Self>,
10120 ) {
10121 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10122 if let Some(breakpoint) = breakpoint {
10123 self.edit_breakpoint_at_anchor(
10124 anchor,
10125 breakpoint,
10126 BreakpointEditAction::Toggle,
10127 cx,
10128 );
10129 } else {
10130 self.edit_breakpoint_at_anchor(
10131 anchor,
10132 Breakpoint::new_standard(),
10133 BreakpointEditAction::Toggle,
10134 cx,
10135 );
10136 }
10137 }
10138 }
10139
10140 pub fn edit_breakpoint_at_anchor(
10141 &mut self,
10142 breakpoint_position: Anchor,
10143 breakpoint: Breakpoint,
10144 edit_action: BreakpointEditAction,
10145 cx: &mut Context<Self>,
10146 ) {
10147 let Some(breakpoint_store) = &self.breakpoint_store else {
10148 return;
10149 };
10150
10151 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10152 if breakpoint_position == Anchor::min() {
10153 self.buffer()
10154 .read(cx)
10155 .excerpt_buffer_ids()
10156 .into_iter()
10157 .next()
10158 } else {
10159 None
10160 }
10161 }) else {
10162 return;
10163 };
10164
10165 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10166 return;
10167 };
10168
10169 breakpoint_store.update(cx, |breakpoint_store, cx| {
10170 breakpoint_store.toggle_breakpoint(
10171 buffer,
10172 BreakpointWithPosition {
10173 position: breakpoint_position.text_anchor,
10174 bp: breakpoint,
10175 },
10176 edit_action,
10177 cx,
10178 );
10179 });
10180
10181 cx.notify();
10182 }
10183
10184 #[cfg(any(test, feature = "test-support"))]
10185 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10186 self.breakpoint_store.clone()
10187 }
10188
10189 pub fn prepare_restore_change(
10190 &self,
10191 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10192 hunk: &MultiBufferDiffHunk,
10193 cx: &mut App,
10194 ) -> Option<()> {
10195 if hunk.is_created_file() {
10196 return None;
10197 }
10198 let buffer = self.buffer.read(cx);
10199 let diff = buffer.diff_for(hunk.buffer_id)?;
10200 let buffer = buffer.buffer(hunk.buffer_id)?;
10201 let buffer = buffer.read(cx);
10202 let original_text = diff
10203 .read(cx)
10204 .base_text()
10205 .as_rope()
10206 .slice(hunk.diff_base_byte_range.clone());
10207 let buffer_snapshot = buffer.snapshot();
10208 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10209 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10210 probe
10211 .0
10212 .start
10213 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10214 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10215 }) {
10216 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10217 Some(())
10218 } else {
10219 None
10220 }
10221 }
10222
10223 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10224 self.manipulate_lines(window, cx, |lines| lines.reverse())
10225 }
10226
10227 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10228 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10229 }
10230
10231 fn manipulate_lines<Fn>(
10232 &mut self,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 mut callback: Fn,
10236 ) where
10237 Fn: FnMut(&mut Vec<&str>),
10238 {
10239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10240
10241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10242 let buffer = self.buffer.read(cx).snapshot(cx);
10243
10244 let mut edits = Vec::new();
10245
10246 let selections = self.selections.all::<Point>(cx);
10247 let mut selections = selections.iter().peekable();
10248 let mut contiguous_row_selections = Vec::new();
10249 let mut new_selections = Vec::new();
10250 let mut added_lines = 0;
10251 let mut removed_lines = 0;
10252
10253 while let Some(selection) = selections.next() {
10254 let (start_row, end_row) = consume_contiguous_rows(
10255 &mut contiguous_row_selections,
10256 selection,
10257 &display_map,
10258 &mut selections,
10259 );
10260
10261 let start_point = Point::new(start_row.0, 0);
10262 let end_point = Point::new(
10263 end_row.previous_row().0,
10264 buffer.line_len(end_row.previous_row()),
10265 );
10266 let text = buffer
10267 .text_for_range(start_point..end_point)
10268 .collect::<String>();
10269
10270 let mut lines = text.split('\n').collect_vec();
10271
10272 let lines_before = lines.len();
10273 callback(&mut lines);
10274 let lines_after = lines.len();
10275
10276 edits.push((start_point..end_point, lines.join("\n")));
10277
10278 // Selections must change based on added and removed line count
10279 let start_row =
10280 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10281 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10282 new_selections.push(Selection {
10283 id: selection.id,
10284 start: start_row,
10285 end: end_row,
10286 goal: SelectionGoal::None,
10287 reversed: selection.reversed,
10288 });
10289
10290 if lines_after > lines_before {
10291 added_lines += lines_after - lines_before;
10292 } else if lines_before > lines_after {
10293 removed_lines += lines_before - lines_after;
10294 }
10295 }
10296
10297 self.transact(window, cx, |this, window, cx| {
10298 let buffer = this.buffer.update(cx, |buffer, cx| {
10299 buffer.edit(edits, None, cx);
10300 buffer.snapshot(cx)
10301 });
10302
10303 // Recalculate offsets on newly edited buffer
10304 let new_selections = new_selections
10305 .iter()
10306 .map(|s| {
10307 let start_point = Point::new(s.start.0, 0);
10308 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10309 Selection {
10310 id: s.id,
10311 start: buffer.point_to_offset(start_point),
10312 end: buffer.point_to_offset(end_point),
10313 goal: s.goal,
10314 reversed: s.reversed,
10315 }
10316 })
10317 .collect();
10318
10319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10320 s.select(new_selections);
10321 });
10322
10323 this.request_autoscroll(Autoscroll::fit(), cx);
10324 });
10325 }
10326
10327 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10328 self.manipulate_text(window, cx, |text| {
10329 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10330 if has_upper_case_characters {
10331 text.to_lowercase()
10332 } else {
10333 text.to_uppercase()
10334 }
10335 })
10336 }
10337
10338 pub fn convert_to_upper_case(
10339 &mut self,
10340 _: &ConvertToUpperCase,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 self.manipulate_text(window, cx, |text| text.to_uppercase())
10345 }
10346
10347 pub fn convert_to_lower_case(
10348 &mut self,
10349 _: &ConvertToLowerCase,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 self.manipulate_text(window, cx, |text| text.to_lowercase())
10354 }
10355
10356 pub fn convert_to_title_case(
10357 &mut self,
10358 _: &ConvertToTitleCase,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 self.manipulate_text(window, cx, |text| {
10363 text.split('\n')
10364 .map(|line| line.to_case(Case::Title))
10365 .join("\n")
10366 })
10367 }
10368
10369 pub fn convert_to_snake_case(
10370 &mut self,
10371 _: &ConvertToSnakeCase,
10372 window: &mut Window,
10373 cx: &mut Context<Self>,
10374 ) {
10375 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10376 }
10377
10378 pub fn convert_to_kebab_case(
10379 &mut self,
10380 _: &ConvertToKebabCase,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10385 }
10386
10387 pub fn convert_to_upper_camel_case(
10388 &mut self,
10389 _: &ConvertToUpperCamelCase,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.manipulate_text(window, cx, |text| {
10394 text.split('\n')
10395 .map(|line| line.to_case(Case::UpperCamel))
10396 .join("\n")
10397 })
10398 }
10399
10400 pub fn convert_to_lower_camel_case(
10401 &mut self,
10402 _: &ConvertToLowerCamelCase,
10403 window: &mut Window,
10404 cx: &mut Context<Self>,
10405 ) {
10406 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10407 }
10408
10409 pub fn convert_to_opposite_case(
10410 &mut self,
10411 _: &ConvertToOppositeCase,
10412 window: &mut Window,
10413 cx: &mut Context<Self>,
10414 ) {
10415 self.manipulate_text(window, cx, |text| {
10416 text.chars()
10417 .fold(String::with_capacity(text.len()), |mut t, c| {
10418 if c.is_uppercase() {
10419 t.extend(c.to_lowercase());
10420 } else {
10421 t.extend(c.to_uppercase());
10422 }
10423 t
10424 })
10425 })
10426 }
10427
10428 pub fn convert_to_rot13(
10429 &mut self,
10430 _: &ConvertToRot13,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.manipulate_text(window, cx, |text| {
10435 text.chars()
10436 .map(|c| match c {
10437 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10438 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10439 _ => c,
10440 })
10441 .collect()
10442 })
10443 }
10444
10445 pub fn convert_to_rot47(
10446 &mut self,
10447 _: &ConvertToRot47,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.manipulate_text(window, cx, |text| {
10452 text.chars()
10453 .map(|c| {
10454 let code_point = c as u32;
10455 if code_point >= 33 && code_point <= 126 {
10456 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10457 }
10458 c
10459 })
10460 .collect()
10461 })
10462 }
10463
10464 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10465 where
10466 Fn: FnMut(&str) -> String,
10467 {
10468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10469 let buffer = self.buffer.read(cx).snapshot(cx);
10470
10471 let mut new_selections = Vec::new();
10472 let mut edits = Vec::new();
10473 let mut selection_adjustment = 0i32;
10474
10475 for selection in self.selections.all::<usize>(cx) {
10476 let selection_is_empty = selection.is_empty();
10477
10478 let (start, end) = if selection_is_empty {
10479 let word_range = movement::surrounding_word(
10480 &display_map,
10481 selection.start.to_display_point(&display_map),
10482 );
10483 let start = word_range.start.to_offset(&display_map, Bias::Left);
10484 let end = word_range.end.to_offset(&display_map, Bias::Left);
10485 (start, end)
10486 } else {
10487 (selection.start, selection.end)
10488 };
10489
10490 let text = buffer.text_for_range(start..end).collect::<String>();
10491 let old_length = text.len() as i32;
10492 let text = callback(&text);
10493
10494 new_selections.push(Selection {
10495 start: (start as i32 - selection_adjustment) as usize,
10496 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10497 goal: SelectionGoal::None,
10498 ..selection
10499 });
10500
10501 selection_adjustment += old_length - text.len() as i32;
10502
10503 edits.push((start..end, text));
10504 }
10505
10506 self.transact(window, cx, |this, window, cx| {
10507 this.buffer.update(cx, |buffer, cx| {
10508 buffer.edit(edits, None, cx);
10509 });
10510
10511 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10512 s.select(new_selections);
10513 });
10514
10515 this.request_autoscroll(Autoscroll::fit(), cx);
10516 });
10517 }
10518
10519 pub fn duplicate(
10520 &mut self,
10521 upwards: bool,
10522 whole_lines: bool,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10527
10528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10529 let buffer = &display_map.buffer_snapshot;
10530 let selections = self.selections.all::<Point>(cx);
10531
10532 let mut edits = Vec::new();
10533 let mut selections_iter = selections.iter().peekable();
10534 while let Some(selection) = selections_iter.next() {
10535 let mut rows = selection.spanned_rows(false, &display_map);
10536 // duplicate line-wise
10537 if whole_lines || selection.start == selection.end {
10538 // Avoid duplicating the same lines twice.
10539 while let Some(next_selection) = selections_iter.peek() {
10540 let next_rows = next_selection.spanned_rows(false, &display_map);
10541 if next_rows.start < rows.end {
10542 rows.end = next_rows.end;
10543 selections_iter.next().unwrap();
10544 } else {
10545 break;
10546 }
10547 }
10548
10549 // Copy the text from the selected row region and splice it either at the start
10550 // or end of the region.
10551 let start = Point::new(rows.start.0, 0);
10552 let end = Point::new(
10553 rows.end.previous_row().0,
10554 buffer.line_len(rows.end.previous_row()),
10555 );
10556 let text = buffer
10557 .text_for_range(start..end)
10558 .chain(Some("\n"))
10559 .collect::<String>();
10560 let insert_location = if upwards {
10561 Point::new(rows.end.0, 0)
10562 } else {
10563 start
10564 };
10565 edits.push((insert_location..insert_location, text));
10566 } else {
10567 // duplicate character-wise
10568 let start = selection.start;
10569 let end = selection.end;
10570 let text = buffer.text_for_range(start..end).collect::<String>();
10571 edits.push((selection.end..selection.end, text));
10572 }
10573 }
10574
10575 self.transact(window, cx, |this, _, cx| {
10576 this.buffer.update(cx, |buffer, cx| {
10577 buffer.edit(edits, None, cx);
10578 });
10579
10580 this.request_autoscroll(Autoscroll::fit(), cx);
10581 });
10582 }
10583
10584 pub fn duplicate_line_up(
10585 &mut self,
10586 _: &DuplicateLineUp,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) {
10590 self.duplicate(true, true, window, cx);
10591 }
10592
10593 pub fn duplicate_line_down(
10594 &mut self,
10595 _: &DuplicateLineDown,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 self.duplicate(false, true, window, cx);
10600 }
10601
10602 pub fn duplicate_selection(
10603 &mut self,
10604 _: &DuplicateSelection,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.duplicate(false, false, window, cx);
10609 }
10610
10611 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10612 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10613
10614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10615 let buffer = self.buffer.read(cx).snapshot(cx);
10616
10617 let mut edits = Vec::new();
10618 let mut unfold_ranges = Vec::new();
10619 let mut refold_creases = Vec::new();
10620
10621 let selections = self.selections.all::<Point>(cx);
10622 let mut selections = selections.iter().peekable();
10623 let mut contiguous_row_selections = Vec::new();
10624 let mut new_selections = Vec::new();
10625
10626 while let Some(selection) = selections.next() {
10627 // Find all the selections that span a contiguous row range
10628 let (start_row, end_row) = consume_contiguous_rows(
10629 &mut contiguous_row_selections,
10630 selection,
10631 &display_map,
10632 &mut selections,
10633 );
10634
10635 // Move the text spanned by the row range to be before the line preceding the row range
10636 if start_row.0 > 0 {
10637 let range_to_move = Point::new(
10638 start_row.previous_row().0,
10639 buffer.line_len(start_row.previous_row()),
10640 )
10641 ..Point::new(
10642 end_row.previous_row().0,
10643 buffer.line_len(end_row.previous_row()),
10644 );
10645 let insertion_point = display_map
10646 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10647 .0;
10648
10649 // Don't move lines across excerpts
10650 if buffer
10651 .excerpt_containing(insertion_point..range_to_move.end)
10652 .is_some()
10653 {
10654 let text = buffer
10655 .text_for_range(range_to_move.clone())
10656 .flat_map(|s| s.chars())
10657 .skip(1)
10658 .chain(['\n'])
10659 .collect::<String>();
10660
10661 edits.push((
10662 buffer.anchor_after(range_to_move.start)
10663 ..buffer.anchor_before(range_to_move.end),
10664 String::new(),
10665 ));
10666 let insertion_anchor = buffer.anchor_after(insertion_point);
10667 edits.push((insertion_anchor..insertion_anchor, text));
10668
10669 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10670
10671 // Move selections up
10672 new_selections.extend(contiguous_row_selections.drain(..).map(
10673 |mut selection| {
10674 selection.start.row -= row_delta;
10675 selection.end.row -= row_delta;
10676 selection
10677 },
10678 ));
10679
10680 // Move folds up
10681 unfold_ranges.push(range_to_move.clone());
10682 for fold in display_map.folds_in_range(
10683 buffer.anchor_before(range_to_move.start)
10684 ..buffer.anchor_after(range_to_move.end),
10685 ) {
10686 let mut start = fold.range.start.to_point(&buffer);
10687 let mut end = fold.range.end.to_point(&buffer);
10688 start.row -= row_delta;
10689 end.row -= row_delta;
10690 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10691 }
10692 }
10693 }
10694
10695 // If we didn't move line(s), preserve the existing selections
10696 new_selections.append(&mut contiguous_row_selections);
10697 }
10698
10699 self.transact(window, cx, |this, window, cx| {
10700 this.unfold_ranges(&unfold_ranges, true, true, cx);
10701 this.buffer.update(cx, |buffer, cx| {
10702 for (range, text) in edits {
10703 buffer.edit([(range, text)], None, cx);
10704 }
10705 });
10706 this.fold_creases(refold_creases, true, window, cx);
10707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10708 s.select(new_selections);
10709 })
10710 });
10711 }
10712
10713 pub fn move_line_down(
10714 &mut self,
10715 _: &MoveLineDown,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10720
10721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10722 let buffer = self.buffer.read(cx).snapshot(cx);
10723
10724 let mut edits = Vec::new();
10725 let mut unfold_ranges = Vec::new();
10726 let mut refold_creases = Vec::new();
10727
10728 let selections = self.selections.all::<Point>(cx);
10729 let mut selections = selections.iter().peekable();
10730 let mut contiguous_row_selections = Vec::new();
10731 let mut new_selections = Vec::new();
10732
10733 while let Some(selection) = selections.next() {
10734 // Find all the selections that span a contiguous row range
10735 let (start_row, end_row) = consume_contiguous_rows(
10736 &mut contiguous_row_selections,
10737 selection,
10738 &display_map,
10739 &mut selections,
10740 );
10741
10742 // Move the text spanned by the row range to be after the last line of the row range
10743 if end_row.0 <= buffer.max_point().row {
10744 let range_to_move =
10745 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10746 let insertion_point = display_map
10747 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10748 .0;
10749
10750 // Don't move lines across excerpt boundaries
10751 if buffer
10752 .excerpt_containing(range_to_move.start..insertion_point)
10753 .is_some()
10754 {
10755 let mut text = String::from("\n");
10756 text.extend(buffer.text_for_range(range_to_move.clone()));
10757 text.pop(); // Drop trailing newline
10758 edits.push((
10759 buffer.anchor_after(range_to_move.start)
10760 ..buffer.anchor_before(range_to_move.end),
10761 String::new(),
10762 ));
10763 let insertion_anchor = buffer.anchor_after(insertion_point);
10764 edits.push((insertion_anchor..insertion_anchor, text));
10765
10766 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10767
10768 // Move selections down
10769 new_selections.extend(contiguous_row_selections.drain(..).map(
10770 |mut selection| {
10771 selection.start.row += row_delta;
10772 selection.end.row += row_delta;
10773 selection
10774 },
10775 ));
10776
10777 // Move folds down
10778 unfold_ranges.push(range_to_move.clone());
10779 for fold in display_map.folds_in_range(
10780 buffer.anchor_before(range_to_move.start)
10781 ..buffer.anchor_after(range_to_move.end),
10782 ) {
10783 let mut start = fold.range.start.to_point(&buffer);
10784 let mut end = fold.range.end.to_point(&buffer);
10785 start.row += row_delta;
10786 end.row += row_delta;
10787 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10788 }
10789 }
10790 }
10791
10792 // If we didn't move line(s), preserve the existing selections
10793 new_selections.append(&mut contiguous_row_selections);
10794 }
10795
10796 self.transact(window, cx, |this, window, cx| {
10797 this.unfold_ranges(&unfold_ranges, true, true, cx);
10798 this.buffer.update(cx, |buffer, cx| {
10799 for (range, text) in edits {
10800 buffer.edit([(range, text)], None, cx);
10801 }
10802 });
10803 this.fold_creases(refold_creases, true, window, cx);
10804 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10805 s.select(new_selections)
10806 });
10807 });
10808 }
10809
10810 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10811 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10812 let text_layout_details = &self.text_layout_details(window);
10813 self.transact(window, cx, |this, window, cx| {
10814 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10815 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10816 s.move_with(|display_map, selection| {
10817 if !selection.is_empty() {
10818 return;
10819 }
10820
10821 let mut head = selection.head();
10822 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10823 if head.column() == display_map.line_len(head.row()) {
10824 transpose_offset = display_map
10825 .buffer_snapshot
10826 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10827 }
10828
10829 if transpose_offset == 0 {
10830 return;
10831 }
10832
10833 *head.column_mut() += 1;
10834 head = display_map.clip_point(head, Bias::Right);
10835 let goal = SelectionGoal::HorizontalPosition(
10836 display_map
10837 .x_for_display_point(head, text_layout_details)
10838 .into(),
10839 );
10840 selection.collapse_to(head, goal);
10841
10842 let transpose_start = display_map
10843 .buffer_snapshot
10844 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10845 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10846 let transpose_end = display_map
10847 .buffer_snapshot
10848 .clip_offset(transpose_offset + 1, Bias::Right);
10849 if let Some(ch) =
10850 display_map.buffer_snapshot.chars_at(transpose_start).next()
10851 {
10852 edits.push((transpose_start..transpose_offset, String::new()));
10853 edits.push((transpose_end..transpose_end, ch.to_string()));
10854 }
10855 }
10856 });
10857 edits
10858 });
10859 this.buffer
10860 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10861 let selections = this.selections.all::<usize>(cx);
10862 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10863 s.select(selections);
10864 });
10865 });
10866 }
10867
10868 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10869 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10870 self.rewrap_impl(RewrapOptions::default(), cx)
10871 }
10872
10873 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10874 let buffer = self.buffer.read(cx).snapshot(cx);
10875 let selections = self.selections.all::<Point>(cx);
10876 let mut selections = selections.iter().peekable();
10877
10878 let mut edits = Vec::new();
10879 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10880
10881 while let Some(selection) = selections.next() {
10882 let mut start_row = selection.start.row;
10883 let mut end_row = selection.end.row;
10884
10885 // Skip selections that overlap with a range that has already been rewrapped.
10886 let selection_range = start_row..end_row;
10887 if rewrapped_row_ranges
10888 .iter()
10889 .any(|range| range.overlaps(&selection_range))
10890 {
10891 continue;
10892 }
10893
10894 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10895
10896 // Since not all lines in the selection may be at the same indent
10897 // level, choose the indent size that is the most common between all
10898 // of the lines.
10899 //
10900 // If there is a tie, we use the deepest indent.
10901 let (indent_size, indent_end) = {
10902 let mut indent_size_occurrences = HashMap::default();
10903 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10904
10905 for row in start_row..=end_row {
10906 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10907 rows_by_indent_size.entry(indent).or_default().push(row);
10908 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10909 }
10910
10911 let indent_size = indent_size_occurrences
10912 .into_iter()
10913 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10914 .map(|(indent, _)| indent)
10915 .unwrap_or_default();
10916 let row = rows_by_indent_size[&indent_size][0];
10917 let indent_end = Point::new(row, indent_size.len);
10918
10919 (indent_size, indent_end)
10920 };
10921
10922 let mut line_prefix = indent_size.chars().collect::<String>();
10923
10924 let mut inside_comment = false;
10925 if let Some(comment_prefix) =
10926 buffer
10927 .language_scope_at(selection.head())
10928 .and_then(|language| {
10929 language
10930 .line_comment_prefixes()
10931 .iter()
10932 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10933 .cloned()
10934 })
10935 {
10936 line_prefix.push_str(&comment_prefix);
10937 inside_comment = true;
10938 }
10939
10940 let language_settings = buffer.language_settings_at(selection.head(), cx);
10941 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10942 RewrapBehavior::InComments => inside_comment,
10943 RewrapBehavior::InSelections => !selection.is_empty(),
10944 RewrapBehavior::Anywhere => true,
10945 };
10946
10947 let should_rewrap = options.override_language_settings
10948 || allow_rewrap_based_on_language
10949 || self.hard_wrap.is_some();
10950 if !should_rewrap {
10951 continue;
10952 }
10953
10954 if selection.is_empty() {
10955 'expand_upwards: while start_row > 0 {
10956 let prev_row = start_row - 1;
10957 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10958 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10959 {
10960 start_row = prev_row;
10961 } else {
10962 break 'expand_upwards;
10963 }
10964 }
10965
10966 'expand_downwards: while end_row < buffer.max_point().row {
10967 let next_row = end_row + 1;
10968 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10969 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10970 {
10971 end_row = next_row;
10972 } else {
10973 break 'expand_downwards;
10974 }
10975 }
10976 }
10977
10978 let start = Point::new(start_row, 0);
10979 let start_offset = start.to_offset(&buffer);
10980 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10981 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10982 let Some(lines_without_prefixes) = selection_text
10983 .lines()
10984 .map(|line| {
10985 line.strip_prefix(&line_prefix)
10986 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10987 .with_context(|| {
10988 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10989 })
10990 })
10991 .collect::<Result<Vec<_>, _>>()
10992 .log_err()
10993 else {
10994 continue;
10995 };
10996
10997 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10998 buffer
10999 .language_settings_at(Point::new(start_row, 0), cx)
11000 .preferred_line_length as usize
11001 });
11002 let wrapped_text = wrap_with_prefix(
11003 line_prefix,
11004 lines_without_prefixes.join("\n"),
11005 wrap_column,
11006 tab_size,
11007 options.preserve_existing_whitespace,
11008 );
11009
11010 // TODO: should always use char-based diff while still supporting cursor behavior that
11011 // matches vim.
11012 let mut diff_options = DiffOptions::default();
11013 if options.override_language_settings {
11014 diff_options.max_word_diff_len = 0;
11015 diff_options.max_word_diff_line_count = 0;
11016 } else {
11017 diff_options.max_word_diff_len = usize::MAX;
11018 diff_options.max_word_diff_line_count = usize::MAX;
11019 }
11020
11021 for (old_range, new_text) in
11022 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11023 {
11024 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11025 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11026 edits.push((edit_start..edit_end, new_text));
11027 }
11028
11029 rewrapped_row_ranges.push(start_row..=end_row);
11030 }
11031
11032 self.buffer
11033 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11034 }
11035
11036 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11037 let mut text = String::new();
11038 let buffer = self.buffer.read(cx).snapshot(cx);
11039 let mut selections = self.selections.all::<Point>(cx);
11040 let mut clipboard_selections = Vec::with_capacity(selections.len());
11041 {
11042 let max_point = buffer.max_point();
11043 let mut is_first = true;
11044 for selection in &mut selections {
11045 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11046 if is_entire_line {
11047 selection.start = Point::new(selection.start.row, 0);
11048 if !selection.is_empty() && selection.end.column == 0 {
11049 selection.end = cmp::min(max_point, selection.end);
11050 } else {
11051 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11052 }
11053 selection.goal = SelectionGoal::None;
11054 }
11055 if is_first {
11056 is_first = false;
11057 } else {
11058 text += "\n";
11059 }
11060 let mut len = 0;
11061 for chunk in buffer.text_for_range(selection.start..selection.end) {
11062 text.push_str(chunk);
11063 len += chunk.len();
11064 }
11065 clipboard_selections.push(ClipboardSelection {
11066 len,
11067 is_entire_line,
11068 first_line_indent: buffer
11069 .indent_size_for_line(MultiBufferRow(selection.start.row))
11070 .len,
11071 });
11072 }
11073 }
11074
11075 self.transact(window, cx, |this, window, cx| {
11076 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11077 s.select(selections);
11078 });
11079 this.insert("", window, cx);
11080 });
11081 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11082 }
11083
11084 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11085 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11086 let item = self.cut_common(window, cx);
11087 cx.write_to_clipboard(item);
11088 }
11089
11090 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11091 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11092 self.change_selections(None, window, cx, |s| {
11093 s.move_with(|snapshot, sel| {
11094 if sel.is_empty() {
11095 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11096 }
11097 });
11098 });
11099 let item = self.cut_common(window, cx);
11100 cx.set_global(KillRing(item))
11101 }
11102
11103 pub fn kill_ring_yank(
11104 &mut self,
11105 _: &KillRingYank,
11106 window: &mut Window,
11107 cx: &mut Context<Self>,
11108 ) {
11109 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11110 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11111 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11112 (kill_ring.text().to_string(), kill_ring.metadata_json())
11113 } else {
11114 return;
11115 }
11116 } else {
11117 return;
11118 };
11119 self.do_paste(&text, metadata, false, window, cx);
11120 }
11121
11122 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11123 self.do_copy(true, cx);
11124 }
11125
11126 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11127 self.do_copy(false, cx);
11128 }
11129
11130 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11131 let selections = self.selections.all::<Point>(cx);
11132 let buffer = self.buffer.read(cx).read(cx);
11133 let mut text = String::new();
11134
11135 let mut clipboard_selections = Vec::with_capacity(selections.len());
11136 {
11137 let max_point = buffer.max_point();
11138 let mut is_first = true;
11139 for selection in &selections {
11140 let mut start = selection.start;
11141 let mut end = selection.end;
11142 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11143 if is_entire_line {
11144 start = Point::new(start.row, 0);
11145 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11146 }
11147
11148 let mut trimmed_selections = Vec::new();
11149 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11150 let row = MultiBufferRow(start.row);
11151 let first_indent = buffer.indent_size_for_line(row);
11152 if first_indent.len == 0 || start.column > first_indent.len {
11153 trimmed_selections.push(start..end);
11154 } else {
11155 trimmed_selections.push(
11156 Point::new(row.0, first_indent.len)
11157 ..Point::new(row.0, buffer.line_len(row)),
11158 );
11159 for row in start.row + 1..=end.row {
11160 let mut line_len = buffer.line_len(MultiBufferRow(row));
11161 if row == end.row {
11162 line_len = end.column;
11163 }
11164 if line_len == 0 {
11165 trimmed_selections
11166 .push(Point::new(row, 0)..Point::new(row, line_len));
11167 continue;
11168 }
11169 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11170 if row_indent_size.len >= first_indent.len {
11171 trimmed_selections.push(
11172 Point::new(row, first_indent.len)..Point::new(row, line_len),
11173 );
11174 } else {
11175 trimmed_selections.clear();
11176 trimmed_selections.push(start..end);
11177 break;
11178 }
11179 }
11180 }
11181 } else {
11182 trimmed_selections.push(start..end);
11183 }
11184
11185 for trimmed_range in trimmed_selections {
11186 if is_first {
11187 is_first = false;
11188 } else {
11189 text += "\n";
11190 }
11191 let mut len = 0;
11192 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11193 text.push_str(chunk);
11194 len += chunk.len();
11195 }
11196 clipboard_selections.push(ClipboardSelection {
11197 len,
11198 is_entire_line,
11199 first_line_indent: buffer
11200 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11201 .len,
11202 });
11203 }
11204 }
11205 }
11206
11207 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11208 text,
11209 clipboard_selections,
11210 ));
11211 }
11212
11213 pub fn do_paste(
11214 &mut self,
11215 text: &String,
11216 clipboard_selections: Option<Vec<ClipboardSelection>>,
11217 handle_entire_lines: bool,
11218 window: &mut Window,
11219 cx: &mut Context<Self>,
11220 ) {
11221 if self.read_only(cx) {
11222 return;
11223 }
11224
11225 let clipboard_text = Cow::Borrowed(text);
11226
11227 self.transact(window, cx, |this, window, cx| {
11228 if let Some(mut clipboard_selections) = clipboard_selections {
11229 let old_selections = this.selections.all::<usize>(cx);
11230 let all_selections_were_entire_line =
11231 clipboard_selections.iter().all(|s| s.is_entire_line);
11232 let first_selection_indent_column =
11233 clipboard_selections.first().map(|s| s.first_line_indent);
11234 if clipboard_selections.len() != old_selections.len() {
11235 clipboard_selections.drain(..);
11236 }
11237 let cursor_offset = this.selections.last::<usize>(cx).head();
11238 let mut auto_indent_on_paste = true;
11239
11240 this.buffer.update(cx, |buffer, cx| {
11241 let snapshot = buffer.read(cx);
11242 auto_indent_on_paste = snapshot
11243 .language_settings_at(cursor_offset, cx)
11244 .auto_indent_on_paste;
11245
11246 let mut start_offset = 0;
11247 let mut edits = Vec::new();
11248 let mut original_indent_columns = Vec::new();
11249 for (ix, selection) in old_selections.iter().enumerate() {
11250 let to_insert;
11251 let entire_line;
11252 let original_indent_column;
11253 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11254 let end_offset = start_offset + clipboard_selection.len;
11255 to_insert = &clipboard_text[start_offset..end_offset];
11256 entire_line = clipboard_selection.is_entire_line;
11257 start_offset = end_offset + 1;
11258 original_indent_column = Some(clipboard_selection.first_line_indent);
11259 } else {
11260 to_insert = clipboard_text.as_str();
11261 entire_line = all_selections_were_entire_line;
11262 original_indent_column = first_selection_indent_column
11263 }
11264
11265 // If the corresponding selection was empty when this slice of the
11266 // clipboard text was written, then the entire line containing the
11267 // selection was copied. If this selection is also currently empty,
11268 // then paste the line before the current line of the buffer.
11269 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11270 let column = selection.start.to_point(&snapshot).column as usize;
11271 let line_start = selection.start - column;
11272 line_start..line_start
11273 } else {
11274 selection.range()
11275 };
11276
11277 edits.push((range, to_insert));
11278 original_indent_columns.push(original_indent_column);
11279 }
11280 drop(snapshot);
11281
11282 buffer.edit(
11283 edits,
11284 if auto_indent_on_paste {
11285 Some(AutoindentMode::Block {
11286 original_indent_columns,
11287 })
11288 } else {
11289 None
11290 },
11291 cx,
11292 );
11293 });
11294
11295 let selections = this.selections.all::<usize>(cx);
11296 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11297 s.select(selections)
11298 });
11299 } else {
11300 this.insert(&clipboard_text, window, cx);
11301 }
11302 });
11303 }
11304
11305 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11307 if let Some(item) = cx.read_from_clipboard() {
11308 let entries = item.entries();
11309
11310 match entries.first() {
11311 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11312 // of all the pasted entries.
11313 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11314 .do_paste(
11315 clipboard_string.text(),
11316 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11317 true,
11318 window,
11319 cx,
11320 ),
11321 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11322 }
11323 }
11324 }
11325
11326 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11327 if self.read_only(cx) {
11328 return;
11329 }
11330
11331 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11332
11333 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11334 if let Some((selections, _)) =
11335 self.selection_history.transaction(transaction_id).cloned()
11336 {
11337 self.change_selections(None, window, cx, |s| {
11338 s.select_anchors(selections.to_vec());
11339 });
11340 } else {
11341 log::error!(
11342 "No entry in selection_history found for undo. \
11343 This may correspond to a bug where undo does not update the selection. \
11344 If this is occurring, please add details to \
11345 https://github.com/zed-industries/zed/issues/22692"
11346 );
11347 }
11348 self.request_autoscroll(Autoscroll::fit(), cx);
11349 self.unmark_text(window, cx);
11350 self.refresh_inline_completion(true, false, window, cx);
11351 cx.emit(EditorEvent::Edited { transaction_id });
11352 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11353 }
11354 }
11355
11356 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11357 if self.read_only(cx) {
11358 return;
11359 }
11360
11361 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11362
11363 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11364 if let Some((_, Some(selections))) =
11365 self.selection_history.transaction(transaction_id).cloned()
11366 {
11367 self.change_selections(None, window, cx, |s| {
11368 s.select_anchors(selections.to_vec());
11369 });
11370 } else {
11371 log::error!(
11372 "No entry in selection_history found for redo. \
11373 This may correspond to a bug where undo does not update the selection. \
11374 If this is occurring, please add details to \
11375 https://github.com/zed-industries/zed/issues/22692"
11376 );
11377 }
11378 self.request_autoscroll(Autoscroll::fit(), cx);
11379 self.unmark_text(window, cx);
11380 self.refresh_inline_completion(true, false, window, cx);
11381 cx.emit(EditorEvent::Edited { transaction_id });
11382 }
11383 }
11384
11385 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11386 self.buffer
11387 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11388 }
11389
11390 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11391 self.buffer
11392 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11393 }
11394
11395 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11396 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11397 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11398 s.move_with(|map, selection| {
11399 let cursor = if selection.is_empty() {
11400 movement::left(map, selection.start)
11401 } else {
11402 selection.start
11403 };
11404 selection.collapse_to(cursor, SelectionGoal::None);
11405 });
11406 })
11407 }
11408
11409 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11410 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11411 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11412 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11413 })
11414 }
11415
11416 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11417 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11418 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11419 s.move_with(|map, selection| {
11420 let cursor = if selection.is_empty() {
11421 movement::right(map, selection.end)
11422 } else {
11423 selection.end
11424 };
11425 selection.collapse_to(cursor, SelectionGoal::None)
11426 });
11427 })
11428 }
11429
11430 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11431 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11433 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11434 })
11435 }
11436
11437 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11438 if self.take_rename(true, window, cx).is_some() {
11439 return;
11440 }
11441
11442 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11443 cx.propagate();
11444 return;
11445 }
11446
11447 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11448
11449 let text_layout_details = &self.text_layout_details(window);
11450 let selection_count = self.selections.count();
11451 let first_selection = self.selections.first_anchor();
11452
11453 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11454 s.move_with(|map, selection| {
11455 if !selection.is_empty() {
11456 selection.goal = SelectionGoal::None;
11457 }
11458 let (cursor, goal) = movement::up(
11459 map,
11460 selection.start,
11461 selection.goal,
11462 false,
11463 text_layout_details,
11464 );
11465 selection.collapse_to(cursor, goal);
11466 });
11467 });
11468
11469 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11470 {
11471 cx.propagate();
11472 }
11473 }
11474
11475 pub fn move_up_by_lines(
11476 &mut self,
11477 action: &MoveUpByLines,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 if self.take_rename(true, window, cx).is_some() {
11482 return;
11483 }
11484
11485 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11486 cx.propagate();
11487 return;
11488 }
11489
11490 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11491
11492 let text_layout_details = &self.text_layout_details(window);
11493
11494 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11495 s.move_with(|map, selection| {
11496 if !selection.is_empty() {
11497 selection.goal = SelectionGoal::None;
11498 }
11499 let (cursor, goal) = movement::up_by_rows(
11500 map,
11501 selection.start,
11502 action.lines,
11503 selection.goal,
11504 false,
11505 text_layout_details,
11506 );
11507 selection.collapse_to(cursor, goal);
11508 });
11509 })
11510 }
11511
11512 pub fn move_down_by_lines(
11513 &mut self,
11514 action: &MoveDownByLines,
11515 window: &mut Window,
11516 cx: &mut Context<Self>,
11517 ) {
11518 if self.take_rename(true, window, cx).is_some() {
11519 return;
11520 }
11521
11522 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11523 cx.propagate();
11524 return;
11525 }
11526
11527 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11528
11529 let text_layout_details = &self.text_layout_details(window);
11530
11531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11532 s.move_with(|map, selection| {
11533 if !selection.is_empty() {
11534 selection.goal = SelectionGoal::None;
11535 }
11536 let (cursor, goal) = movement::down_by_rows(
11537 map,
11538 selection.start,
11539 action.lines,
11540 selection.goal,
11541 false,
11542 text_layout_details,
11543 );
11544 selection.collapse_to(cursor, goal);
11545 });
11546 })
11547 }
11548
11549 pub fn select_down_by_lines(
11550 &mut self,
11551 action: &SelectDownByLines,
11552 window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11556 let text_layout_details = &self.text_layout_details(window);
11557 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11558 s.move_heads_with(|map, head, goal| {
11559 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11560 })
11561 })
11562 }
11563
11564 pub fn select_up_by_lines(
11565 &mut self,
11566 action: &SelectUpByLines,
11567 window: &mut Window,
11568 cx: &mut Context<Self>,
11569 ) {
11570 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11571 let text_layout_details = &self.text_layout_details(window);
11572 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11573 s.move_heads_with(|map, head, goal| {
11574 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11575 })
11576 })
11577 }
11578
11579 pub fn select_page_up(
11580 &mut self,
11581 _: &SelectPageUp,
11582 window: &mut Window,
11583 cx: &mut Context<Self>,
11584 ) {
11585 let Some(row_count) = self.visible_row_count() else {
11586 return;
11587 };
11588
11589 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11590
11591 let text_layout_details = &self.text_layout_details(window);
11592
11593 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11594 s.move_heads_with(|map, head, goal| {
11595 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11596 })
11597 })
11598 }
11599
11600 pub fn move_page_up(
11601 &mut self,
11602 action: &MovePageUp,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 if self.take_rename(true, window, cx).is_some() {
11607 return;
11608 }
11609
11610 if self
11611 .context_menu
11612 .borrow_mut()
11613 .as_mut()
11614 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11615 .unwrap_or(false)
11616 {
11617 return;
11618 }
11619
11620 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11621 cx.propagate();
11622 return;
11623 }
11624
11625 let Some(row_count) = self.visible_row_count() else {
11626 return;
11627 };
11628
11629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11630
11631 let autoscroll = if action.center_cursor {
11632 Autoscroll::center()
11633 } else {
11634 Autoscroll::fit()
11635 };
11636
11637 let text_layout_details = &self.text_layout_details(window);
11638
11639 self.change_selections(Some(autoscroll), window, cx, |s| {
11640 s.move_with(|map, selection| {
11641 if !selection.is_empty() {
11642 selection.goal = SelectionGoal::None;
11643 }
11644 let (cursor, goal) = movement::up_by_rows(
11645 map,
11646 selection.end,
11647 row_count,
11648 selection.goal,
11649 false,
11650 text_layout_details,
11651 );
11652 selection.collapse_to(cursor, goal);
11653 });
11654 });
11655 }
11656
11657 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11659 let text_layout_details = &self.text_layout_details(window);
11660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11661 s.move_heads_with(|map, head, goal| {
11662 movement::up(map, head, goal, false, text_layout_details)
11663 })
11664 })
11665 }
11666
11667 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11668 self.take_rename(true, window, cx);
11669
11670 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11671 cx.propagate();
11672 return;
11673 }
11674
11675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11676
11677 let text_layout_details = &self.text_layout_details(window);
11678 let selection_count = self.selections.count();
11679 let first_selection = self.selections.first_anchor();
11680
11681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11682 s.move_with(|map, selection| {
11683 if !selection.is_empty() {
11684 selection.goal = SelectionGoal::None;
11685 }
11686 let (cursor, goal) = movement::down(
11687 map,
11688 selection.end,
11689 selection.goal,
11690 false,
11691 text_layout_details,
11692 );
11693 selection.collapse_to(cursor, goal);
11694 });
11695 });
11696
11697 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11698 {
11699 cx.propagate();
11700 }
11701 }
11702
11703 pub fn select_page_down(
11704 &mut self,
11705 _: &SelectPageDown,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 let Some(row_count) = self.visible_row_count() else {
11710 return;
11711 };
11712
11713 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11714
11715 let text_layout_details = &self.text_layout_details(window);
11716
11717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11718 s.move_heads_with(|map, head, goal| {
11719 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11720 })
11721 })
11722 }
11723
11724 pub fn move_page_down(
11725 &mut self,
11726 action: &MovePageDown,
11727 window: &mut Window,
11728 cx: &mut Context<Self>,
11729 ) {
11730 if self.take_rename(true, window, cx).is_some() {
11731 return;
11732 }
11733
11734 if self
11735 .context_menu
11736 .borrow_mut()
11737 .as_mut()
11738 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11739 .unwrap_or(false)
11740 {
11741 return;
11742 }
11743
11744 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11745 cx.propagate();
11746 return;
11747 }
11748
11749 let Some(row_count) = self.visible_row_count() else {
11750 return;
11751 };
11752
11753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11754
11755 let autoscroll = if action.center_cursor {
11756 Autoscroll::center()
11757 } else {
11758 Autoscroll::fit()
11759 };
11760
11761 let text_layout_details = &self.text_layout_details(window);
11762 self.change_selections(Some(autoscroll), window, cx, |s| {
11763 s.move_with(|map, selection| {
11764 if !selection.is_empty() {
11765 selection.goal = SelectionGoal::None;
11766 }
11767 let (cursor, goal) = movement::down_by_rows(
11768 map,
11769 selection.end,
11770 row_count,
11771 selection.goal,
11772 false,
11773 text_layout_details,
11774 );
11775 selection.collapse_to(cursor, goal);
11776 });
11777 });
11778 }
11779
11780 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11781 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11782 let text_layout_details = &self.text_layout_details(window);
11783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11784 s.move_heads_with(|map, head, goal| {
11785 movement::down(map, head, goal, false, text_layout_details)
11786 })
11787 });
11788 }
11789
11790 pub fn context_menu_first(
11791 &mut self,
11792 _: &ContextMenuFirst,
11793 window: &mut Window,
11794 cx: &mut Context<Self>,
11795 ) {
11796 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11797 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11798 }
11799 }
11800
11801 pub fn context_menu_prev(
11802 &mut self,
11803 _: &ContextMenuPrevious,
11804 window: &mut Window,
11805 cx: &mut Context<Self>,
11806 ) {
11807 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11808 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11809 }
11810 }
11811
11812 pub fn context_menu_next(
11813 &mut self,
11814 _: &ContextMenuNext,
11815 window: &mut Window,
11816 cx: &mut Context<Self>,
11817 ) {
11818 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11819 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11820 }
11821 }
11822
11823 pub fn context_menu_last(
11824 &mut self,
11825 _: &ContextMenuLast,
11826 window: &mut Window,
11827 cx: &mut Context<Self>,
11828 ) {
11829 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11830 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11831 }
11832 }
11833
11834 pub fn move_to_previous_word_start(
11835 &mut self,
11836 _: &MoveToPreviousWordStart,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11842 s.move_cursors_with(|map, head, _| {
11843 (
11844 movement::previous_word_start(map, head),
11845 SelectionGoal::None,
11846 )
11847 });
11848 })
11849 }
11850
11851 pub fn move_to_previous_subword_start(
11852 &mut self,
11853 _: &MoveToPreviousSubwordStart,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11858 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11859 s.move_cursors_with(|map, head, _| {
11860 (
11861 movement::previous_subword_start(map, head),
11862 SelectionGoal::None,
11863 )
11864 });
11865 })
11866 }
11867
11868 pub fn select_to_previous_word_start(
11869 &mut self,
11870 _: &SelectToPreviousWordStart,
11871 window: &mut Window,
11872 cx: &mut Context<Self>,
11873 ) {
11874 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11875 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11876 s.move_heads_with(|map, head, _| {
11877 (
11878 movement::previous_word_start(map, head),
11879 SelectionGoal::None,
11880 )
11881 });
11882 })
11883 }
11884
11885 pub fn select_to_previous_subword_start(
11886 &mut self,
11887 _: &SelectToPreviousSubwordStart,
11888 window: &mut Window,
11889 cx: &mut Context<Self>,
11890 ) {
11891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11893 s.move_heads_with(|map, head, _| {
11894 (
11895 movement::previous_subword_start(map, head),
11896 SelectionGoal::None,
11897 )
11898 });
11899 })
11900 }
11901
11902 pub fn delete_to_previous_word_start(
11903 &mut self,
11904 action: &DeleteToPreviousWordStart,
11905 window: &mut Window,
11906 cx: &mut Context<Self>,
11907 ) {
11908 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11909 self.transact(window, cx, |this, window, cx| {
11910 this.select_autoclose_pair(window, cx);
11911 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11912 s.move_with(|map, selection| {
11913 if selection.is_empty() {
11914 let cursor = if action.ignore_newlines {
11915 movement::previous_word_start(map, selection.head())
11916 } else {
11917 movement::previous_word_start_or_newline(map, selection.head())
11918 };
11919 selection.set_head(cursor, SelectionGoal::None);
11920 }
11921 });
11922 });
11923 this.insert("", window, cx);
11924 });
11925 }
11926
11927 pub fn delete_to_previous_subword_start(
11928 &mut self,
11929 _: &DeleteToPreviousSubwordStart,
11930 window: &mut Window,
11931 cx: &mut Context<Self>,
11932 ) {
11933 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11934 self.transact(window, cx, |this, window, cx| {
11935 this.select_autoclose_pair(window, cx);
11936 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11937 s.move_with(|map, selection| {
11938 if selection.is_empty() {
11939 let cursor = movement::previous_subword_start(map, selection.head());
11940 selection.set_head(cursor, SelectionGoal::None);
11941 }
11942 });
11943 });
11944 this.insert("", window, cx);
11945 });
11946 }
11947
11948 pub fn move_to_next_word_end(
11949 &mut self,
11950 _: &MoveToNextWordEnd,
11951 window: &mut Window,
11952 cx: &mut Context<Self>,
11953 ) {
11954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11956 s.move_cursors_with(|map, head, _| {
11957 (movement::next_word_end(map, head), SelectionGoal::None)
11958 });
11959 })
11960 }
11961
11962 pub fn move_to_next_subword_end(
11963 &mut self,
11964 _: &MoveToNextSubwordEnd,
11965 window: &mut Window,
11966 cx: &mut Context<Self>,
11967 ) {
11968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11970 s.move_cursors_with(|map, head, _| {
11971 (movement::next_subword_end(map, head), SelectionGoal::None)
11972 });
11973 })
11974 }
11975
11976 pub fn select_to_next_word_end(
11977 &mut self,
11978 _: &SelectToNextWordEnd,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11983 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11984 s.move_heads_with(|map, head, _| {
11985 (movement::next_word_end(map, head), SelectionGoal::None)
11986 });
11987 })
11988 }
11989
11990 pub fn select_to_next_subword_end(
11991 &mut self,
11992 _: &SelectToNextSubwordEnd,
11993 window: &mut Window,
11994 cx: &mut Context<Self>,
11995 ) {
11996 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11997 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11998 s.move_heads_with(|map, head, _| {
11999 (movement::next_subword_end(map, head), SelectionGoal::None)
12000 });
12001 })
12002 }
12003
12004 pub fn delete_to_next_word_end(
12005 &mut self,
12006 action: &DeleteToNextWordEnd,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12011 self.transact(window, cx, |this, window, cx| {
12012 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12013 s.move_with(|map, selection| {
12014 if selection.is_empty() {
12015 let cursor = if action.ignore_newlines {
12016 movement::next_word_end(map, selection.head())
12017 } else {
12018 movement::next_word_end_or_newline(map, selection.head())
12019 };
12020 selection.set_head(cursor, SelectionGoal::None);
12021 }
12022 });
12023 });
12024 this.insert("", window, cx);
12025 });
12026 }
12027
12028 pub fn delete_to_next_subword_end(
12029 &mut self,
12030 _: &DeleteToNextSubwordEnd,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12035 self.transact(window, cx, |this, window, cx| {
12036 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12037 s.move_with(|map, selection| {
12038 if selection.is_empty() {
12039 let cursor = movement::next_subword_end(map, selection.head());
12040 selection.set_head(cursor, SelectionGoal::None);
12041 }
12042 });
12043 });
12044 this.insert("", window, cx);
12045 });
12046 }
12047
12048 pub fn move_to_beginning_of_line(
12049 &mut self,
12050 action: &MoveToBeginningOfLine,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12056 s.move_cursors_with(|map, head, _| {
12057 (
12058 movement::indented_line_beginning(
12059 map,
12060 head,
12061 action.stop_at_soft_wraps,
12062 action.stop_at_indent,
12063 ),
12064 SelectionGoal::None,
12065 )
12066 });
12067 })
12068 }
12069
12070 pub fn select_to_beginning_of_line(
12071 &mut self,
12072 action: &SelectToBeginningOfLine,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12078 s.move_heads_with(|map, head, _| {
12079 (
12080 movement::indented_line_beginning(
12081 map,
12082 head,
12083 action.stop_at_soft_wraps,
12084 action.stop_at_indent,
12085 ),
12086 SelectionGoal::None,
12087 )
12088 });
12089 });
12090 }
12091
12092 pub fn delete_to_beginning_of_line(
12093 &mut self,
12094 action: &DeleteToBeginningOfLine,
12095 window: &mut Window,
12096 cx: &mut Context<Self>,
12097 ) {
12098 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12099 self.transact(window, cx, |this, window, cx| {
12100 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12101 s.move_with(|_, selection| {
12102 selection.reversed = true;
12103 });
12104 });
12105
12106 this.select_to_beginning_of_line(
12107 &SelectToBeginningOfLine {
12108 stop_at_soft_wraps: false,
12109 stop_at_indent: action.stop_at_indent,
12110 },
12111 window,
12112 cx,
12113 );
12114 this.backspace(&Backspace, window, cx);
12115 });
12116 }
12117
12118 pub fn move_to_end_of_line(
12119 &mut self,
12120 action: &MoveToEndOfLine,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12126 s.move_cursors_with(|map, head, _| {
12127 (
12128 movement::line_end(map, head, action.stop_at_soft_wraps),
12129 SelectionGoal::None,
12130 )
12131 });
12132 })
12133 }
12134
12135 pub fn select_to_end_of_line(
12136 &mut self,
12137 action: &SelectToEndOfLine,
12138 window: &mut Window,
12139 cx: &mut Context<Self>,
12140 ) {
12141 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12142 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12143 s.move_heads_with(|map, head, _| {
12144 (
12145 movement::line_end(map, head, action.stop_at_soft_wraps),
12146 SelectionGoal::None,
12147 )
12148 });
12149 })
12150 }
12151
12152 pub fn delete_to_end_of_line(
12153 &mut self,
12154 _: &DeleteToEndOfLine,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12159 self.transact(window, cx, |this, window, cx| {
12160 this.select_to_end_of_line(
12161 &SelectToEndOfLine {
12162 stop_at_soft_wraps: false,
12163 },
12164 window,
12165 cx,
12166 );
12167 this.delete(&Delete, window, cx);
12168 });
12169 }
12170
12171 pub fn cut_to_end_of_line(
12172 &mut self,
12173 _: &CutToEndOfLine,
12174 window: &mut Window,
12175 cx: &mut Context<Self>,
12176 ) {
12177 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12178 self.transact(window, cx, |this, window, cx| {
12179 this.select_to_end_of_line(
12180 &SelectToEndOfLine {
12181 stop_at_soft_wraps: false,
12182 },
12183 window,
12184 cx,
12185 );
12186 this.cut(&Cut, window, cx);
12187 });
12188 }
12189
12190 pub fn move_to_start_of_paragraph(
12191 &mut self,
12192 _: &MoveToStartOfParagraph,
12193 window: &mut Window,
12194 cx: &mut Context<Self>,
12195 ) {
12196 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12197 cx.propagate();
12198 return;
12199 }
12200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.move_with(|map, selection| {
12203 selection.collapse_to(
12204 movement::start_of_paragraph(map, selection.head(), 1),
12205 SelectionGoal::None,
12206 )
12207 });
12208 })
12209 }
12210
12211 pub fn move_to_end_of_paragraph(
12212 &mut self,
12213 _: &MoveToEndOfParagraph,
12214 window: &mut Window,
12215 cx: &mut Context<Self>,
12216 ) {
12217 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12218 cx.propagate();
12219 return;
12220 }
12221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12222 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12223 s.move_with(|map, selection| {
12224 selection.collapse_to(
12225 movement::end_of_paragraph(map, selection.head(), 1),
12226 SelectionGoal::None,
12227 )
12228 });
12229 })
12230 }
12231
12232 pub fn select_to_start_of_paragraph(
12233 &mut self,
12234 _: &SelectToStartOfParagraph,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) {
12238 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12239 cx.propagate();
12240 return;
12241 }
12242 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12243 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12244 s.move_heads_with(|map, head, _| {
12245 (
12246 movement::start_of_paragraph(map, head, 1),
12247 SelectionGoal::None,
12248 )
12249 });
12250 })
12251 }
12252
12253 pub fn select_to_end_of_paragraph(
12254 &mut self,
12255 _: &SelectToEndOfParagraph,
12256 window: &mut Window,
12257 cx: &mut Context<Self>,
12258 ) {
12259 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12260 cx.propagate();
12261 return;
12262 }
12263 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12264 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12265 s.move_heads_with(|map, head, _| {
12266 (
12267 movement::end_of_paragraph(map, head, 1),
12268 SelectionGoal::None,
12269 )
12270 });
12271 })
12272 }
12273
12274 pub fn move_to_start_of_excerpt(
12275 &mut self,
12276 _: &MoveToStartOfExcerpt,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12281 cx.propagate();
12282 return;
12283 }
12284 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12285 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12286 s.move_with(|map, selection| {
12287 selection.collapse_to(
12288 movement::start_of_excerpt(
12289 map,
12290 selection.head(),
12291 workspace::searchable::Direction::Prev,
12292 ),
12293 SelectionGoal::None,
12294 )
12295 });
12296 })
12297 }
12298
12299 pub fn move_to_start_of_next_excerpt(
12300 &mut self,
12301 _: &MoveToStartOfNextExcerpt,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12306 cx.propagate();
12307 return;
12308 }
12309
12310 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12311 s.move_with(|map, selection| {
12312 selection.collapse_to(
12313 movement::start_of_excerpt(
12314 map,
12315 selection.head(),
12316 workspace::searchable::Direction::Next,
12317 ),
12318 SelectionGoal::None,
12319 )
12320 });
12321 })
12322 }
12323
12324 pub fn move_to_end_of_excerpt(
12325 &mut self,
12326 _: &MoveToEndOfExcerpt,
12327 window: &mut Window,
12328 cx: &mut Context<Self>,
12329 ) {
12330 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12331 cx.propagate();
12332 return;
12333 }
12334 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12335 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12336 s.move_with(|map, selection| {
12337 selection.collapse_to(
12338 movement::end_of_excerpt(
12339 map,
12340 selection.head(),
12341 workspace::searchable::Direction::Next,
12342 ),
12343 SelectionGoal::None,
12344 )
12345 });
12346 })
12347 }
12348
12349 pub fn move_to_end_of_previous_excerpt(
12350 &mut self,
12351 _: &MoveToEndOfPreviousExcerpt,
12352 window: &mut Window,
12353 cx: &mut Context<Self>,
12354 ) {
12355 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12356 cx.propagate();
12357 return;
12358 }
12359 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12360 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12361 s.move_with(|map, selection| {
12362 selection.collapse_to(
12363 movement::end_of_excerpt(
12364 map,
12365 selection.head(),
12366 workspace::searchable::Direction::Prev,
12367 ),
12368 SelectionGoal::None,
12369 )
12370 });
12371 })
12372 }
12373
12374 pub fn select_to_start_of_excerpt(
12375 &mut self,
12376 _: &SelectToStartOfExcerpt,
12377 window: &mut Window,
12378 cx: &mut Context<Self>,
12379 ) {
12380 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12381 cx.propagate();
12382 return;
12383 }
12384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12385 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12386 s.move_heads_with(|map, head, _| {
12387 (
12388 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12389 SelectionGoal::None,
12390 )
12391 });
12392 })
12393 }
12394
12395 pub fn select_to_start_of_next_excerpt(
12396 &mut self,
12397 _: &SelectToStartOfNextExcerpt,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12402 cx.propagate();
12403 return;
12404 }
12405 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12406 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12407 s.move_heads_with(|map, head, _| {
12408 (
12409 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12410 SelectionGoal::None,
12411 )
12412 });
12413 })
12414 }
12415
12416 pub fn select_to_end_of_excerpt(
12417 &mut self,
12418 _: &SelectToEndOfExcerpt,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12423 cx.propagate();
12424 return;
12425 }
12426 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12427 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12428 s.move_heads_with(|map, head, _| {
12429 (
12430 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12431 SelectionGoal::None,
12432 )
12433 });
12434 })
12435 }
12436
12437 pub fn select_to_end_of_previous_excerpt(
12438 &mut self,
12439 _: &SelectToEndOfPreviousExcerpt,
12440 window: &mut Window,
12441 cx: &mut Context<Self>,
12442 ) {
12443 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12444 cx.propagate();
12445 return;
12446 }
12447 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12449 s.move_heads_with(|map, head, _| {
12450 (
12451 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12452 SelectionGoal::None,
12453 )
12454 });
12455 })
12456 }
12457
12458 pub fn move_to_beginning(
12459 &mut self,
12460 _: &MoveToBeginning,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) {
12464 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12465 cx.propagate();
12466 return;
12467 }
12468 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12469 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12470 s.select_ranges(vec![0..0]);
12471 });
12472 }
12473
12474 pub fn select_to_beginning(
12475 &mut self,
12476 _: &SelectToBeginning,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 let mut selection = self.selections.last::<Point>(cx);
12481 selection.set_head(Point::zero(), SelectionGoal::None);
12482 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12483 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12484 s.select(vec![selection]);
12485 });
12486 }
12487
12488 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12489 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12490 cx.propagate();
12491 return;
12492 }
12493 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12494 let cursor = self.buffer.read(cx).read(cx).len();
12495 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12496 s.select_ranges(vec![cursor..cursor])
12497 });
12498 }
12499
12500 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12501 self.nav_history = nav_history;
12502 }
12503
12504 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12505 self.nav_history.as_ref()
12506 }
12507
12508 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12509 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12510 }
12511
12512 fn push_to_nav_history(
12513 &mut self,
12514 cursor_anchor: Anchor,
12515 new_position: Option<Point>,
12516 is_deactivate: bool,
12517 cx: &mut Context<Self>,
12518 ) {
12519 if let Some(nav_history) = self.nav_history.as_mut() {
12520 let buffer = self.buffer.read(cx).read(cx);
12521 let cursor_position = cursor_anchor.to_point(&buffer);
12522 let scroll_state = self.scroll_manager.anchor();
12523 let scroll_top_row = scroll_state.top_row(&buffer);
12524 drop(buffer);
12525
12526 if let Some(new_position) = new_position {
12527 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12528 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12529 return;
12530 }
12531 }
12532
12533 nav_history.push(
12534 Some(NavigationData {
12535 cursor_anchor,
12536 cursor_position,
12537 scroll_anchor: scroll_state,
12538 scroll_top_row,
12539 }),
12540 cx,
12541 );
12542 cx.emit(EditorEvent::PushedToNavHistory {
12543 anchor: cursor_anchor,
12544 is_deactivate,
12545 })
12546 }
12547 }
12548
12549 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12550 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12551 let buffer = self.buffer.read(cx).snapshot(cx);
12552 let mut selection = self.selections.first::<usize>(cx);
12553 selection.set_head(buffer.len(), SelectionGoal::None);
12554 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12555 s.select(vec![selection]);
12556 });
12557 }
12558
12559 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12560 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12561 let end = self.buffer.read(cx).read(cx).len();
12562 self.change_selections(None, window, cx, |s| {
12563 s.select_ranges(vec![0..end]);
12564 });
12565 }
12566
12567 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12570 let mut selections = self.selections.all::<Point>(cx);
12571 let max_point = display_map.buffer_snapshot.max_point();
12572 for selection in &mut selections {
12573 let rows = selection.spanned_rows(true, &display_map);
12574 selection.start = Point::new(rows.start.0, 0);
12575 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12576 selection.reversed = false;
12577 }
12578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12579 s.select(selections);
12580 });
12581 }
12582
12583 pub fn split_selection_into_lines(
12584 &mut self,
12585 _: &SplitSelectionIntoLines,
12586 window: &mut Window,
12587 cx: &mut Context<Self>,
12588 ) {
12589 let selections = self
12590 .selections
12591 .all::<Point>(cx)
12592 .into_iter()
12593 .map(|selection| selection.start..selection.end)
12594 .collect::<Vec<_>>();
12595 self.unfold_ranges(&selections, true, true, cx);
12596
12597 let mut new_selection_ranges = Vec::new();
12598 {
12599 let buffer = self.buffer.read(cx).read(cx);
12600 for selection in selections {
12601 for row in selection.start.row..selection.end.row {
12602 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12603 new_selection_ranges.push(cursor..cursor);
12604 }
12605
12606 let is_multiline_selection = selection.start.row != selection.end.row;
12607 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12608 // so this action feels more ergonomic when paired with other selection operations
12609 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12610 if !should_skip_last {
12611 new_selection_ranges.push(selection.end..selection.end);
12612 }
12613 }
12614 }
12615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12616 s.select_ranges(new_selection_ranges);
12617 });
12618 }
12619
12620 pub fn add_selection_above(
12621 &mut self,
12622 _: &AddSelectionAbove,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) {
12626 self.add_selection(true, window, cx);
12627 }
12628
12629 pub fn add_selection_below(
12630 &mut self,
12631 _: &AddSelectionBelow,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 self.add_selection(false, window, cx);
12636 }
12637
12638 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12640
12641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12642 let mut selections = self.selections.all::<Point>(cx);
12643 let text_layout_details = self.text_layout_details(window);
12644 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12645 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12646 let range = oldest_selection.display_range(&display_map).sorted();
12647
12648 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12649 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12650 let positions = start_x.min(end_x)..start_x.max(end_x);
12651
12652 selections.clear();
12653 let mut stack = Vec::new();
12654 for row in range.start.row().0..=range.end.row().0 {
12655 if let Some(selection) = self.selections.build_columnar_selection(
12656 &display_map,
12657 DisplayRow(row),
12658 &positions,
12659 oldest_selection.reversed,
12660 &text_layout_details,
12661 ) {
12662 stack.push(selection.id);
12663 selections.push(selection);
12664 }
12665 }
12666
12667 if above {
12668 stack.reverse();
12669 }
12670
12671 AddSelectionsState { above, stack }
12672 });
12673
12674 let last_added_selection = *state.stack.last().unwrap();
12675 let mut new_selections = Vec::new();
12676 if above == state.above {
12677 let end_row = if above {
12678 DisplayRow(0)
12679 } else {
12680 display_map.max_point().row()
12681 };
12682
12683 'outer: for selection in selections {
12684 if selection.id == last_added_selection {
12685 let range = selection.display_range(&display_map).sorted();
12686 debug_assert_eq!(range.start.row(), range.end.row());
12687 let mut row = range.start.row();
12688 let positions =
12689 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12690 px(start)..px(end)
12691 } else {
12692 let start_x =
12693 display_map.x_for_display_point(range.start, &text_layout_details);
12694 let end_x =
12695 display_map.x_for_display_point(range.end, &text_layout_details);
12696 start_x.min(end_x)..start_x.max(end_x)
12697 };
12698
12699 while row != end_row {
12700 if above {
12701 row.0 -= 1;
12702 } else {
12703 row.0 += 1;
12704 }
12705
12706 if let Some(new_selection) = self.selections.build_columnar_selection(
12707 &display_map,
12708 row,
12709 &positions,
12710 selection.reversed,
12711 &text_layout_details,
12712 ) {
12713 state.stack.push(new_selection.id);
12714 if above {
12715 new_selections.push(new_selection);
12716 new_selections.push(selection);
12717 } else {
12718 new_selections.push(selection);
12719 new_selections.push(new_selection);
12720 }
12721
12722 continue 'outer;
12723 }
12724 }
12725 }
12726
12727 new_selections.push(selection);
12728 }
12729 } else {
12730 new_selections = selections;
12731 new_selections.retain(|s| s.id != last_added_selection);
12732 state.stack.pop();
12733 }
12734
12735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12736 s.select(new_selections);
12737 });
12738 if state.stack.len() > 1 {
12739 self.add_selections_state = Some(state);
12740 }
12741 }
12742
12743 fn select_match_ranges(
12744 &mut self,
12745 range: Range<usize>,
12746 reversed: bool,
12747 replace_newest: bool,
12748 auto_scroll: Option<Autoscroll>,
12749 window: &mut Window,
12750 cx: &mut Context<Editor>,
12751 ) {
12752 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12753 self.change_selections(auto_scroll, window, cx, |s| {
12754 if replace_newest {
12755 s.delete(s.newest_anchor().id);
12756 }
12757 if reversed {
12758 s.insert_range(range.end..range.start);
12759 } else {
12760 s.insert_range(range);
12761 }
12762 });
12763 }
12764
12765 pub fn select_next_match_internal(
12766 &mut self,
12767 display_map: &DisplaySnapshot,
12768 replace_newest: bool,
12769 autoscroll: Option<Autoscroll>,
12770 window: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) -> Result<()> {
12773 let buffer = &display_map.buffer_snapshot;
12774 let mut selections = self.selections.all::<usize>(cx);
12775 if let Some(mut select_next_state) = self.select_next_state.take() {
12776 let query = &select_next_state.query;
12777 if !select_next_state.done {
12778 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12779 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12780 let mut next_selected_range = None;
12781
12782 let bytes_after_last_selection =
12783 buffer.bytes_in_range(last_selection.end..buffer.len());
12784 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12785 let query_matches = query
12786 .stream_find_iter(bytes_after_last_selection)
12787 .map(|result| (last_selection.end, result))
12788 .chain(
12789 query
12790 .stream_find_iter(bytes_before_first_selection)
12791 .map(|result| (0, result)),
12792 );
12793
12794 for (start_offset, query_match) in query_matches {
12795 let query_match = query_match.unwrap(); // can only fail due to I/O
12796 let offset_range =
12797 start_offset + query_match.start()..start_offset + query_match.end();
12798 let display_range = offset_range.start.to_display_point(display_map)
12799 ..offset_range.end.to_display_point(display_map);
12800
12801 if !select_next_state.wordwise
12802 || (!movement::is_inside_word(display_map, display_range.start)
12803 && !movement::is_inside_word(display_map, display_range.end))
12804 {
12805 // TODO: This is n^2, because we might check all the selections
12806 if !selections
12807 .iter()
12808 .any(|selection| selection.range().overlaps(&offset_range))
12809 {
12810 next_selected_range = Some(offset_range);
12811 break;
12812 }
12813 }
12814 }
12815
12816 if let Some(next_selected_range) = next_selected_range {
12817 self.select_match_ranges(
12818 next_selected_range,
12819 last_selection.reversed,
12820 replace_newest,
12821 autoscroll,
12822 window,
12823 cx,
12824 );
12825 } else {
12826 select_next_state.done = true;
12827 }
12828 }
12829
12830 self.select_next_state = Some(select_next_state);
12831 } else {
12832 let mut only_carets = true;
12833 let mut same_text_selected = true;
12834 let mut selected_text = None;
12835
12836 let mut selections_iter = selections.iter().peekable();
12837 while let Some(selection) = selections_iter.next() {
12838 if selection.start != selection.end {
12839 only_carets = false;
12840 }
12841
12842 if same_text_selected {
12843 if selected_text.is_none() {
12844 selected_text =
12845 Some(buffer.text_for_range(selection.range()).collect::<String>());
12846 }
12847
12848 if let Some(next_selection) = selections_iter.peek() {
12849 if next_selection.range().len() == selection.range().len() {
12850 let next_selected_text = buffer
12851 .text_for_range(next_selection.range())
12852 .collect::<String>();
12853 if Some(next_selected_text) != selected_text {
12854 same_text_selected = false;
12855 selected_text = None;
12856 }
12857 } else {
12858 same_text_selected = false;
12859 selected_text = None;
12860 }
12861 }
12862 }
12863 }
12864
12865 if only_carets {
12866 for selection in &mut selections {
12867 let word_range = movement::surrounding_word(
12868 display_map,
12869 selection.start.to_display_point(display_map),
12870 );
12871 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12872 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12873 selection.goal = SelectionGoal::None;
12874 selection.reversed = false;
12875 self.select_match_ranges(
12876 selection.start..selection.end,
12877 selection.reversed,
12878 replace_newest,
12879 autoscroll,
12880 window,
12881 cx,
12882 );
12883 }
12884
12885 if selections.len() == 1 {
12886 let selection = selections
12887 .last()
12888 .expect("ensured that there's only one selection");
12889 let query = buffer
12890 .text_for_range(selection.start..selection.end)
12891 .collect::<String>();
12892 let is_empty = query.is_empty();
12893 let select_state = SelectNextState {
12894 query: AhoCorasick::new(&[query])?,
12895 wordwise: true,
12896 done: is_empty,
12897 };
12898 self.select_next_state = Some(select_state);
12899 } else {
12900 self.select_next_state = None;
12901 }
12902 } else if let Some(selected_text) = selected_text {
12903 self.select_next_state = Some(SelectNextState {
12904 query: AhoCorasick::new(&[selected_text])?,
12905 wordwise: false,
12906 done: false,
12907 });
12908 self.select_next_match_internal(
12909 display_map,
12910 replace_newest,
12911 autoscroll,
12912 window,
12913 cx,
12914 )?;
12915 }
12916 }
12917 Ok(())
12918 }
12919
12920 pub fn select_all_matches(
12921 &mut self,
12922 _action: &SelectAllMatches,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) -> Result<()> {
12926 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12927
12928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12929
12930 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12931 let Some(select_next_state) = self.select_next_state.as_mut() else {
12932 return Ok(());
12933 };
12934 if select_next_state.done {
12935 return Ok(());
12936 }
12937
12938 let mut new_selections = Vec::new();
12939
12940 let reversed = self.selections.oldest::<usize>(cx).reversed;
12941 let buffer = &display_map.buffer_snapshot;
12942 let query_matches = select_next_state
12943 .query
12944 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12945
12946 for query_match in query_matches.into_iter() {
12947 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12948 let offset_range = if reversed {
12949 query_match.end()..query_match.start()
12950 } else {
12951 query_match.start()..query_match.end()
12952 };
12953 let display_range = offset_range.start.to_display_point(&display_map)
12954 ..offset_range.end.to_display_point(&display_map);
12955
12956 if !select_next_state.wordwise
12957 || (!movement::is_inside_word(&display_map, display_range.start)
12958 && !movement::is_inside_word(&display_map, display_range.end))
12959 {
12960 new_selections.push(offset_range.start..offset_range.end);
12961 }
12962 }
12963
12964 select_next_state.done = true;
12965 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12966 self.change_selections(None, window, cx, |selections| {
12967 selections.select_ranges(new_selections)
12968 });
12969
12970 Ok(())
12971 }
12972
12973 pub fn select_next(
12974 &mut self,
12975 action: &SelectNext,
12976 window: &mut Window,
12977 cx: &mut Context<Self>,
12978 ) -> Result<()> {
12979 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12980 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12981 self.select_next_match_internal(
12982 &display_map,
12983 action.replace_newest,
12984 Some(Autoscroll::newest()),
12985 window,
12986 cx,
12987 )?;
12988 Ok(())
12989 }
12990
12991 pub fn select_previous(
12992 &mut self,
12993 action: &SelectPrevious,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) -> Result<()> {
12997 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12998 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12999 let buffer = &display_map.buffer_snapshot;
13000 let mut selections = self.selections.all::<usize>(cx);
13001 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13002 let query = &select_prev_state.query;
13003 if !select_prev_state.done {
13004 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13005 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13006 let mut next_selected_range = None;
13007 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13008 let bytes_before_last_selection =
13009 buffer.reversed_bytes_in_range(0..last_selection.start);
13010 let bytes_after_first_selection =
13011 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13012 let query_matches = query
13013 .stream_find_iter(bytes_before_last_selection)
13014 .map(|result| (last_selection.start, result))
13015 .chain(
13016 query
13017 .stream_find_iter(bytes_after_first_selection)
13018 .map(|result| (buffer.len(), result)),
13019 );
13020 for (end_offset, query_match) in query_matches {
13021 let query_match = query_match.unwrap(); // can only fail due to I/O
13022 let offset_range =
13023 end_offset - query_match.end()..end_offset - query_match.start();
13024 let display_range = offset_range.start.to_display_point(&display_map)
13025 ..offset_range.end.to_display_point(&display_map);
13026
13027 if !select_prev_state.wordwise
13028 || (!movement::is_inside_word(&display_map, display_range.start)
13029 && !movement::is_inside_word(&display_map, display_range.end))
13030 {
13031 next_selected_range = Some(offset_range);
13032 break;
13033 }
13034 }
13035
13036 if let Some(next_selected_range) = next_selected_range {
13037 self.select_match_ranges(
13038 next_selected_range,
13039 last_selection.reversed,
13040 action.replace_newest,
13041 Some(Autoscroll::newest()),
13042 window,
13043 cx,
13044 );
13045 } else {
13046 select_prev_state.done = true;
13047 }
13048 }
13049
13050 self.select_prev_state = Some(select_prev_state);
13051 } else {
13052 let mut only_carets = true;
13053 let mut same_text_selected = true;
13054 let mut selected_text = None;
13055
13056 let mut selections_iter = selections.iter().peekable();
13057 while let Some(selection) = selections_iter.next() {
13058 if selection.start != selection.end {
13059 only_carets = false;
13060 }
13061
13062 if same_text_selected {
13063 if selected_text.is_none() {
13064 selected_text =
13065 Some(buffer.text_for_range(selection.range()).collect::<String>());
13066 }
13067
13068 if let Some(next_selection) = selections_iter.peek() {
13069 if next_selection.range().len() == selection.range().len() {
13070 let next_selected_text = buffer
13071 .text_for_range(next_selection.range())
13072 .collect::<String>();
13073 if Some(next_selected_text) != selected_text {
13074 same_text_selected = false;
13075 selected_text = None;
13076 }
13077 } else {
13078 same_text_selected = false;
13079 selected_text = None;
13080 }
13081 }
13082 }
13083 }
13084
13085 if only_carets {
13086 for selection in &mut selections {
13087 let word_range = movement::surrounding_word(
13088 &display_map,
13089 selection.start.to_display_point(&display_map),
13090 );
13091 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13092 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13093 selection.goal = SelectionGoal::None;
13094 selection.reversed = false;
13095 self.select_match_ranges(
13096 selection.start..selection.end,
13097 selection.reversed,
13098 action.replace_newest,
13099 Some(Autoscroll::newest()),
13100 window,
13101 cx,
13102 );
13103 }
13104 if selections.len() == 1 {
13105 let selection = selections
13106 .last()
13107 .expect("ensured that there's only one selection");
13108 let query = buffer
13109 .text_for_range(selection.start..selection.end)
13110 .collect::<String>();
13111 let is_empty = query.is_empty();
13112 let select_state = SelectNextState {
13113 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13114 wordwise: true,
13115 done: is_empty,
13116 };
13117 self.select_prev_state = Some(select_state);
13118 } else {
13119 self.select_prev_state = None;
13120 }
13121 } else if let Some(selected_text) = selected_text {
13122 self.select_prev_state = Some(SelectNextState {
13123 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13124 wordwise: false,
13125 done: false,
13126 });
13127 self.select_previous(action, window, cx)?;
13128 }
13129 }
13130 Ok(())
13131 }
13132
13133 pub fn find_next_match(
13134 &mut self,
13135 _: &FindNextMatch,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) -> Result<()> {
13139 let selections = self.selections.disjoint_anchors();
13140 match selections.first() {
13141 Some(first) if selections.len() >= 2 => {
13142 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13143 s.select_ranges([first.range()]);
13144 });
13145 }
13146 _ => self.select_next(
13147 &SelectNext {
13148 replace_newest: true,
13149 },
13150 window,
13151 cx,
13152 )?,
13153 }
13154 Ok(())
13155 }
13156
13157 pub fn find_previous_match(
13158 &mut self,
13159 _: &FindPreviousMatch,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) -> Result<()> {
13163 let selections = self.selections.disjoint_anchors();
13164 match selections.last() {
13165 Some(last) if selections.len() >= 2 => {
13166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13167 s.select_ranges([last.range()]);
13168 });
13169 }
13170 _ => self.select_previous(
13171 &SelectPrevious {
13172 replace_newest: true,
13173 },
13174 window,
13175 cx,
13176 )?,
13177 }
13178 Ok(())
13179 }
13180
13181 pub fn toggle_comments(
13182 &mut self,
13183 action: &ToggleComments,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 if self.read_only(cx) {
13188 return;
13189 }
13190 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13191 let text_layout_details = &self.text_layout_details(window);
13192 self.transact(window, cx, |this, window, cx| {
13193 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13194 let mut edits = Vec::new();
13195 let mut selection_edit_ranges = Vec::new();
13196 let mut last_toggled_row = None;
13197 let snapshot = this.buffer.read(cx).read(cx);
13198 let empty_str: Arc<str> = Arc::default();
13199 let mut suffixes_inserted = Vec::new();
13200 let ignore_indent = action.ignore_indent;
13201
13202 fn comment_prefix_range(
13203 snapshot: &MultiBufferSnapshot,
13204 row: MultiBufferRow,
13205 comment_prefix: &str,
13206 comment_prefix_whitespace: &str,
13207 ignore_indent: bool,
13208 ) -> Range<Point> {
13209 let indent_size = if ignore_indent {
13210 0
13211 } else {
13212 snapshot.indent_size_for_line(row).len
13213 };
13214
13215 let start = Point::new(row.0, indent_size);
13216
13217 let mut line_bytes = snapshot
13218 .bytes_in_range(start..snapshot.max_point())
13219 .flatten()
13220 .copied();
13221
13222 // If this line currently begins with the line comment prefix, then record
13223 // the range containing the prefix.
13224 if line_bytes
13225 .by_ref()
13226 .take(comment_prefix.len())
13227 .eq(comment_prefix.bytes())
13228 {
13229 // Include any whitespace that matches the comment prefix.
13230 let matching_whitespace_len = line_bytes
13231 .zip(comment_prefix_whitespace.bytes())
13232 .take_while(|(a, b)| a == b)
13233 .count() as u32;
13234 let end = Point::new(
13235 start.row,
13236 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13237 );
13238 start..end
13239 } else {
13240 start..start
13241 }
13242 }
13243
13244 fn comment_suffix_range(
13245 snapshot: &MultiBufferSnapshot,
13246 row: MultiBufferRow,
13247 comment_suffix: &str,
13248 comment_suffix_has_leading_space: bool,
13249 ) -> Range<Point> {
13250 let end = Point::new(row.0, snapshot.line_len(row));
13251 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13252
13253 let mut line_end_bytes = snapshot
13254 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13255 .flatten()
13256 .copied();
13257
13258 let leading_space_len = if suffix_start_column > 0
13259 && line_end_bytes.next() == Some(b' ')
13260 && comment_suffix_has_leading_space
13261 {
13262 1
13263 } else {
13264 0
13265 };
13266
13267 // If this line currently begins with the line comment prefix, then record
13268 // the range containing the prefix.
13269 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13270 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13271 start..end
13272 } else {
13273 end..end
13274 }
13275 }
13276
13277 // TODO: Handle selections that cross excerpts
13278 for selection in &mut selections {
13279 let start_column = snapshot
13280 .indent_size_for_line(MultiBufferRow(selection.start.row))
13281 .len;
13282 let language = if let Some(language) =
13283 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13284 {
13285 language
13286 } else {
13287 continue;
13288 };
13289
13290 selection_edit_ranges.clear();
13291
13292 // If multiple selections contain a given row, avoid processing that
13293 // row more than once.
13294 let mut start_row = MultiBufferRow(selection.start.row);
13295 if last_toggled_row == Some(start_row) {
13296 start_row = start_row.next_row();
13297 }
13298 let end_row =
13299 if selection.end.row > selection.start.row && selection.end.column == 0 {
13300 MultiBufferRow(selection.end.row - 1)
13301 } else {
13302 MultiBufferRow(selection.end.row)
13303 };
13304 last_toggled_row = Some(end_row);
13305
13306 if start_row > end_row {
13307 continue;
13308 }
13309
13310 // If the language has line comments, toggle those.
13311 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13312
13313 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13314 if ignore_indent {
13315 full_comment_prefixes = full_comment_prefixes
13316 .into_iter()
13317 .map(|s| Arc::from(s.trim_end()))
13318 .collect();
13319 }
13320
13321 if !full_comment_prefixes.is_empty() {
13322 let first_prefix = full_comment_prefixes
13323 .first()
13324 .expect("prefixes is non-empty");
13325 let prefix_trimmed_lengths = full_comment_prefixes
13326 .iter()
13327 .map(|p| p.trim_end_matches(' ').len())
13328 .collect::<SmallVec<[usize; 4]>>();
13329
13330 let mut all_selection_lines_are_comments = true;
13331
13332 for row in start_row.0..=end_row.0 {
13333 let row = MultiBufferRow(row);
13334 if start_row < end_row && snapshot.is_line_blank(row) {
13335 continue;
13336 }
13337
13338 let prefix_range = full_comment_prefixes
13339 .iter()
13340 .zip(prefix_trimmed_lengths.iter().copied())
13341 .map(|(prefix, trimmed_prefix_len)| {
13342 comment_prefix_range(
13343 snapshot.deref(),
13344 row,
13345 &prefix[..trimmed_prefix_len],
13346 &prefix[trimmed_prefix_len..],
13347 ignore_indent,
13348 )
13349 })
13350 .max_by_key(|range| range.end.column - range.start.column)
13351 .expect("prefixes is non-empty");
13352
13353 if prefix_range.is_empty() {
13354 all_selection_lines_are_comments = false;
13355 }
13356
13357 selection_edit_ranges.push(prefix_range);
13358 }
13359
13360 if all_selection_lines_are_comments {
13361 edits.extend(
13362 selection_edit_ranges
13363 .iter()
13364 .cloned()
13365 .map(|range| (range, empty_str.clone())),
13366 );
13367 } else {
13368 let min_column = selection_edit_ranges
13369 .iter()
13370 .map(|range| range.start.column)
13371 .min()
13372 .unwrap_or(0);
13373 edits.extend(selection_edit_ranges.iter().map(|range| {
13374 let position = Point::new(range.start.row, min_column);
13375 (position..position, first_prefix.clone())
13376 }));
13377 }
13378 } else if let Some((full_comment_prefix, comment_suffix)) =
13379 language.block_comment_delimiters()
13380 {
13381 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13382 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13383 let prefix_range = comment_prefix_range(
13384 snapshot.deref(),
13385 start_row,
13386 comment_prefix,
13387 comment_prefix_whitespace,
13388 ignore_indent,
13389 );
13390 let suffix_range = comment_suffix_range(
13391 snapshot.deref(),
13392 end_row,
13393 comment_suffix.trim_start_matches(' '),
13394 comment_suffix.starts_with(' '),
13395 );
13396
13397 if prefix_range.is_empty() || suffix_range.is_empty() {
13398 edits.push((
13399 prefix_range.start..prefix_range.start,
13400 full_comment_prefix.clone(),
13401 ));
13402 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13403 suffixes_inserted.push((end_row, comment_suffix.len()));
13404 } else {
13405 edits.push((prefix_range, empty_str.clone()));
13406 edits.push((suffix_range, empty_str.clone()));
13407 }
13408 } else {
13409 continue;
13410 }
13411 }
13412
13413 drop(snapshot);
13414 this.buffer.update(cx, |buffer, cx| {
13415 buffer.edit(edits, None, cx);
13416 });
13417
13418 // Adjust selections so that they end before any comment suffixes that
13419 // were inserted.
13420 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13421 let mut selections = this.selections.all::<Point>(cx);
13422 let snapshot = this.buffer.read(cx).read(cx);
13423 for selection in &mut selections {
13424 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13425 match row.cmp(&MultiBufferRow(selection.end.row)) {
13426 Ordering::Less => {
13427 suffixes_inserted.next();
13428 continue;
13429 }
13430 Ordering::Greater => break,
13431 Ordering::Equal => {
13432 if selection.end.column == snapshot.line_len(row) {
13433 if selection.is_empty() {
13434 selection.start.column -= suffix_len as u32;
13435 }
13436 selection.end.column -= suffix_len as u32;
13437 }
13438 break;
13439 }
13440 }
13441 }
13442 }
13443
13444 drop(snapshot);
13445 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13446 s.select(selections)
13447 });
13448
13449 let selections = this.selections.all::<Point>(cx);
13450 let selections_on_single_row = selections.windows(2).all(|selections| {
13451 selections[0].start.row == selections[1].start.row
13452 && selections[0].end.row == selections[1].end.row
13453 && selections[0].start.row == selections[0].end.row
13454 });
13455 let selections_selecting = selections
13456 .iter()
13457 .any(|selection| selection.start != selection.end);
13458 let advance_downwards = action.advance_downwards
13459 && selections_on_single_row
13460 && !selections_selecting
13461 && !matches!(this.mode, EditorMode::SingleLine { .. });
13462
13463 if advance_downwards {
13464 let snapshot = this.buffer.read(cx).snapshot(cx);
13465
13466 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13467 s.move_cursors_with(|display_snapshot, display_point, _| {
13468 let mut point = display_point.to_point(display_snapshot);
13469 point.row += 1;
13470 point = snapshot.clip_point(point, Bias::Left);
13471 let display_point = point.to_display_point(display_snapshot);
13472 let goal = SelectionGoal::HorizontalPosition(
13473 display_snapshot
13474 .x_for_display_point(display_point, text_layout_details)
13475 .into(),
13476 );
13477 (display_point, goal)
13478 })
13479 });
13480 }
13481 });
13482 }
13483
13484 pub fn select_enclosing_symbol(
13485 &mut self,
13486 _: &SelectEnclosingSymbol,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13491
13492 let buffer = self.buffer.read(cx).snapshot(cx);
13493 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13494
13495 fn update_selection(
13496 selection: &Selection<usize>,
13497 buffer_snap: &MultiBufferSnapshot,
13498 ) -> Option<Selection<usize>> {
13499 let cursor = selection.head();
13500 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13501 for symbol in symbols.iter().rev() {
13502 let start = symbol.range.start.to_offset(buffer_snap);
13503 let end = symbol.range.end.to_offset(buffer_snap);
13504 let new_range = start..end;
13505 if start < selection.start || end > selection.end {
13506 return Some(Selection {
13507 id: selection.id,
13508 start: new_range.start,
13509 end: new_range.end,
13510 goal: SelectionGoal::None,
13511 reversed: selection.reversed,
13512 });
13513 }
13514 }
13515 None
13516 }
13517
13518 let mut selected_larger_symbol = false;
13519 let new_selections = old_selections
13520 .iter()
13521 .map(|selection| match update_selection(selection, &buffer) {
13522 Some(new_selection) => {
13523 if new_selection.range() != selection.range() {
13524 selected_larger_symbol = true;
13525 }
13526 new_selection
13527 }
13528 None => selection.clone(),
13529 })
13530 .collect::<Vec<_>>();
13531
13532 if selected_larger_symbol {
13533 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13534 s.select(new_selections);
13535 });
13536 }
13537 }
13538
13539 pub fn select_larger_syntax_node(
13540 &mut self,
13541 _: &SelectLargerSyntaxNode,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 let Some(visible_row_count) = self.visible_row_count() else {
13546 return;
13547 };
13548 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13549 if old_selections.is_empty() {
13550 return;
13551 }
13552
13553 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13554
13555 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13556 let buffer = self.buffer.read(cx).snapshot(cx);
13557
13558 let mut selected_larger_node = false;
13559 let mut new_selections = old_selections
13560 .iter()
13561 .map(|selection| {
13562 let old_range = selection.start..selection.end;
13563
13564 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13565 // manually select word at selection
13566 if ["string_content", "inline"].contains(&node.kind()) {
13567 let word_range = {
13568 let display_point = buffer
13569 .offset_to_point(old_range.start)
13570 .to_display_point(&display_map);
13571 let Range { start, end } =
13572 movement::surrounding_word(&display_map, display_point);
13573 start.to_point(&display_map).to_offset(&buffer)
13574 ..end.to_point(&display_map).to_offset(&buffer)
13575 };
13576 // ignore if word is already selected
13577 if !word_range.is_empty() && old_range != word_range {
13578 let last_word_range = {
13579 let display_point = buffer
13580 .offset_to_point(old_range.end)
13581 .to_display_point(&display_map);
13582 let Range { start, end } =
13583 movement::surrounding_word(&display_map, display_point);
13584 start.to_point(&display_map).to_offset(&buffer)
13585 ..end.to_point(&display_map).to_offset(&buffer)
13586 };
13587 // only select word if start and end point belongs to same word
13588 if word_range == last_word_range {
13589 selected_larger_node = true;
13590 return Selection {
13591 id: selection.id,
13592 start: word_range.start,
13593 end: word_range.end,
13594 goal: SelectionGoal::None,
13595 reversed: selection.reversed,
13596 };
13597 }
13598 }
13599 }
13600 }
13601
13602 let mut new_range = old_range.clone();
13603 while let Some((_node, containing_range)) =
13604 buffer.syntax_ancestor(new_range.clone())
13605 {
13606 new_range = match containing_range {
13607 MultiOrSingleBufferOffsetRange::Single(_) => break,
13608 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13609 };
13610 if !display_map.intersects_fold(new_range.start)
13611 && !display_map.intersects_fold(new_range.end)
13612 {
13613 break;
13614 }
13615 }
13616
13617 selected_larger_node |= new_range != old_range;
13618 Selection {
13619 id: selection.id,
13620 start: new_range.start,
13621 end: new_range.end,
13622 goal: SelectionGoal::None,
13623 reversed: selection.reversed,
13624 }
13625 })
13626 .collect::<Vec<_>>();
13627
13628 if !selected_larger_node {
13629 return; // don't put this call in the history
13630 }
13631
13632 // scroll based on transformation done to the last selection created by the user
13633 let (last_old, last_new) = old_selections
13634 .last()
13635 .zip(new_selections.last().cloned())
13636 .expect("old_selections isn't empty");
13637
13638 // revert selection
13639 let is_selection_reversed = {
13640 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13641 new_selections.last_mut().expect("checked above").reversed =
13642 should_newest_selection_be_reversed;
13643 should_newest_selection_be_reversed
13644 };
13645
13646 if selected_larger_node {
13647 self.select_syntax_node_history.disable_clearing = true;
13648 self.change_selections(None, window, cx, |s| {
13649 s.select(new_selections.clone());
13650 });
13651 self.select_syntax_node_history.disable_clearing = false;
13652 }
13653
13654 let start_row = last_new.start.to_display_point(&display_map).row().0;
13655 let end_row = last_new.end.to_display_point(&display_map).row().0;
13656 let selection_height = end_row - start_row + 1;
13657 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13658
13659 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13660 let scroll_behavior = if fits_on_the_screen {
13661 self.request_autoscroll(Autoscroll::fit(), cx);
13662 SelectSyntaxNodeScrollBehavior::FitSelection
13663 } else if is_selection_reversed {
13664 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13665 SelectSyntaxNodeScrollBehavior::CursorTop
13666 } else {
13667 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13668 SelectSyntaxNodeScrollBehavior::CursorBottom
13669 };
13670
13671 self.select_syntax_node_history.push((
13672 old_selections,
13673 scroll_behavior,
13674 is_selection_reversed,
13675 ));
13676 }
13677
13678 pub fn select_smaller_syntax_node(
13679 &mut self,
13680 _: &SelectSmallerSyntaxNode,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13685
13686 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13687 self.select_syntax_node_history.pop()
13688 {
13689 if let Some(selection) = selections.last_mut() {
13690 selection.reversed = is_selection_reversed;
13691 }
13692
13693 self.select_syntax_node_history.disable_clearing = true;
13694 self.change_selections(None, window, cx, |s| {
13695 s.select(selections.to_vec());
13696 });
13697 self.select_syntax_node_history.disable_clearing = false;
13698
13699 match scroll_behavior {
13700 SelectSyntaxNodeScrollBehavior::CursorTop => {
13701 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13702 }
13703 SelectSyntaxNodeScrollBehavior::FitSelection => {
13704 self.request_autoscroll(Autoscroll::fit(), cx);
13705 }
13706 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13707 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13708 }
13709 }
13710 }
13711 }
13712
13713 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13714 if !EditorSettings::get_global(cx).gutter.runnables {
13715 self.clear_tasks();
13716 return Task::ready(());
13717 }
13718 let project = self.project.as_ref().map(Entity::downgrade);
13719 let task_sources = self.lsp_task_sources(cx);
13720 let multi_buffer = self.buffer.downgrade();
13721 cx.spawn_in(window, async move |editor, cx| {
13722 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13723 let Some(project) = project.and_then(|p| p.upgrade()) else {
13724 return;
13725 };
13726 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13727 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13728 }) else {
13729 return;
13730 };
13731
13732 let hide_runnables = project
13733 .update(cx, |project, cx| {
13734 // Do not display any test indicators in non-dev server remote projects.
13735 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13736 })
13737 .unwrap_or(true);
13738 if hide_runnables {
13739 return;
13740 }
13741 let new_rows =
13742 cx.background_spawn({
13743 let snapshot = display_snapshot.clone();
13744 async move {
13745 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13746 }
13747 })
13748 .await;
13749 let Ok(lsp_tasks) =
13750 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13751 else {
13752 return;
13753 };
13754 let lsp_tasks = lsp_tasks.await;
13755
13756 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13757 lsp_tasks
13758 .into_iter()
13759 .flat_map(|(kind, tasks)| {
13760 tasks.into_iter().filter_map(move |(location, task)| {
13761 Some((kind.clone(), location?, task))
13762 })
13763 })
13764 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13765 let buffer = location.target.buffer;
13766 let buffer_snapshot = buffer.read(cx).snapshot();
13767 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13768 |(excerpt_id, snapshot, _)| {
13769 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13770 display_snapshot
13771 .buffer_snapshot
13772 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13773 } else {
13774 None
13775 }
13776 },
13777 );
13778 if let Some(offset) = offset {
13779 let task_buffer_range =
13780 location.target.range.to_point(&buffer_snapshot);
13781 let context_buffer_range =
13782 task_buffer_range.to_offset(&buffer_snapshot);
13783 let context_range = BufferOffset(context_buffer_range.start)
13784 ..BufferOffset(context_buffer_range.end);
13785
13786 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13787 .or_insert_with(|| RunnableTasks {
13788 templates: Vec::new(),
13789 offset,
13790 column: task_buffer_range.start.column,
13791 extra_variables: HashMap::default(),
13792 context_range,
13793 })
13794 .templates
13795 .push((kind, task.original_task().clone()));
13796 }
13797
13798 acc
13799 })
13800 }) else {
13801 return;
13802 };
13803
13804 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13805 buffer.language_settings(cx).tasks.prefer_lsp
13806 }) else {
13807 return;
13808 };
13809
13810 let rows = Self::runnable_rows(
13811 project,
13812 display_snapshot,
13813 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13814 new_rows,
13815 cx.clone(),
13816 );
13817 editor
13818 .update(cx, |editor, _| {
13819 editor.clear_tasks();
13820 for (key, mut value) in rows {
13821 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13822 value.templates.extend(lsp_tasks.templates);
13823 }
13824
13825 editor.insert_tasks(key, value);
13826 }
13827 for (key, value) in lsp_tasks_by_rows {
13828 editor.insert_tasks(key, value);
13829 }
13830 })
13831 .ok();
13832 })
13833 }
13834 fn fetch_runnable_ranges(
13835 snapshot: &DisplaySnapshot,
13836 range: Range<Anchor>,
13837 ) -> Vec<language::RunnableRange> {
13838 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13839 }
13840
13841 fn runnable_rows(
13842 project: Entity<Project>,
13843 snapshot: DisplaySnapshot,
13844 prefer_lsp: bool,
13845 runnable_ranges: Vec<RunnableRange>,
13846 mut cx: AsyncWindowContext,
13847 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13848 runnable_ranges
13849 .into_iter()
13850 .filter_map(|mut runnable| {
13851 let mut tasks = cx
13852 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13853 .ok()?;
13854 if prefer_lsp {
13855 tasks.retain(|(task_kind, _)| {
13856 !matches!(task_kind, TaskSourceKind::Language { .. })
13857 });
13858 }
13859 if tasks.is_empty() {
13860 return None;
13861 }
13862
13863 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13864
13865 let row = snapshot
13866 .buffer_snapshot
13867 .buffer_line_for_row(MultiBufferRow(point.row))?
13868 .1
13869 .start
13870 .row;
13871
13872 let context_range =
13873 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13874 Some((
13875 (runnable.buffer_id, row),
13876 RunnableTasks {
13877 templates: tasks,
13878 offset: snapshot
13879 .buffer_snapshot
13880 .anchor_before(runnable.run_range.start),
13881 context_range,
13882 column: point.column,
13883 extra_variables: runnable.extra_captures,
13884 },
13885 ))
13886 })
13887 .collect()
13888 }
13889
13890 fn templates_with_tags(
13891 project: &Entity<Project>,
13892 runnable: &mut Runnable,
13893 cx: &mut App,
13894 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13895 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13896 let (worktree_id, file) = project
13897 .buffer_for_id(runnable.buffer, cx)
13898 .and_then(|buffer| buffer.read(cx).file())
13899 .map(|file| (file.worktree_id(cx), file.clone()))
13900 .unzip();
13901
13902 (
13903 project.task_store().read(cx).task_inventory().cloned(),
13904 worktree_id,
13905 file,
13906 )
13907 });
13908
13909 let mut templates_with_tags = mem::take(&mut runnable.tags)
13910 .into_iter()
13911 .flat_map(|RunnableTag(tag)| {
13912 inventory
13913 .as_ref()
13914 .into_iter()
13915 .flat_map(|inventory| {
13916 inventory.read(cx).list_tasks(
13917 file.clone(),
13918 Some(runnable.language.clone()),
13919 worktree_id,
13920 cx,
13921 )
13922 })
13923 .filter(move |(_, template)| {
13924 template.tags.iter().any(|source_tag| source_tag == &tag)
13925 })
13926 })
13927 .sorted_by_key(|(kind, _)| kind.to_owned())
13928 .collect::<Vec<_>>();
13929 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13930 // Strongest source wins; if we have worktree tag binding, prefer that to
13931 // global and language bindings;
13932 // if we have a global binding, prefer that to language binding.
13933 let first_mismatch = templates_with_tags
13934 .iter()
13935 .position(|(tag_source, _)| tag_source != leading_tag_source);
13936 if let Some(index) = first_mismatch {
13937 templates_with_tags.truncate(index);
13938 }
13939 }
13940
13941 templates_with_tags
13942 }
13943
13944 pub fn move_to_enclosing_bracket(
13945 &mut self,
13946 _: &MoveToEnclosingBracket,
13947 window: &mut Window,
13948 cx: &mut Context<Self>,
13949 ) {
13950 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13951 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13952 s.move_offsets_with(|snapshot, selection| {
13953 let Some(enclosing_bracket_ranges) =
13954 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13955 else {
13956 return;
13957 };
13958
13959 let mut best_length = usize::MAX;
13960 let mut best_inside = false;
13961 let mut best_in_bracket_range = false;
13962 let mut best_destination = None;
13963 for (open, close) in enclosing_bracket_ranges {
13964 let close = close.to_inclusive();
13965 let length = close.end() - open.start;
13966 let inside = selection.start >= open.end && selection.end <= *close.start();
13967 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13968 || close.contains(&selection.head());
13969
13970 // If best is next to a bracket and current isn't, skip
13971 if !in_bracket_range && best_in_bracket_range {
13972 continue;
13973 }
13974
13975 // Prefer smaller lengths unless best is inside and current isn't
13976 if length > best_length && (best_inside || !inside) {
13977 continue;
13978 }
13979
13980 best_length = length;
13981 best_inside = inside;
13982 best_in_bracket_range = in_bracket_range;
13983 best_destination = Some(
13984 if close.contains(&selection.start) && close.contains(&selection.end) {
13985 if inside { open.end } else { open.start }
13986 } else if inside {
13987 *close.start()
13988 } else {
13989 *close.end()
13990 },
13991 );
13992 }
13993
13994 if let Some(destination) = best_destination {
13995 selection.collapse_to(destination, SelectionGoal::None);
13996 }
13997 })
13998 });
13999 }
14000
14001 pub fn undo_selection(
14002 &mut self,
14003 _: &UndoSelection,
14004 window: &mut Window,
14005 cx: &mut Context<Self>,
14006 ) {
14007 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14008 self.end_selection(window, cx);
14009 self.selection_history.mode = SelectionHistoryMode::Undoing;
14010 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14011 self.change_selections(None, window, cx, |s| {
14012 s.select_anchors(entry.selections.to_vec())
14013 });
14014 self.select_next_state = entry.select_next_state;
14015 self.select_prev_state = entry.select_prev_state;
14016 self.add_selections_state = entry.add_selections_state;
14017 self.request_autoscroll(Autoscroll::newest(), cx);
14018 }
14019 self.selection_history.mode = SelectionHistoryMode::Normal;
14020 }
14021
14022 pub fn redo_selection(
14023 &mut self,
14024 _: &RedoSelection,
14025 window: &mut Window,
14026 cx: &mut Context<Self>,
14027 ) {
14028 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14029 self.end_selection(window, cx);
14030 self.selection_history.mode = SelectionHistoryMode::Redoing;
14031 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14032 self.change_selections(None, window, cx, |s| {
14033 s.select_anchors(entry.selections.to_vec())
14034 });
14035 self.select_next_state = entry.select_next_state;
14036 self.select_prev_state = entry.select_prev_state;
14037 self.add_selections_state = entry.add_selections_state;
14038 self.request_autoscroll(Autoscroll::newest(), cx);
14039 }
14040 self.selection_history.mode = SelectionHistoryMode::Normal;
14041 }
14042
14043 pub fn expand_excerpts(
14044 &mut self,
14045 action: &ExpandExcerpts,
14046 _: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) {
14049 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14050 }
14051
14052 pub fn expand_excerpts_down(
14053 &mut self,
14054 action: &ExpandExcerptsDown,
14055 _: &mut Window,
14056 cx: &mut Context<Self>,
14057 ) {
14058 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14059 }
14060
14061 pub fn expand_excerpts_up(
14062 &mut self,
14063 action: &ExpandExcerptsUp,
14064 _: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14068 }
14069
14070 pub fn expand_excerpts_for_direction(
14071 &mut self,
14072 lines: u32,
14073 direction: ExpandExcerptDirection,
14074
14075 cx: &mut Context<Self>,
14076 ) {
14077 let selections = self.selections.disjoint_anchors();
14078
14079 let lines = if lines == 0 {
14080 EditorSettings::get_global(cx).expand_excerpt_lines
14081 } else {
14082 lines
14083 };
14084
14085 self.buffer.update(cx, |buffer, cx| {
14086 let snapshot = buffer.snapshot(cx);
14087 let mut excerpt_ids = selections
14088 .iter()
14089 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14090 .collect::<Vec<_>>();
14091 excerpt_ids.sort();
14092 excerpt_ids.dedup();
14093 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14094 })
14095 }
14096
14097 pub fn expand_excerpt(
14098 &mut self,
14099 excerpt: ExcerptId,
14100 direction: ExpandExcerptDirection,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 let current_scroll_position = self.scroll_position(cx);
14105 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14106 let mut should_scroll_up = false;
14107
14108 if direction == ExpandExcerptDirection::Down {
14109 let multi_buffer = self.buffer.read(cx);
14110 let snapshot = multi_buffer.snapshot(cx);
14111 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14112 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14113 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14114 let buffer_snapshot = buffer.read(cx).snapshot();
14115 let excerpt_end_row =
14116 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14117 let last_row = buffer_snapshot.max_point().row;
14118 let lines_below = last_row.saturating_sub(excerpt_end_row);
14119 should_scroll_up = lines_below >= lines_to_expand;
14120 }
14121 }
14122 }
14123 }
14124
14125 self.buffer.update(cx, |buffer, cx| {
14126 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14127 });
14128
14129 if should_scroll_up {
14130 let new_scroll_position =
14131 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14132 self.set_scroll_position(new_scroll_position, window, cx);
14133 }
14134 }
14135
14136 pub fn go_to_singleton_buffer_point(
14137 &mut self,
14138 point: Point,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) {
14142 self.go_to_singleton_buffer_range(point..point, window, cx);
14143 }
14144
14145 pub fn go_to_singleton_buffer_range(
14146 &mut self,
14147 range: Range<Point>,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) {
14151 let multibuffer = self.buffer().read(cx);
14152 let Some(buffer) = multibuffer.as_singleton() else {
14153 return;
14154 };
14155 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14156 return;
14157 };
14158 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14159 return;
14160 };
14161 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14162 s.select_anchor_ranges([start..end])
14163 });
14164 }
14165
14166 pub fn go_to_diagnostic(
14167 &mut self,
14168 _: &GoToDiagnostic,
14169 window: &mut Window,
14170 cx: &mut Context<Self>,
14171 ) {
14172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14173 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14174 }
14175
14176 pub fn go_to_prev_diagnostic(
14177 &mut self,
14178 _: &GoToPreviousDiagnostic,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) {
14182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14183 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14184 }
14185
14186 pub fn go_to_diagnostic_impl(
14187 &mut self,
14188 direction: Direction,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) {
14192 let buffer = self.buffer.read(cx).snapshot(cx);
14193 let selection = self.selections.newest::<usize>(cx);
14194
14195 let mut active_group_id = None;
14196 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14197 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14198 active_group_id = Some(active_group.group_id);
14199 }
14200 }
14201
14202 fn filtered(
14203 snapshot: EditorSnapshot,
14204 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14205 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14206 diagnostics
14207 .filter(|entry| entry.range.start != entry.range.end)
14208 .filter(|entry| !entry.diagnostic.is_unnecessary)
14209 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14210 }
14211
14212 let snapshot = self.snapshot(window, cx);
14213 let before = filtered(
14214 snapshot.clone(),
14215 buffer
14216 .diagnostics_in_range(0..selection.start)
14217 .filter(|entry| entry.range.start <= selection.start),
14218 );
14219 let after = filtered(
14220 snapshot,
14221 buffer
14222 .diagnostics_in_range(selection.start..buffer.len())
14223 .filter(|entry| entry.range.start >= selection.start),
14224 );
14225
14226 let mut found: Option<DiagnosticEntry<usize>> = None;
14227 if direction == Direction::Prev {
14228 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14229 {
14230 for diagnostic in prev_diagnostics.into_iter().rev() {
14231 if diagnostic.range.start != selection.start
14232 || active_group_id
14233 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14234 {
14235 found = Some(diagnostic);
14236 break 'outer;
14237 }
14238 }
14239 }
14240 } else {
14241 for diagnostic in after.chain(before) {
14242 if diagnostic.range.start != selection.start
14243 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14244 {
14245 found = Some(diagnostic);
14246 break;
14247 }
14248 }
14249 }
14250 let Some(next_diagnostic) = found else {
14251 return;
14252 };
14253
14254 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14255 return;
14256 };
14257 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14258 s.select_ranges(vec![
14259 next_diagnostic.range.start..next_diagnostic.range.start,
14260 ])
14261 });
14262 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14263 self.refresh_inline_completion(false, true, window, cx);
14264 }
14265
14266 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14267 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14268 let snapshot = self.snapshot(window, cx);
14269 let selection = self.selections.newest::<Point>(cx);
14270 self.go_to_hunk_before_or_after_position(
14271 &snapshot,
14272 selection.head(),
14273 Direction::Next,
14274 window,
14275 cx,
14276 );
14277 }
14278
14279 pub fn go_to_hunk_before_or_after_position(
14280 &mut self,
14281 snapshot: &EditorSnapshot,
14282 position: Point,
14283 direction: Direction,
14284 window: &mut Window,
14285 cx: &mut Context<Editor>,
14286 ) {
14287 let row = if direction == Direction::Next {
14288 self.hunk_after_position(snapshot, position)
14289 .map(|hunk| hunk.row_range.start)
14290 } else {
14291 self.hunk_before_position(snapshot, position)
14292 };
14293
14294 if let Some(row) = row {
14295 let destination = Point::new(row.0, 0);
14296 let autoscroll = Autoscroll::center();
14297
14298 self.unfold_ranges(&[destination..destination], false, false, cx);
14299 self.change_selections(Some(autoscroll), window, cx, |s| {
14300 s.select_ranges([destination..destination]);
14301 });
14302 }
14303 }
14304
14305 fn hunk_after_position(
14306 &mut self,
14307 snapshot: &EditorSnapshot,
14308 position: Point,
14309 ) -> Option<MultiBufferDiffHunk> {
14310 snapshot
14311 .buffer_snapshot
14312 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14313 .find(|hunk| hunk.row_range.start.0 > position.row)
14314 .or_else(|| {
14315 snapshot
14316 .buffer_snapshot
14317 .diff_hunks_in_range(Point::zero()..position)
14318 .find(|hunk| hunk.row_range.end.0 < position.row)
14319 })
14320 }
14321
14322 fn go_to_prev_hunk(
14323 &mut self,
14324 _: &GoToPreviousHunk,
14325 window: &mut Window,
14326 cx: &mut Context<Self>,
14327 ) {
14328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14329 let snapshot = self.snapshot(window, cx);
14330 let selection = self.selections.newest::<Point>(cx);
14331 self.go_to_hunk_before_or_after_position(
14332 &snapshot,
14333 selection.head(),
14334 Direction::Prev,
14335 window,
14336 cx,
14337 );
14338 }
14339
14340 fn hunk_before_position(
14341 &mut self,
14342 snapshot: &EditorSnapshot,
14343 position: Point,
14344 ) -> Option<MultiBufferRow> {
14345 snapshot
14346 .buffer_snapshot
14347 .diff_hunk_before(position)
14348 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14349 }
14350
14351 fn go_to_next_change(
14352 &mut self,
14353 _: &GoToNextChange,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 if let Some(selections) = self
14358 .change_list
14359 .next_change(1, Direction::Next)
14360 .map(|s| s.to_vec())
14361 {
14362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14363 let map = s.display_map();
14364 s.select_display_ranges(selections.iter().map(|a| {
14365 let point = a.to_display_point(&map);
14366 point..point
14367 }))
14368 })
14369 }
14370 }
14371
14372 fn go_to_previous_change(
14373 &mut self,
14374 _: &GoToPreviousChange,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 if let Some(selections) = self
14379 .change_list
14380 .next_change(1, Direction::Prev)
14381 .map(|s| s.to_vec())
14382 {
14383 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14384 let map = s.display_map();
14385 s.select_display_ranges(selections.iter().map(|a| {
14386 let point = a.to_display_point(&map);
14387 point..point
14388 }))
14389 })
14390 }
14391 }
14392
14393 fn go_to_line<T: 'static>(
14394 &mut self,
14395 position: Anchor,
14396 highlight_color: Option<Hsla>,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 let snapshot = self.snapshot(window, cx).display_snapshot;
14401 let position = position.to_point(&snapshot.buffer_snapshot);
14402 let start = snapshot
14403 .buffer_snapshot
14404 .clip_point(Point::new(position.row, 0), Bias::Left);
14405 let end = start + Point::new(1, 0);
14406 let start = snapshot.buffer_snapshot.anchor_before(start);
14407 let end = snapshot.buffer_snapshot.anchor_before(end);
14408
14409 self.highlight_rows::<T>(
14410 start..end,
14411 highlight_color
14412 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14413 Default::default(),
14414 cx,
14415 );
14416
14417 if self.buffer.read(cx).is_singleton() {
14418 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14419 }
14420 }
14421
14422 pub fn go_to_definition(
14423 &mut self,
14424 _: &GoToDefinition,
14425 window: &mut Window,
14426 cx: &mut Context<Self>,
14427 ) -> Task<Result<Navigated>> {
14428 let definition =
14429 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14430 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14431 cx.spawn_in(window, async move |editor, cx| {
14432 if definition.await? == Navigated::Yes {
14433 return Ok(Navigated::Yes);
14434 }
14435 match fallback_strategy {
14436 GoToDefinitionFallback::None => Ok(Navigated::No),
14437 GoToDefinitionFallback::FindAllReferences => {
14438 match editor.update_in(cx, |editor, window, cx| {
14439 editor.find_all_references(&FindAllReferences, window, cx)
14440 })? {
14441 Some(references) => references.await,
14442 None => Ok(Navigated::No),
14443 }
14444 }
14445 }
14446 })
14447 }
14448
14449 pub fn go_to_declaration(
14450 &mut self,
14451 _: &GoToDeclaration,
14452 window: &mut Window,
14453 cx: &mut Context<Self>,
14454 ) -> Task<Result<Navigated>> {
14455 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14456 }
14457
14458 pub fn go_to_declaration_split(
14459 &mut self,
14460 _: &GoToDeclaration,
14461 window: &mut Window,
14462 cx: &mut Context<Self>,
14463 ) -> Task<Result<Navigated>> {
14464 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14465 }
14466
14467 pub fn go_to_implementation(
14468 &mut self,
14469 _: &GoToImplementation,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Task<Result<Navigated>> {
14473 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14474 }
14475
14476 pub fn go_to_implementation_split(
14477 &mut self,
14478 _: &GoToImplementationSplit,
14479 window: &mut Window,
14480 cx: &mut Context<Self>,
14481 ) -> Task<Result<Navigated>> {
14482 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14483 }
14484
14485 pub fn go_to_type_definition(
14486 &mut self,
14487 _: &GoToTypeDefinition,
14488 window: &mut Window,
14489 cx: &mut Context<Self>,
14490 ) -> Task<Result<Navigated>> {
14491 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14492 }
14493
14494 pub fn go_to_definition_split(
14495 &mut self,
14496 _: &GoToDefinitionSplit,
14497 window: &mut Window,
14498 cx: &mut Context<Self>,
14499 ) -> Task<Result<Navigated>> {
14500 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14501 }
14502
14503 pub fn go_to_type_definition_split(
14504 &mut self,
14505 _: &GoToTypeDefinitionSplit,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) -> Task<Result<Navigated>> {
14509 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14510 }
14511
14512 fn go_to_definition_of_kind(
14513 &mut self,
14514 kind: GotoDefinitionKind,
14515 split: bool,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) -> Task<Result<Navigated>> {
14519 let Some(provider) = self.semantics_provider.clone() else {
14520 return Task::ready(Ok(Navigated::No));
14521 };
14522 let head = self.selections.newest::<usize>(cx).head();
14523 let buffer = self.buffer.read(cx);
14524 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14525 text_anchor
14526 } else {
14527 return Task::ready(Ok(Navigated::No));
14528 };
14529
14530 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14531 return Task::ready(Ok(Navigated::No));
14532 };
14533
14534 cx.spawn_in(window, async move |editor, cx| {
14535 let definitions = definitions.await?;
14536 let navigated = editor
14537 .update_in(cx, |editor, window, cx| {
14538 editor.navigate_to_hover_links(
14539 Some(kind),
14540 definitions
14541 .into_iter()
14542 .filter(|location| {
14543 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14544 })
14545 .map(HoverLink::Text)
14546 .collect::<Vec<_>>(),
14547 split,
14548 window,
14549 cx,
14550 )
14551 })?
14552 .await?;
14553 anyhow::Ok(navigated)
14554 })
14555 }
14556
14557 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14558 let selection = self.selections.newest_anchor();
14559 let head = selection.head();
14560 let tail = selection.tail();
14561
14562 let Some((buffer, start_position)) =
14563 self.buffer.read(cx).text_anchor_for_position(head, cx)
14564 else {
14565 return;
14566 };
14567
14568 let end_position = if head != tail {
14569 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14570 return;
14571 };
14572 Some(pos)
14573 } else {
14574 None
14575 };
14576
14577 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14578 let url = if let Some(end_pos) = end_position {
14579 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14580 } else {
14581 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14582 };
14583
14584 if let Some(url) = url {
14585 editor.update(cx, |_, cx| {
14586 cx.open_url(&url);
14587 })
14588 } else {
14589 Ok(())
14590 }
14591 });
14592
14593 url_finder.detach();
14594 }
14595
14596 pub fn open_selected_filename(
14597 &mut self,
14598 _: &OpenSelectedFilename,
14599 window: &mut Window,
14600 cx: &mut Context<Self>,
14601 ) {
14602 let Some(workspace) = self.workspace() else {
14603 return;
14604 };
14605
14606 let position = self.selections.newest_anchor().head();
14607
14608 let Some((buffer, buffer_position)) =
14609 self.buffer.read(cx).text_anchor_for_position(position, cx)
14610 else {
14611 return;
14612 };
14613
14614 let project = self.project.clone();
14615
14616 cx.spawn_in(window, async move |_, cx| {
14617 let result = find_file(&buffer, project, buffer_position, cx).await;
14618
14619 if let Some((_, path)) = result {
14620 workspace
14621 .update_in(cx, |workspace, window, cx| {
14622 workspace.open_resolved_path(path, window, cx)
14623 })?
14624 .await?;
14625 }
14626 anyhow::Ok(())
14627 })
14628 .detach();
14629 }
14630
14631 pub(crate) fn navigate_to_hover_links(
14632 &mut self,
14633 kind: Option<GotoDefinitionKind>,
14634 mut definitions: Vec<HoverLink>,
14635 split: bool,
14636 window: &mut Window,
14637 cx: &mut Context<Editor>,
14638 ) -> Task<Result<Navigated>> {
14639 // If there is one definition, just open it directly
14640 if definitions.len() == 1 {
14641 let definition = definitions.pop().unwrap();
14642
14643 enum TargetTaskResult {
14644 Location(Option<Location>),
14645 AlreadyNavigated,
14646 }
14647
14648 let target_task = match definition {
14649 HoverLink::Text(link) => {
14650 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14651 }
14652 HoverLink::InlayHint(lsp_location, server_id) => {
14653 let computation =
14654 self.compute_target_location(lsp_location, server_id, window, cx);
14655 cx.background_spawn(async move {
14656 let location = computation.await?;
14657 Ok(TargetTaskResult::Location(location))
14658 })
14659 }
14660 HoverLink::Url(url) => {
14661 cx.open_url(&url);
14662 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14663 }
14664 HoverLink::File(path) => {
14665 if let Some(workspace) = self.workspace() {
14666 cx.spawn_in(window, async move |_, cx| {
14667 workspace
14668 .update_in(cx, |workspace, window, cx| {
14669 workspace.open_resolved_path(path, window, cx)
14670 })?
14671 .await
14672 .map(|_| TargetTaskResult::AlreadyNavigated)
14673 })
14674 } else {
14675 Task::ready(Ok(TargetTaskResult::Location(None)))
14676 }
14677 }
14678 };
14679 cx.spawn_in(window, async move |editor, cx| {
14680 let target = match target_task.await.context("target resolution task")? {
14681 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14682 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14683 TargetTaskResult::Location(Some(target)) => target,
14684 };
14685
14686 editor.update_in(cx, |editor, window, cx| {
14687 let Some(workspace) = editor.workspace() else {
14688 return Navigated::No;
14689 };
14690 let pane = workspace.read(cx).active_pane().clone();
14691
14692 let range = target.range.to_point(target.buffer.read(cx));
14693 let range = editor.range_for_match(&range);
14694 let range = collapse_multiline_range(range);
14695
14696 if !split
14697 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14698 {
14699 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14700 } else {
14701 window.defer(cx, move |window, cx| {
14702 let target_editor: Entity<Self> =
14703 workspace.update(cx, |workspace, cx| {
14704 let pane = if split {
14705 workspace.adjacent_pane(window, cx)
14706 } else {
14707 workspace.active_pane().clone()
14708 };
14709
14710 workspace.open_project_item(
14711 pane,
14712 target.buffer.clone(),
14713 true,
14714 true,
14715 window,
14716 cx,
14717 )
14718 });
14719 target_editor.update(cx, |target_editor, cx| {
14720 // When selecting a definition in a different buffer, disable the nav history
14721 // to avoid creating a history entry at the previous cursor location.
14722 pane.update(cx, |pane, _| pane.disable_history());
14723 target_editor.go_to_singleton_buffer_range(range, window, cx);
14724 pane.update(cx, |pane, _| pane.enable_history());
14725 });
14726 });
14727 }
14728 Navigated::Yes
14729 })
14730 })
14731 } else if !definitions.is_empty() {
14732 cx.spawn_in(window, async move |editor, cx| {
14733 let (title, location_tasks, workspace) = editor
14734 .update_in(cx, |editor, window, cx| {
14735 let tab_kind = match kind {
14736 Some(GotoDefinitionKind::Implementation) => "Implementations",
14737 _ => "Definitions",
14738 };
14739 let title = definitions
14740 .iter()
14741 .find_map(|definition| match definition {
14742 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14743 let buffer = origin.buffer.read(cx);
14744 format!(
14745 "{} for {}",
14746 tab_kind,
14747 buffer
14748 .text_for_range(origin.range.clone())
14749 .collect::<String>()
14750 )
14751 }),
14752 HoverLink::InlayHint(_, _) => None,
14753 HoverLink::Url(_) => None,
14754 HoverLink::File(_) => None,
14755 })
14756 .unwrap_or(tab_kind.to_string());
14757 let location_tasks = definitions
14758 .into_iter()
14759 .map(|definition| match definition {
14760 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14761 HoverLink::InlayHint(lsp_location, server_id) => editor
14762 .compute_target_location(lsp_location, server_id, window, cx),
14763 HoverLink::Url(_) => Task::ready(Ok(None)),
14764 HoverLink::File(_) => Task::ready(Ok(None)),
14765 })
14766 .collect::<Vec<_>>();
14767 (title, location_tasks, editor.workspace().clone())
14768 })
14769 .context("location tasks preparation")?;
14770
14771 let locations = future::join_all(location_tasks)
14772 .await
14773 .into_iter()
14774 .filter_map(|location| location.transpose())
14775 .collect::<Result<_>>()
14776 .context("location tasks")?;
14777
14778 let Some(workspace) = workspace else {
14779 return Ok(Navigated::No);
14780 };
14781 let opened = workspace
14782 .update_in(cx, |workspace, window, cx| {
14783 Self::open_locations_in_multibuffer(
14784 workspace,
14785 locations,
14786 title,
14787 split,
14788 MultibufferSelectionMode::First,
14789 window,
14790 cx,
14791 )
14792 })
14793 .ok();
14794
14795 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14796 })
14797 } else {
14798 Task::ready(Ok(Navigated::No))
14799 }
14800 }
14801
14802 fn compute_target_location(
14803 &self,
14804 lsp_location: lsp::Location,
14805 server_id: LanguageServerId,
14806 window: &mut Window,
14807 cx: &mut Context<Self>,
14808 ) -> Task<anyhow::Result<Option<Location>>> {
14809 let Some(project) = self.project.clone() else {
14810 return Task::ready(Ok(None));
14811 };
14812
14813 cx.spawn_in(window, async move |editor, cx| {
14814 let location_task = editor.update(cx, |_, cx| {
14815 project.update(cx, |project, cx| {
14816 let language_server_name = project
14817 .language_server_statuses(cx)
14818 .find(|(id, _)| server_id == *id)
14819 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14820 language_server_name.map(|language_server_name| {
14821 project.open_local_buffer_via_lsp(
14822 lsp_location.uri.clone(),
14823 server_id,
14824 language_server_name,
14825 cx,
14826 )
14827 })
14828 })
14829 })?;
14830 let location = match location_task {
14831 Some(task) => Some({
14832 let target_buffer_handle = task.await.context("open local buffer")?;
14833 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14834 let target_start = target_buffer
14835 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14836 let target_end = target_buffer
14837 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14838 target_buffer.anchor_after(target_start)
14839 ..target_buffer.anchor_before(target_end)
14840 })?;
14841 Location {
14842 buffer: target_buffer_handle,
14843 range,
14844 }
14845 }),
14846 None => None,
14847 };
14848 Ok(location)
14849 })
14850 }
14851
14852 pub fn find_all_references(
14853 &mut self,
14854 _: &FindAllReferences,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) -> Option<Task<Result<Navigated>>> {
14858 let selection = self.selections.newest::<usize>(cx);
14859 let multi_buffer = self.buffer.read(cx);
14860 let head = selection.head();
14861
14862 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14863 let head_anchor = multi_buffer_snapshot.anchor_at(
14864 head,
14865 if head < selection.tail() {
14866 Bias::Right
14867 } else {
14868 Bias::Left
14869 },
14870 );
14871
14872 match self
14873 .find_all_references_task_sources
14874 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14875 {
14876 Ok(_) => {
14877 log::info!(
14878 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14879 );
14880 return None;
14881 }
14882 Err(i) => {
14883 self.find_all_references_task_sources.insert(i, head_anchor);
14884 }
14885 }
14886
14887 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14888 let workspace = self.workspace()?;
14889 let project = workspace.read(cx).project().clone();
14890 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14891 Some(cx.spawn_in(window, async move |editor, cx| {
14892 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14893 if let Ok(i) = editor
14894 .find_all_references_task_sources
14895 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14896 {
14897 editor.find_all_references_task_sources.remove(i);
14898 }
14899 });
14900
14901 let locations = references.await?;
14902 if locations.is_empty() {
14903 return anyhow::Ok(Navigated::No);
14904 }
14905
14906 workspace.update_in(cx, |workspace, window, cx| {
14907 let title = locations
14908 .first()
14909 .as_ref()
14910 .map(|location| {
14911 let buffer = location.buffer.read(cx);
14912 format!(
14913 "References to `{}`",
14914 buffer
14915 .text_for_range(location.range.clone())
14916 .collect::<String>()
14917 )
14918 })
14919 .unwrap();
14920 Self::open_locations_in_multibuffer(
14921 workspace,
14922 locations,
14923 title,
14924 false,
14925 MultibufferSelectionMode::First,
14926 window,
14927 cx,
14928 );
14929 Navigated::Yes
14930 })
14931 }))
14932 }
14933
14934 /// Opens a multibuffer with the given project locations in it
14935 pub fn open_locations_in_multibuffer(
14936 workspace: &mut Workspace,
14937 mut locations: Vec<Location>,
14938 title: String,
14939 split: bool,
14940 multibuffer_selection_mode: MultibufferSelectionMode,
14941 window: &mut Window,
14942 cx: &mut Context<Workspace>,
14943 ) {
14944 // If there are multiple definitions, open them in a multibuffer
14945 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14946 let mut locations = locations.into_iter().peekable();
14947 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14948 let capability = workspace.project().read(cx).capability();
14949
14950 let excerpt_buffer = cx.new(|cx| {
14951 let mut multibuffer = MultiBuffer::new(capability);
14952 while let Some(location) = locations.next() {
14953 let buffer = location.buffer.read(cx);
14954 let mut ranges_for_buffer = Vec::new();
14955 let range = location.range.to_point(buffer);
14956 ranges_for_buffer.push(range.clone());
14957
14958 while let Some(next_location) = locations.peek() {
14959 if next_location.buffer == location.buffer {
14960 ranges_for_buffer.push(next_location.range.to_point(buffer));
14961 locations.next();
14962 } else {
14963 break;
14964 }
14965 }
14966
14967 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14968 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14969 PathKey::for_buffer(&location.buffer, cx),
14970 location.buffer.clone(),
14971 ranges_for_buffer,
14972 DEFAULT_MULTIBUFFER_CONTEXT,
14973 cx,
14974 );
14975 ranges.extend(new_ranges)
14976 }
14977
14978 multibuffer.with_title(title)
14979 });
14980
14981 let editor = cx.new(|cx| {
14982 Editor::for_multibuffer(
14983 excerpt_buffer,
14984 Some(workspace.project().clone()),
14985 window,
14986 cx,
14987 )
14988 });
14989 editor.update(cx, |editor, cx| {
14990 match multibuffer_selection_mode {
14991 MultibufferSelectionMode::First => {
14992 if let Some(first_range) = ranges.first() {
14993 editor.change_selections(None, window, cx, |selections| {
14994 selections.clear_disjoint();
14995 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14996 });
14997 }
14998 editor.highlight_background::<Self>(
14999 &ranges,
15000 |theme| theme.editor_highlighted_line_background,
15001 cx,
15002 );
15003 }
15004 MultibufferSelectionMode::All => {
15005 editor.change_selections(None, window, cx, |selections| {
15006 selections.clear_disjoint();
15007 selections.select_anchor_ranges(ranges);
15008 });
15009 }
15010 }
15011 editor.register_buffers_with_language_servers(cx);
15012 });
15013
15014 let item = Box::new(editor);
15015 let item_id = item.item_id();
15016
15017 if split {
15018 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15019 } else {
15020 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15021 let (preview_item_id, preview_item_idx) =
15022 workspace.active_pane().read_with(cx, |pane, _| {
15023 (pane.preview_item_id(), pane.preview_item_idx())
15024 });
15025
15026 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15027
15028 if let Some(preview_item_id) = preview_item_id {
15029 workspace.active_pane().update(cx, |pane, cx| {
15030 pane.remove_item(preview_item_id, false, false, window, cx);
15031 });
15032 }
15033 } else {
15034 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15035 }
15036 }
15037 workspace.active_pane().update(cx, |pane, cx| {
15038 pane.set_preview_item_id(Some(item_id), cx);
15039 });
15040 }
15041
15042 pub fn rename(
15043 &mut self,
15044 _: &Rename,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) -> Option<Task<Result<()>>> {
15048 use language::ToOffset as _;
15049
15050 let provider = self.semantics_provider.clone()?;
15051 let selection = self.selections.newest_anchor().clone();
15052 let (cursor_buffer, cursor_buffer_position) = self
15053 .buffer
15054 .read(cx)
15055 .text_anchor_for_position(selection.head(), cx)?;
15056 let (tail_buffer, cursor_buffer_position_end) = self
15057 .buffer
15058 .read(cx)
15059 .text_anchor_for_position(selection.tail(), cx)?;
15060 if tail_buffer != cursor_buffer {
15061 return None;
15062 }
15063
15064 let snapshot = cursor_buffer.read(cx).snapshot();
15065 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15066 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15067 let prepare_rename = provider
15068 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15069 .unwrap_or_else(|| Task::ready(Ok(None)));
15070 drop(snapshot);
15071
15072 Some(cx.spawn_in(window, async move |this, cx| {
15073 let rename_range = if let Some(range) = prepare_rename.await? {
15074 Some(range)
15075 } else {
15076 this.update(cx, |this, cx| {
15077 let buffer = this.buffer.read(cx).snapshot(cx);
15078 let mut buffer_highlights = this
15079 .document_highlights_for_position(selection.head(), &buffer)
15080 .filter(|highlight| {
15081 highlight.start.excerpt_id == selection.head().excerpt_id
15082 && highlight.end.excerpt_id == selection.head().excerpt_id
15083 });
15084 buffer_highlights
15085 .next()
15086 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15087 })?
15088 };
15089 if let Some(rename_range) = rename_range {
15090 this.update_in(cx, |this, window, cx| {
15091 let snapshot = cursor_buffer.read(cx).snapshot();
15092 let rename_buffer_range = rename_range.to_offset(&snapshot);
15093 let cursor_offset_in_rename_range =
15094 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15095 let cursor_offset_in_rename_range_end =
15096 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15097
15098 this.take_rename(false, window, cx);
15099 let buffer = this.buffer.read(cx).read(cx);
15100 let cursor_offset = selection.head().to_offset(&buffer);
15101 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15102 let rename_end = rename_start + rename_buffer_range.len();
15103 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15104 let mut old_highlight_id = None;
15105 let old_name: Arc<str> = buffer
15106 .chunks(rename_start..rename_end, true)
15107 .map(|chunk| {
15108 if old_highlight_id.is_none() {
15109 old_highlight_id = chunk.syntax_highlight_id;
15110 }
15111 chunk.text
15112 })
15113 .collect::<String>()
15114 .into();
15115
15116 drop(buffer);
15117
15118 // Position the selection in the rename editor so that it matches the current selection.
15119 this.show_local_selections = false;
15120 let rename_editor = cx.new(|cx| {
15121 let mut editor = Editor::single_line(window, cx);
15122 editor.buffer.update(cx, |buffer, cx| {
15123 buffer.edit([(0..0, old_name.clone())], None, cx)
15124 });
15125 let rename_selection_range = match cursor_offset_in_rename_range
15126 .cmp(&cursor_offset_in_rename_range_end)
15127 {
15128 Ordering::Equal => {
15129 editor.select_all(&SelectAll, window, cx);
15130 return editor;
15131 }
15132 Ordering::Less => {
15133 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15134 }
15135 Ordering::Greater => {
15136 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15137 }
15138 };
15139 if rename_selection_range.end > old_name.len() {
15140 editor.select_all(&SelectAll, window, cx);
15141 } else {
15142 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15143 s.select_ranges([rename_selection_range]);
15144 });
15145 }
15146 editor
15147 });
15148 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15149 if e == &EditorEvent::Focused {
15150 cx.emit(EditorEvent::FocusedIn)
15151 }
15152 })
15153 .detach();
15154
15155 let write_highlights =
15156 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15157 let read_highlights =
15158 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15159 let ranges = write_highlights
15160 .iter()
15161 .flat_map(|(_, ranges)| ranges.iter())
15162 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15163 .cloned()
15164 .collect();
15165
15166 this.highlight_text::<Rename>(
15167 ranges,
15168 HighlightStyle {
15169 fade_out: Some(0.6),
15170 ..Default::default()
15171 },
15172 cx,
15173 );
15174 let rename_focus_handle = rename_editor.focus_handle(cx);
15175 window.focus(&rename_focus_handle);
15176 let block_id = this.insert_blocks(
15177 [BlockProperties {
15178 style: BlockStyle::Flex,
15179 placement: BlockPlacement::Below(range.start),
15180 height: Some(1),
15181 render: Arc::new({
15182 let rename_editor = rename_editor.clone();
15183 move |cx: &mut BlockContext| {
15184 let mut text_style = cx.editor_style.text.clone();
15185 if let Some(highlight_style) = old_highlight_id
15186 .and_then(|h| h.style(&cx.editor_style.syntax))
15187 {
15188 text_style = text_style.highlight(highlight_style);
15189 }
15190 div()
15191 .block_mouse_except_scroll()
15192 .pl(cx.anchor_x)
15193 .child(EditorElement::new(
15194 &rename_editor,
15195 EditorStyle {
15196 background: cx.theme().system().transparent,
15197 local_player: cx.editor_style.local_player,
15198 text: text_style,
15199 scrollbar_width: cx.editor_style.scrollbar_width,
15200 syntax: cx.editor_style.syntax.clone(),
15201 status: cx.editor_style.status.clone(),
15202 inlay_hints_style: HighlightStyle {
15203 font_weight: Some(FontWeight::BOLD),
15204 ..make_inlay_hints_style(cx.app)
15205 },
15206 inline_completion_styles: make_suggestion_styles(
15207 cx.app,
15208 ),
15209 ..EditorStyle::default()
15210 },
15211 ))
15212 .into_any_element()
15213 }
15214 }),
15215 priority: 0,
15216 render_in_minimap: true,
15217 }],
15218 Some(Autoscroll::fit()),
15219 cx,
15220 )[0];
15221 this.pending_rename = Some(RenameState {
15222 range,
15223 old_name,
15224 editor: rename_editor,
15225 block_id,
15226 });
15227 })?;
15228 }
15229
15230 Ok(())
15231 }))
15232 }
15233
15234 pub fn confirm_rename(
15235 &mut self,
15236 _: &ConfirmRename,
15237 window: &mut Window,
15238 cx: &mut Context<Self>,
15239 ) -> Option<Task<Result<()>>> {
15240 let rename = self.take_rename(false, window, cx)?;
15241 let workspace = self.workspace()?.downgrade();
15242 let (buffer, start) = self
15243 .buffer
15244 .read(cx)
15245 .text_anchor_for_position(rename.range.start, cx)?;
15246 let (end_buffer, _) = self
15247 .buffer
15248 .read(cx)
15249 .text_anchor_for_position(rename.range.end, cx)?;
15250 if buffer != end_buffer {
15251 return None;
15252 }
15253
15254 let old_name = rename.old_name;
15255 let new_name = rename.editor.read(cx).text(cx);
15256
15257 let rename = self.semantics_provider.as_ref()?.perform_rename(
15258 &buffer,
15259 start,
15260 new_name.clone(),
15261 cx,
15262 )?;
15263
15264 Some(cx.spawn_in(window, async move |editor, cx| {
15265 let project_transaction = rename.await?;
15266 Self::open_project_transaction(
15267 &editor,
15268 workspace,
15269 project_transaction,
15270 format!("Rename: {} → {}", old_name, new_name),
15271 cx,
15272 )
15273 .await?;
15274
15275 editor.update(cx, |editor, cx| {
15276 editor.refresh_document_highlights(cx);
15277 })?;
15278 Ok(())
15279 }))
15280 }
15281
15282 fn take_rename(
15283 &mut self,
15284 moving_cursor: bool,
15285 window: &mut Window,
15286 cx: &mut Context<Self>,
15287 ) -> Option<RenameState> {
15288 let rename = self.pending_rename.take()?;
15289 if rename.editor.focus_handle(cx).is_focused(window) {
15290 window.focus(&self.focus_handle);
15291 }
15292
15293 self.remove_blocks(
15294 [rename.block_id].into_iter().collect(),
15295 Some(Autoscroll::fit()),
15296 cx,
15297 );
15298 self.clear_highlights::<Rename>(cx);
15299 self.show_local_selections = true;
15300
15301 if moving_cursor {
15302 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15303 editor.selections.newest::<usize>(cx).head()
15304 });
15305
15306 // Update the selection to match the position of the selection inside
15307 // the rename editor.
15308 let snapshot = self.buffer.read(cx).read(cx);
15309 let rename_range = rename.range.to_offset(&snapshot);
15310 let cursor_in_editor = snapshot
15311 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15312 .min(rename_range.end);
15313 drop(snapshot);
15314
15315 self.change_selections(None, window, cx, |s| {
15316 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15317 });
15318 } else {
15319 self.refresh_document_highlights(cx);
15320 }
15321
15322 Some(rename)
15323 }
15324
15325 pub fn pending_rename(&self) -> Option<&RenameState> {
15326 self.pending_rename.as_ref()
15327 }
15328
15329 fn format(
15330 &mut self,
15331 _: &Format,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) -> Option<Task<Result<()>>> {
15335 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15336
15337 let project = match &self.project {
15338 Some(project) => project.clone(),
15339 None => return None,
15340 };
15341
15342 Some(self.perform_format(
15343 project,
15344 FormatTrigger::Manual,
15345 FormatTarget::Buffers,
15346 window,
15347 cx,
15348 ))
15349 }
15350
15351 fn format_selections(
15352 &mut self,
15353 _: &FormatSelections,
15354 window: &mut Window,
15355 cx: &mut Context<Self>,
15356 ) -> Option<Task<Result<()>>> {
15357 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15358
15359 let project = match &self.project {
15360 Some(project) => project.clone(),
15361 None => return None,
15362 };
15363
15364 let ranges = self
15365 .selections
15366 .all_adjusted(cx)
15367 .into_iter()
15368 .map(|selection| selection.range())
15369 .collect_vec();
15370
15371 Some(self.perform_format(
15372 project,
15373 FormatTrigger::Manual,
15374 FormatTarget::Ranges(ranges),
15375 window,
15376 cx,
15377 ))
15378 }
15379
15380 fn perform_format(
15381 &mut self,
15382 project: Entity<Project>,
15383 trigger: FormatTrigger,
15384 target: FormatTarget,
15385 window: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) -> Task<Result<()>> {
15388 let buffer = self.buffer.clone();
15389 let (buffers, target) = match target {
15390 FormatTarget::Buffers => {
15391 let mut buffers = buffer.read(cx).all_buffers();
15392 if trigger == FormatTrigger::Save {
15393 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15394 }
15395 (buffers, LspFormatTarget::Buffers)
15396 }
15397 FormatTarget::Ranges(selection_ranges) => {
15398 let multi_buffer = buffer.read(cx);
15399 let snapshot = multi_buffer.read(cx);
15400 let mut buffers = HashSet::default();
15401 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15402 BTreeMap::new();
15403 for selection_range in selection_ranges {
15404 for (buffer, buffer_range, _) in
15405 snapshot.range_to_buffer_ranges(selection_range)
15406 {
15407 let buffer_id = buffer.remote_id();
15408 let start = buffer.anchor_before(buffer_range.start);
15409 let end = buffer.anchor_after(buffer_range.end);
15410 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15411 buffer_id_to_ranges
15412 .entry(buffer_id)
15413 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15414 .or_insert_with(|| vec![start..end]);
15415 }
15416 }
15417 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15418 }
15419 };
15420
15421 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15422 let selections_prev = transaction_id_prev
15423 .and_then(|transaction_id_prev| {
15424 // default to selections as they were after the last edit, if we have them,
15425 // instead of how they are now.
15426 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15427 // will take you back to where you made the last edit, instead of staying where you scrolled
15428 self.selection_history
15429 .transaction(transaction_id_prev)
15430 .map(|t| t.0.clone())
15431 })
15432 .unwrap_or_else(|| {
15433 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15434 self.selections.disjoint_anchors()
15435 });
15436
15437 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15438 let format = project.update(cx, |project, cx| {
15439 project.format(buffers, target, true, trigger, cx)
15440 });
15441
15442 cx.spawn_in(window, async move |editor, cx| {
15443 let transaction = futures::select_biased! {
15444 transaction = format.log_err().fuse() => transaction,
15445 () = timeout => {
15446 log::warn!("timed out waiting for formatting");
15447 None
15448 }
15449 };
15450
15451 buffer
15452 .update(cx, |buffer, cx| {
15453 if let Some(transaction) = transaction {
15454 if !buffer.is_singleton() {
15455 buffer.push_transaction(&transaction.0, cx);
15456 }
15457 }
15458 cx.notify();
15459 })
15460 .ok();
15461
15462 if let Some(transaction_id_now) =
15463 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15464 {
15465 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15466 if has_new_transaction {
15467 _ = editor.update(cx, |editor, _| {
15468 editor
15469 .selection_history
15470 .insert_transaction(transaction_id_now, selections_prev);
15471 });
15472 }
15473 }
15474
15475 Ok(())
15476 })
15477 }
15478
15479 fn organize_imports(
15480 &mut self,
15481 _: &OrganizeImports,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) -> Option<Task<Result<()>>> {
15485 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15486 let project = match &self.project {
15487 Some(project) => project.clone(),
15488 None => return None,
15489 };
15490 Some(self.perform_code_action_kind(
15491 project,
15492 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15493 window,
15494 cx,
15495 ))
15496 }
15497
15498 fn perform_code_action_kind(
15499 &mut self,
15500 project: Entity<Project>,
15501 kind: CodeActionKind,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) -> Task<Result<()>> {
15505 let buffer = self.buffer.clone();
15506 let buffers = buffer.read(cx).all_buffers();
15507 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15508 let apply_action = project.update(cx, |project, cx| {
15509 project.apply_code_action_kind(buffers, kind, true, cx)
15510 });
15511 cx.spawn_in(window, async move |_, cx| {
15512 let transaction = futures::select_biased! {
15513 () = timeout => {
15514 log::warn!("timed out waiting for executing code action");
15515 None
15516 }
15517 transaction = apply_action.log_err().fuse() => transaction,
15518 };
15519 buffer
15520 .update(cx, |buffer, cx| {
15521 // check if we need this
15522 if let Some(transaction) = transaction {
15523 if !buffer.is_singleton() {
15524 buffer.push_transaction(&transaction.0, cx);
15525 }
15526 }
15527 cx.notify();
15528 })
15529 .ok();
15530 Ok(())
15531 })
15532 }
15533
15534 fn restart_language_server(
15535 &mut self,
15536 _: &RestartLanguageServer,
15537 _: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) {
15540 if let Some(project) = self.project.clone() {
15541 self.buffer.update(cx, |multi_buffer, cx| {
15542 project.update(cx, |project, cx| {
15543 project.restart_language_servers_for_buffers(
15544 multi_buffer.all_buffers().into_iter().collect(),
15545 cx,
15546 );
15547 });
15548 })
15549 }
15550 }
15551
15552 fn stop_language_server(
15553 &mut self,
15554 _: &StopLanguageServer,
15555 _: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) {
15558 if let Some(project) = self.project.clone() {
15559 self.buffer.update(cx, |multi_buffer, cx| {
15560 project.update(cx, |project, cx| {
15561 project.stop_language_servers_for_buffers(
15562 multi_buffer.all_buffers().into_iter().collect(),
15563 cx,
15564 );
15565 cx.emit(project::Event::RefreshInlayHints);
15566 });
15567 });
15568 }
15569 }
15570
15571 fn cancel_language_server_work(
15572 workspace: &mut Workspace,
15573 _: &actions::CancelLanguageServerWork,
15574 _: &mut Window,
15575 cx: &mut Context<Workspace>,
15576 ) {
15577 let project = workspace.project();
15578 let buffers = workspace
15579 .active_item(cx)
15580 .and_then(|item| item.act_as::<Editor>(cx))
15581 .map_or(HashSet::default(), |editor| {
15582 editor.read(cx).buffer.read(cx).all_buffers()
15583 });
15584 project.update(cx, |project, cx| {
15585 project.cancel_language_server_work_for_buffers(buffers, cx);
15586 });
15587 }
15588
15589 fn show_character_palette(
15590 &mut self,
15591 _: &ShowCharacterPalette,
15592 window: &mut Window,
15593 _: &mut Context<Self>,
15594 ) {
15595 window.show_character_palette();
15596 }
15597
15598 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15599 if self.mode.is_minimap() {
15600 return;
15601 }
15602
15603 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15604 let buffer = self.buffer.read(cx).snapshot(cx);
15605 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15606 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15607 let is_valid = buffer
15608 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15609 .any(|entry| {
15610 entry.diagnostic.is_primary
15611 && !entry.range.is_empty()
15612 && entry.range.start == primary_range_start
15613 && entry.diagnostic.message == active_diagnostics.active_message
15614 });
15615
15616 if !is_valid {
15617 self.dismiss_diagnostics(cx);
15618 }
15619 }
15620 }
15621
15622 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15623 match &self.active_diagnostics {
15624 ActiveDiagnostic::Group(group) => Some(group),
15625 _ => None,
15626 }
15627 }
15628
15629 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15630 self.dismiss_diagnostics(cx);
15631 self.active_diagnostics = ActiveDiagnostic::All;
15632 }
15633
15634 fn activate_diagnostics(
15635 &mut self,
15636 buffer_id: BufferId,
15637 diagnostic: DiagnosticEntry<usize>,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15642 return;
15643 }
15644 self.dismiss_diagnostics(cx);
15645 let snapshot = self.snapshot(window, cx);
15646 let buffer = self.buffer.read(cx).snapshot(cx);
15647 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15648 return;
15649 };
15650
15651 let diagnostic_group = buffer
15652 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15653 .collect::<Vec<_>>();
15654
15655 let blocks =
15656 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15657
15658 let blocks = self.display_map.update(cx, |display_map, cx| {
15659 display_map.insert_blocks(blocks, cx).into_iter().collect()
15660 });
15661 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15662 active_range: buffer.anchor_before(diagnostic.range.start)
15663 ..buffer.anchor_after(diagnostic.range.end),
15664 active_message: diagnostic.diagnostic.message.clone(),
15665 group_id: diagnostic.diagnostic.group_id,
15666 blocks,
15667 });
15668 cx.notify();
15669 }
15670
15671 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15672 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15673 return;
15674 };
15675
15676 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15677 if let ActiveDiagnostic::Group(group) = prev {
15678 self.display_map.update(cx, |display_map, cx| {
15679 display_map.remove_blocks(group.blocks, cx);
15680 });
15681 cx.notify();
15682 }
15683 }
15684
15685 /// Disable inline diagnostics rendering for this editor.
15686 pub fn disable_inline_diagnostics(&mut self) {
15687 self.inline_diagnostics_enabled = false;
15688 self.inline_diagnostics_update = Task::ready(());
15689 self.inline_diagnostics.clear();
15690 }
15691
15692 pub fn diagnostics_enabled(&self) -> bool {
15693 self.mode.is_full()
15694 }
15695
15696 pub fn inline_diagnostics_enabled(&self) -> bool {
15697 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15698 }
15699
15700 pub fn show_inline_diagnostics(&self) -> bool {
15701 self.show_inline_diagnostics
15702 }
15703
15704 pub fn toggle_inline_diagnostics(
15705 &mut self,
15706 _: &ToggleInlineDiagnostics,
15707 window: &mut Window,
15708 cx: &mut Context<Editor>,
15709 ) {
15710 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15711 self.refresh_inline_diagnostics(false, window, cx);
15712 }
15713
15714 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15715 self.diagnostics_max_severity = severity;
15716 self.display_map.update(cx, |display_map, _| {
15717 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15718 });
15719 }
15720
15721 pub fn toggle_diagnostics(
15722 &mut self,
15723 _: &ToggleDiagnostics,
15724 window: &mut Window,
15725 cx: &mut Context<Editor>,
15726 ) {
15727 if !self.diagnostics_enabled() {
15728 return;
15729 }
15730
15731 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15732 EditorSettings::get_global(cx)
15733 .diagnostics_max_severity
15734 .filter(|severity| severity != &DiagnosticSeverity::Off)
15735 .unwrap_or(DiagnosticSeverity::Hint)
15736 } else {
15737 DiagnosticSeverity::Off
15738 };
15739 self.set_max_diagnostics_severity(new_severity, cx);
15740 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15741 self.active_diagnostics = ActiveDiagnostic::None;
15742 self.inline_diagnostics_update = Task::ready(());
15743 self.inline_diagnostics.clear();
15744 } else {
15745 self.refresh_inline_diagnostics(false, window, cx);
15746 }
15747
15748 cx.notify();
15749 }
15750
15751 pub fn toggle_minimap(
15752 &mut self,
15753 _: &ToggleMinimap,
15754 window: &mut Window,
15755 cx: &mut Context<Editor>,
15756 ) {
15757 if self.supports_minimap(cx) {
15758 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15759 }
15760 }
15761
15762 fn refresh_inline_diagnostics(
15763 &mut self,
15764 debounce: bool,
15765 window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) {
15768 let max_severity = ProjectSettings::get_global(cx)
15769 .diagnostics
15770 .inline
15771 .max_severity
15772 .unwrap_or(self.diagnostics_max_severity);
15773
15774 if !self.inline_diagnostics_enabled()
15775 || !self.show_inline_diagnostics
15776 || max_severity == DiagnosticSeverity::Off
15777 {
15778 self.inline_diagnostics_update = Task::ready(());
15779 self.inline_diagnostics.clear();
15780 return;
15781 }
15782
15783 let debounce_ms = ProjectSettings::get_global(cx)
15784 .diagnostics
15785 .inline
15786 .update_debounce_ms;
15787 let debounce = if debounce && debounce_ms > 0 {
15788 Some(Duration::from_millis(debounce_ms))
15789 } else {
15790 None
15791 };
15792 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15793 if let Some(debounce) = debounce {
15794 cx.background_executor().timer(debounce).await;
15795 }
15796 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15797 editor
15798 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15799 .ok()
15800 }) else {
15801 return;
15802 };
15803
15804 let new_inline_diagnostics = cx
15805 .background_spawn(async move {
15806 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15807 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15808 let message = diagnostic_entry
15809 .diagnostic
15810 .message
15811 .split_once('\n')
15812 .map(|(line, _)| line)
15813 .map(SharedString::new)
15814 .unwrap_or_else(|| {
15815 SharedString::from(diagnostic_entry.diagnostic.message)
15816 });
15817 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15818 let (Ok(i) | Err(i)) = inline_diagnostics
15819 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15820 inline_diagnostics.insert(
15821 i,
15822 (
15823 start_anchor,
15824 InlineDiagnostic {
15825 message,
15826 group_id: diagnostic_entry.diagnostic.group_id,
15827 start: diagnostic_entry.range.start.to_point(&snapshot),
15828 is_primary: diagnostic_entry.diagnostic.is_primary,
15829 severity: diagnostic_entry.diagnostic.severity,
15830 },
15831 ),
15832 );
15833 }
15834 inline_diagnostics
15835 })
15836 .await;
15837
15838 editor
15839 .update(cx, |editor, cx| {
15840 editor.inline_diagnostics = new_inline_diagnostics;
15841 cx.notify();
15842 })
15843 .ok();
15844 });
15845 }
15846
15847 pub fn set_selections_from_remote(
15848 &mut self,
15849 selections: Vec<Selection<Anchor>>,
15850 pending_selection: Option<Selection<Anchor>>,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) {
15854 let old_cursor_position = self.selections.newest_anchor().head();
15855 self.selections.change_with(cx, |s| {
15856 s.select_anchors(selections);
15857 if let Some(pending_selection) = pending_selection {
15858 s.set_pending(pending_selection, SelectMode::Character);
15859 } else {
15860 s.clear_pending();
15861 }
15862 });
15863 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15864 }
15865
15866 pub fn transact(
15867 &mut self,
15868 window: &mut Window,
15869 cx: &mut Context<Self>,
15870 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15871 ) -> Option<TransactionId> {
15872 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15873 this.start_transaction_at(Instant::now(), window, cx);
15874 update(this, window, cx);
15875 this.end_transaction_at(Instant::now(), cx)
15876 })
15877 }
15878
15879 pub fn start_transaction_at(
15880 &mut self,
15881 now: Instant,
15882 window: &mut Window,
15883 cx: &mut Context<Self>,
15884 ) {
15885 self.end_selection(window, cx);
15886 if let Some(tx_id) = self
15887 .buffer
15888 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15889 {
15890 self.selection_history
15891 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15892 cx.emit(EditorEvent::TransactionBegun {
15893 transaction_id: tx_id,
15894 })
15895 }
15896 }
15897
15898 pub fn end_transaction_at(
15899 &mut self,
15900 now: Instant,
15901 cx: &mut Context<Self>,
15902 ) -> Option<TransactionId> {
15903 if let Some(transaction_id) = self
15904 .buffer
15905 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15906 {
15907 if let Some((_, end_selections)) =
15908 self.selection_history.transaction_mut(transaction_id)
15909 {
15910 *end_selections = Some(self.selections.disjoint_anchors());
15911 } else {
15912 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15913 }
15914
15915 cx.emit(EditorEvent::Edited { transaction_id });
15916 Some(transaction_id)
15917 } else {
15918 None
15919 }
15920 }
15921
15922 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15923 if self.selection_mark_mode {
15924 self.change_selections(None, window, cx, |s| {
15925 s.move_with(|_, sel| {
15926 sel.collapse_to(sel.head(), SelectionGoal::None);
15927 });
15928 })
15929 }
15930 self.selection_mark_mode = true;
15931 cx.notify();
15932 }
15933
15934 pub fn swap_selection_ends(
15935 &mut self,
15936 _: &actions::SwapSelectionEnds,
15937 window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 self.change_selections(None, window, cx, |s| {
15941 s.move_with(|_, sel| {
15942 if sel.start != sel.end {
15943 sel.reversed = !sel.reversed
15944 }
15945 });
15946 });
15947 self.request_autoscroll(Autoscroll::newest(), cx);
15948 cx.notify();
15949 }
15950
15951 pub fn toggle_fold(
15952 &mut self,
15953 _: &actions::ToggleFold,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 if self.is_singleton(cx) {
15958 let selection = self.selections.newest::<Point>(cx);
15959
15960 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15961 let range = if selection.is_empty() {
15962 let point = selection.head().to_display_point(&display_map);
15963 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15964 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15965 .to_point(&display_map);
15966 start..end
15967 } else {
15968 selection.range()
15969 };
15970 if display_map.folds_in_range(range).next().is_some() {
15971 self.unfold_lines(&Default::default(), window, cx)
15972 } else {
15973 self.fold(&Default::default(), window, cx)
15974 }
15975 } else {
15976 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15977 let buffer_ids: HashSet<_> = self
15978 .selections
15979 .disjoint_anchor_ranges()
15980 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15981 .collect();
15982
15983 let should_unfold = buffer_ids
15984 .iter()
15985 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15986
15987 for buffer_id in buffer_ids {
15988 if should_unfold {
15989 self.unfold_buffer(buffer_id, cx);
15990 } else {
15991 self.fold_buffer(buffer_id, cx);
15992 }
15993 }
15994 }
15995 }
15996
15997 pub fn toggle_fold_recursive(
15998 &mut self,
15999 _: &actions::ToggleFoldRecursive,
16000 window: &mut Window,
16001 cx: &mut Context<Self>,
16002 ) {
16003 let selection = self.selections.newest::<Point>(cx);
16004
16005 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16006 let range = if selection.is_empty() {
16007 let point = selection.head().to_display_point(&display_map);
16008 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16009 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16010 .to_point(&display_map);
16011 start..end
16012 } else {
16013 selection.range()
16014 };
16015 if display_map.folds_in_range(range).next().is_some() {
16016 self.unfold_recursive(&Default::default(), window, cx)
16017 } else {
16018 self.fold_recursive(&Default::default(), window, cx)
16019 }
16020 }
16021
16022 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16023 if self.is_singleton(cx) {
16024 let mut to_fold = Vec::new();
16025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16026 let selections = self.selections.all_adjusted(cx);
16027
16028 for selection in selections {
16029 let range = selection.range().sorted();
16030 let buffer_start_row = range.start.row;
16031
16032 if range.start.row != range.end.row {
16033 let mut found = false;
16034 let mut row = range.start.row;
16035 while row <= range.end.row {
16036 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16037 {
16038 found = true;
16039 row = crease.range().end.row + 1;
16040 to_fold.push(crease);
16041 } else {
16042 row += 1
16043 }
16044 }
16045 if found {
16046 continue;
16047 }
16048 }
16049
16050 for row in (0..=range.start.row).rev() {
16051 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16052 if crease.range().end.row >= buffer_start_row {
16053 to_fold.push(crease);
16054 if row <= range.start.row {
16055 break;
16056 }
16057 }
16058 }
16059 }
16060 }
16061
16062 self.fold_creases(to_fold, true, window, cx);
16063 } else {
16064 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16065 let buffer_ids = self
16066 .selections
16067 .disjoint_anchor_ranges()
16068 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16069 .collect::<HashSet<_>>();
16070 for buffer_id in buffer_ids {
16071 self.fold_buffer(buffer_id, cx);
16072 }
16073 }
16074 }
16075
16076 fn fold_at_level(
16077 &mut self,
16078 fold_at: &FoldAtLevel,
16079 window: &mut Window,
16080 cx: &mut Context<Self>,
16081 ) {
16082 if !self.buffer.read(cx).is_singleton() {
16083 return;
16084 }
16085
16086 let fold_at_level = fold_at.0;
16087 let snapshot = self.buffer.read(cx).snapshot(cx);
16088 let mut to_fold = Vec::new();
16089 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16090
16091 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16092 while start_row < end_row {
16093 match self
16094 .snapshot(window, cx)
16095 .crease_for_buffer_row(MultiBufferRow(start_row))
16096 {
16097 Some(crease) => {
16098 let nested_start_row = crease.range().start.row + 1;
16099 let nested_end_row = crease.range().end.row;
16100
16101 if current_level < fold_at_level {
16102 stack.push((nested_start_row, nested_end_row, current_level + 1));
16103 } else if current_level == fold_at_level {
16104 to_fold.push(crease);
16105 }
16106
16107 start_row = nested_end_row + 1;
16108 }
16109 None => start_row += 1,
16110 }
16111 }
16112 }
16113
16114 self.fold_creases(to_fold, true, window, cx);
16115 }
16116
16117 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16118 if self.buffer.read(cx).is_singleton() {
16119 let mut fold_ranges = Vec::new();
16120 let snapshot = self.buffer.read(cx).snapshot(cx);
16121
16122 for row in 0..snapshot.max_row().0 {
16123 if let Some(foldable_range) = self
16124 .snapshot(window, cx)
16125 .crease_for_buffer_row(MultiBufferRow(row))
16126 {
16127 fold_ranges.push(foldable_range);
16128 }
16129 }
16130
16131 self.fold_creases(fold_ranges, true, window, cx);
16132 } else {
16133 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16134 editor
16135 .update_in(cx, |editor, _, cx| {
16136 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16137 editor.fold_buffer(buffer_id, cx);
16138 }
16139 })
16140 .ok();
16141 });
16142 }
16143 }
16144
16145 pub fn fold_function_bodies(
16146 &mut self,
16147 _: &actions::FoldFunctionBodies,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 let snapshot = self.buffer.read(cx).snapshot(cx);
16152
16153 let ranges = snapshot
16154 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16155 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16156 .collect::<Vec<_>>();
16157
16158 let creases = ranges
16159 .into_iter()
16160 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16161 .collect();
16162
16163 self.fold_creases(creases, true, window, cx);
16164 }
16165
16166 pub fn fold_recursive(
16167 &mut self,
16168 _: &actions::FoldRecursive,
16169 window: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) {
16172 let mut to_fold = Vec::new();
16173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16174 let selections = self.selections.all_adjusted(cx);
16175
16176 for selection in selections {
16177 let range = selection.range().sorted();
16178 let buffer_start_row = range.start.row;
16179
16180 if range.start.row != range.end.row {
16181 let mut found = false;
16182 for row in range.start.row..=range.end.row {
16183 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16184 found = true;
16185 to_fold.push(crease);
16186 }
16187 }
16188 if found {
16189 continue;
16190 }
16191 }
16192
16193 for row in (0..=range.start.row).rev() {
16194 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16195 if crease.range().end.row >= buffer_start_row {
16196 to_fold.push(crease);
16197 } else {
16198 break;
16199 }
16200 }
16201 }
16202 }
16203
16204 self.fold_creases(to_fold, true, window, cx);
16205 }
16206
16207 pub fn fold_at(
16208 &mut self,
16209 buffer_row: MultiBufferRow,
16210 window: &mut Window,
16211 cx: &mut Context<Self>,
16212 ) {
16213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16214
16215 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16216 let autoscroll = self
16217 .selections
16218 .all::<Point>(cx)
16219 .iter()
16220 .any(|selection| crease.range().overlaps(&selection.range()));
16221
16222 self.fold_creases(vec![crease], autoscroll, window, cx);
16223 }
16224 }
16225
16226 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16227 if self.is_singleton(cx) {
16228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16229 let buffer = &display_map.buffer_snapshot;
16230 let selections = self.selections.all::<Point>(cx);
16231 let ranges = selections
16232 .iter()
16233 .map(|s| {
16234 let range = s.display_range(&display_map).sorted();
16235 let mut start = range.start.to_point(&display_map);
16236 let mut end = range.end.to_point(&display_map);
16237 start.column = 0;
16238 end.column = buffer.line_len(MultiBufferRow(end.row));
16239 start..end
16240 })
16241 .collect::<Vec<_>>();
16242
16243 self.unfold_ranges(&ranges, true, true, cx);
16244 } else {
16245 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16246 let buffer_ids = self
16247 .selections
16248 .disjoint_anchor_ranges()
16249 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16250 .collect::<HashSet<_>>();
16251 for buffer_id in buffer_ids {
16252 self.unfold_buffer(buffer_id, cx);
16253 }
16254 }
16255 }
16256
16257 pub fn unfold_recursive(
16258 &mut self,
16259 _: &UnfoldRecursive,
16260 _window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) {
16263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16264 let selections = self.selections.all::<Point>(cx);
16265 let ranges = selections
16266 .iter()
16267 .map(|s| {
16268 let mut range = s.display_range(&display_map).sorted();
16269 *range.start.column_mut() = 0;
16270 *range.end.column_mut() = display_map.line_len(range.end.row());
16271 let start = range.start.to_point(&display_map);
16272 let end = range.end.to_point(&display_map);
16273 start..end
16274 })
16275 .collect::<Vec<_>>();
16276
16277 self.unfold_ranges(&ranges, true, true, cx);
16278 }
16279
16280 pub fn unfold_at(
16281 &mut self,
16282 buffer_row: MultiBufferRow,
16283 _window: &mut Window,
16284 cx: &mut Context<Self>,
16285 ) {
16286 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16287
16288 let intersection_range = Point::new(buffer_row.0, 0)
16289 ..Point::new(
16290 buffer_row.0,
16291 display_map.buffer_snapshot.line_len(buffer_row),
16292 );
16293
16294 let autoscroll = self
16295 .selections
16296 .all::<Point>(cx)
16297 .iter()
16298 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16299
16300 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16301 }
16302
16303 pub fn unfold_all(
16304 &mut self,
16305 _: &actions::UnfoldAll,
16306 _window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) {
16309 if self.buffer.read(cx).is_singleton() {
16310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16311 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16312 } else {
16313 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16314 editor
16315 .update(cx, |editor, cx| {
16316 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16317 editor.unfold_buffer(buffer_id, cx);
16318 }
16319 })
16320 .ok();
16321 });
16322 }
16323 }
16324
16325 pub fn fold_selected_ranges(
16326 &mut self,
16327 _: &FoldSelectedRanges,
16328 window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) {
16331 let selections = self.selections.all_adjusted(cx);
16332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16333 let ranges = selections
16334 .into_iter()
16335 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16336 .collect::<Vec<_>>();
16337 self.fold_creases(ranges, true, window, cx);
16338 }
16339
16340 pub fn fold_ranges<T: ToOffset + Clone>(
16341 &mut self,
16342 ranges: Vec<Range<T>>,
16343 auto_scroll: bool,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16348 let ranges = ranges
16349 .into_iter()
16350 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16351 .collect::<Vec<_>>();
16352 self.fold_creases(ranges, auto_scroll, window, cx);
16353 }
16354
16355 pub fn fold_creases<T: ToOffset + Clone>(
16356 &mut self,
16357 creases: Vec<Crease<T>>,
16358 auto_scroll: bool,
16359 _window: &mut Window,
16360 cx: &mut Context<Self>,
16361 ) {
16362 if creases.is_empty() {
16363 return;
16364 }
16365
16366 let mut buffers_affected = HashSet::default();
16367 let multi_buffer = self.buffer().read(cx);
16368 for crease in &creases {
16369 if let Some((_, buffer, _)) =
16370 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16371 {
16372 buffers_affected.insert(buffer.read(cx).remote_id());
16373 };
16374 }
16375
16376 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16377
16378 if auto_scroll {
16379 self.request_autoscroll(Autoscroll::fit(), cx);
16380 }
16381
16382 cx.notify();
16383
16384 self.scrollbar_marker_state.dirty = true;
16385 self.folds_did_change(cx);
16386 }
16387
16388 /// Removes any folds whose ranges intersect any of the given ranges.
16389 pub fn unfold_ranges<T: ToOffset + Clone>(
16390 &mut self,
16391 ranges: &[Range<T>],
16392 inclusive: bool,
16393 auto_scroll: bool,
16394 cx: &mut Context<Self>,
16395 ) {
16396 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16397 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16398 });
16399 self.folds_did_change(cx);
16400 }
16401
16402 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16403 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16404 return;
16405 }
16406 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16407 self.display_map.update(cx, |display_map, cx| {
16408 display_map.fold_buffers([buffer_id], cx)
16409 });
16410 cx.emit(EditorEvent::BufferFoldToggled {
16411 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16412 folded: true,
16413 });
16414 cx.notify();
16415 }
16416
16417 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16418 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16419 return;
16420 }
16421 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16422 self.display_map.update(cx, |display_map, cx| {
16423 display_map.unfold_buffers([buffer_id], cx);
16424 });
16425 cx.emit(EditorEvent::BufferFoldToggled {
16426 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16427 folded: false,
16428 });
16429 cx.notify();
16430 }
16431
16432 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16433 self.display_map.read(cx).is_buffer_folded(buffer)
16434 }
16435
16436 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16437 self.display_map.read(cx).folded_buffers()
16438 }
16439
16440 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16441 self.display_map.update(cx, |display_map, cx| {
16442 display_map.disable_header_for_buffer(buffer_id, cx);
16443 });
16444 cx.notify();
16445 }
16446
16447 /// Removes any folds with the given ranges.
16448 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16449 &mut self,
16450 ranges: &[Range<T>],
16451 type_id: TypeId,
16452 auto_scroll: bool,
16453 cx: &mut Context<Self>,
16454 ) {
16455 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16456 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16457 });
16458 self.folds_did_change(cx);
16459 }
16460
16461 fn remove_folds_with<T: ToOffset + Clone>(
16462 &mut self,
16463 ranges: &[Range<T>],
16464 auto_scroll: bool,
16465 cx: &mut Context<Self>,
16466 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16467 ) {
16468 if ranges.is_empty() {
16469 return;
16470 }
16471
16472 let mut buffers_affected = HashSet::default();
16473 let multi_buffer = self.buffer().read(cx);
16474 for range in ranges {
16475 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16476 buffers_affected.insert(buffer.read(cx).remote_id());
16477 };
16478 }
16479
16480 self.display_map.update(cx, update);
16481
16482 if auto_scroll {
16483 self.request_autoscroll(Autoscroll::fit(), cx);
16484 }
16485
16486 cx.notify();
16487 self.scrollbar_marker_state.dirty = true;
16488 self.active_indent_guides_state.dirty = true;
16489 }
16490
16491 pub fn update_fold_widths(
16492 &mut self,
16493 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16494 cx: &mut Context<Self>,
16495 ) -> bool {
16496 self.display_map
16497 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16498 }
16499
16500 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16501 self.display_map.read(cx).fold_placeholder.clone()
16502 }
16503
16504 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16505 self.buffer.update(cx, |buffer, cx| {
16506 buffer.set_all_diff_hunks_expanded(cx);
16507 });
16508 }
16509
16510 pub fn expand_all_diff_hunks(
16511 &mut self,
16512 _: &ExpandAllDiffHunks,
16513 _window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) {
16516 self.buffer.update(cx, |buffer, cx| {
16517 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16518 });
16519 }
16520
16521 pub fn toggle_selected_diff_hunks(
16522 &mut self,
16523 _: &ToggleSelectedDiffHunks,
16524 _window: &mut Window,
16525 cx: &mut Context<Self>,
16526 ) {
16527 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16528 self.toggle_diff_hunks_in_ranges(ranges, cx);
16529 }
16530
16531 pub fn diff_hunks_in_ranges<'a>(
16532 &'a self,
16533 ranges: &'a [Range<Anchor>],
16534 buffer: &'a MultiBufferSnapshot,
16535 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16536 ranges.iter().flat_map(move |range| {
16537 let end_excerpt_id = range.end.excerpt_id;
16538 let range = range.to_point(buffer);
16539 let mut peek_end = range.end;
16540 if range.end.row < buffer.max_row().0 {
16541 peek_end = Point::new(range.end.row + 1, 0);
16542 }
16543 buffer
16544 .diff_hunks_in_range(range.start..peek_end)
16545 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16546 })
16547 }
16548
16549 pub fn has_stageable_diff_hunks_in_ranges(
16550 &self,
16551 ranges: &[Range<Anchor>],
16552 snapshot: &MultiBufferSnapshot,
16553 ) -> bool {
16554 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16555 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16556 }
16557
16558 pub fn toggle_staged_selected_diff_hunks(
16559 &mut self,
16560 _: &::git::ToggleStaged,
16561 _: &mut Window,
16562 cx: &mut Context<Self>,
16563 ) {
16564 let snapshot = self.buffer.read(cx).snapshot(cx);
16565 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16566 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16567 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16568 }
16569
16570 pub fn set_render_diff_hunk_controls(
16571 &mut self,
16572 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16573 cx: &mut Context<Self>,
16574 ) {
16575 self.render_diff_hunk_controls = render_diff_hunk_controls;
16576 cx.notify();
16577 }
16578
16579 pub fn stage_and_next(
16580 &mut self,
16581 _: &::git::StageAndNext,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 self.do_stage_or_unstage_and_next(true, window, cx);
16586 }
16587
16588 pub fn unstage_and_next(
16589 &mut self,
16590 _: &::git::UnstageAndNext,
16591 window: &mut Window,
16592 cx: &mut Context<Self>,
16593 ) {
16594 self.do_stage_or_unstage_and_next(false, window, cx);
16595 }
16596
16597 pub fn stage_or_unstage_diff_hunks(
16598 &mut self,
16599 stage: bool,
16600 ranges: Vec<Range<Anchor>>,
16601 cx: &mut Context<Self>,
16602 ) {
16603 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16604 cx.spawn(async move |this, cx| {
16605 task.await?;
16606 this.update(cx, |this, cx| {
16607 let snapshot = this.buffer.read(cx).snapshot(cx);
16608 let chunk_by = this
16609 .diff_hunks_in_ranges(&ranges, &snapshot)
16610 .chunk_by(|hunk| hunk.buffer_id);
16611 for (buffer_id, hunks) in &chunk_by {
16612 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16613 }
16614 })
16615 })
16616 .detach_and_log_err(cx);
16617 }
16618
16619 fn save_buffers_for_ranges_if_needed(
16620 &mut self,
16621 ranges: &[Range<Anchor>],
16622 cx: &mut Context<Editor>,
16623 ) -> Task<Result<()>> {
16624 let multibuffer = self.buffer.read(cx);
16625 let snapshot = multibuffer.read(cx);
16626 let buffer_ids: HashSet<_> = ranges
16627 .iter()
16628 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16629 .collect();
16630 drop(snapshot);
16631
16632 let mut buffers = HashSet::default();
16633 for buffer_id in buffer_ids {
16634 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16635 let buffer = buffer_entity.read(cx);
16636 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16637 {
16638 buffers.insert(buffer_entity);
16639 }
16640 }
16641 }
16642
16643 if let Some(project) = &self.project {
16644 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16645 } else {
16646 Task::ready(Ok(()))
16647 }
16648 }
16649
16650 fn do_stage_or_unstage_and_next(
16651 &mut self,
16652 stage: bool,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16657
16658 if ranges.iter().any(|range| range.start != range.end) {
16659 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16660 return;
16661 }
16662
16663 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16664 let snapshot = self.snapshot(window, cx);
16665 let position = self.selections.newest::<Point>(cx).head();
16666 let mut row = snapshot
16667 .buffer_snapshot
16668 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16669 .find(|hunk| hunk.row_range.start.0 > position.row)
16670 .map(|hunk| hunk.row_range.start);
16671
16672 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16673 // Outside of the project diff editor, wrap around to the beginning.
16674 if !all_diff_hunks_expanded {
16675 row = row.or_else(|| {
16676 snapshot
16677 .buffer_snapshot
16678 .diff_hunks_in_range(Point::zero()..position)
16679 .find(|hunk| hunk.row_range.end.0 < position.row)
16680 .map(|hunk| hunk.row_range.start)
16681 });
16682 }
16683
16684 if let Some(row) = row {
16685 let destination = Point::new(row.0, 0);
16686 let autoscroll = Autoscroll::center();
16687
16688 self.unfold_ranges(&[destination..destination], false, false, cx);
16689 self.change_selections(Some(autoscroll), window, cx, |s| {
16690 s.select_ranges([destination..destination]);
16691 });
16692 }
16693 }
16694
16695 fn do_stage_or_unstage(
16696 &self,
16697 stage: bool,
16698 buffer_id: BufferId,
16699 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16700 cx: &mut App,
16701 ) -> Option<()> {
16702 let project = self.project.as_ref()?;
16703 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16704 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16705 let buffer_snapshot = buffer.read(cx).snapshot();
16706 let file_exists = buffer_snapshot
16707 .file()
16708 .is_some_and(|file| file.disk_state().exists());
16709 diff.update(cx, |diff, cx| {
16710 diff.stage_or_unstage_hunks(
16711 stage,
16712 &hunks
16713 .map(|hunk| buffer_diff::DiffHunk {
16714 buffer_range: hunk.buffer_range,
16715 diff_base_byte_range: hunk.diff_base_byte_range,
16716 secondary_status: hunk.secondary_status,
16717 range: Point::zero()..Point::zero(), // unused
16718 })
16719 .collect::<Vec<_>>(),
16720 &buffer_snapshot,
16721 file_exists,
16722 cx,
16723 )
16724 });
16725 None
16726 }
16727
16728 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16729 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16730 self.buffer
16731 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16732 }
16733
16734 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16735 self.buffer.update(cx, |buffer, cx| {
16736 let ranges = vec![Anchor::min()..Anchor::max()];
16737 if !buffer.all_diff_hunks_expanded()
16738 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16739 {
16740 buffer.collapse_diff_hunks(ranges, cx);
16741 true
16742 } else {
16743 false
16744 }
16745 })
16746 }
16747
16748 fn toggle_diff_hunks_in_ranges(
16749 &mut self,
16750 ranges: Vec<Range<Anchor>>,
16751 cx: &mut Context<Editor>,
16752 ) {
16753 self.buffer.update(cx, |buffer, cx| {
16754 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16755 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16756 })
16757 }
16758
16759 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16760 self.buffer.update(cx, |buffer, cx| {
16761 let snapshot = buffer.snapshot(cx);
16762 let excerpt_id = range.end.excerpt_id;
16763 let point_range = range.to_point(&snapshot);
16764 let expand = !buffer.single_hunk_is_expanded(range, cx);
16765 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16766 })
16767 }
16768
16769 pub(crate) fn apply_all_diff_hunks(
16770 &mut self,
16771 _: &ApplyAllDiffHunks,
16772 window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16776
16777 let buffers = self.buffer.read(cx).all_buffers();
16778 for branch_buffer in buffers {
16779 branch_buffer.update(cx, |branch_buffer, cx| {
16780 branch_buffer.merge_into_base(Vec::new(), cx);
16781 });
16782 }
16783
16784 if let Some(project) = self.project.clone() {
16785 self.save(true, project, window, cx).detach_and_log_err(cx);
16786 }
16787 }
16788
16789 pub(crate) fn apply_selected_diff_hunks(
16790 &mut self,
16791 _: &ApplyDiffHunk,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16796 let snapshot = self.snapshot(window, cx);
16797 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16798 let mut ranges_by_buffer = HashMap::default();
16799 self.transact(window, cx, |editor, _window, cx| {
16800 for hunk in hunks {
16801 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16802 ranges_by_buffer
16803 .entry(buffer.clone())
16804 .or_insert_with(Vec::new)
16805 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16806 }
16807 }
16808
16809 for (buffer, ranges) in ranges_by_buffer {
16810 buffer.update(cx, |buffer, cx| {
16811 buffer.merge_into_base(ranges, cx);
16812 });
16813 }
16814 });
16815
16816 if let Some(project) = self.project.clone() {
16817 self.save(true, project, window, cx).detach_and_log_err(cx);
16818 }
16819 }
16820
16821 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16822 if hovered != self.gutter_hovered {
16823 self.gutter_hovered = hovered;
16824 cx.notify();
16825 }
16826 }
16827
16828 pub fn insert_blocks(
16829 &mut self,
16830 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16831 autoscroll: Option<Autoscroll>,
16832 cx: &mut Context<Self>,
16833 ) -> Vec<CustomBlockId> {
16834 let blocks = self
16835 .display_map
16836 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16837 if let Some(autoscroll) = autoscroll {
16838 self.request_autoscroll(autoscroll, cx);
16839 }
16840 cx.notify();
16841 blocks
16842 }
16843
16844 pub fn resize_blocks(
16845 &mut self,
16846 heights: HashMap<CustomBlockId, u32>,
16847 autoscroll: Option<Autoscroll>,
16848 cx: &mut Context<Self>,
16849 ) {
16850 self.display_map
16851 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16852 if let Some(autoscroll) = autoscroll {
16853 self.request_autoscroll(autoscroll, cx);
16854 }
16855 cx.notify();
16856 }
16857
16858 pub fn replace_blocks(
16859 &mut self,
16860 renderers: HashMap<CustomBlockId, RenderBlock>,
16861 autoscroll: Option<Autoscroll>,
16862 cx: &mut Context<Self>,
16863 ) {
16864 self.display_map
16865 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16866 if let Some(autoscroll) = autoscroll {
16867 self.request_autoscroll(autoscroll, cx);
16868 }
16869 cx.notify();
16870 }
16871
16872 pub fn remove_blocks(
16873 &mut self,
16874 block_ids: HashSet<CustomBlockId>,
16875 autoscroll: Option<Autoscroll>,
16876 cx: &mut Context<Self>,
16877 ) {
16878 self.display_map.update(cx, |display_map, cx| {
16879 display_map.remove_blocks(block_ids, cx)
16880 });
16881 if let Some(autoscroll) = autoscroll {
16882 self.request_autoscroll(autoscroll, cx);
16883 }
16884 cx.notify();
16885 }
16886
16887 pub fn row_for_block(
16888 &self,
16889 block_id: CustomBlockId,
16890 cx: &mut Context<Self>,
16891 ) -> Option<DisplayRow> {
16892 self.display_map
16893 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16894 }
16895
16896 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16897 self.focused_block = Some(focused_block);
16898 }
16899
16900 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16901 self.focused_block.take()
16902 }
16903
16904 pub fn insert_creases(
16905 &mut self,
16906 creases: impl IntoIterator<Item = Crease<Anchor>>,
16907 cx: &mut Context<Self>,
16908 ) -> Vec<CreaseId> {
16909 self.display_map
16910 .update(cx, |map, cx| map.insert_creases(creases, cx))
16911 }
16912
16913 pub fn remove_creases(
16914 &mut self,
16915 ids: impl IntoIterator<Item = CreaseId>,
16916 cx: &mut Context<Self>,
16917 ) -> Vec<(CreaseId, Range<Anchor>)> {
16918 self.display_map
16919 .update(cx, |map, cx| map.remove_creases(ids, cx))
16920 }
16921
16922 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16923 self.display_map
16924 .update(cx, |map, cx| map.snapshot(cx))
16925 .longest_row()
16926 }
16927
16928 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16929 self.display_map
16930 .update(cx, |map, cx| map.snapshot(cx))
16931 .max_point()
16932 }
16933
16934 pub fn text(&self, cx: &App) -> String {
16935 self.buffer.read(cx).read(cx).text()
16936 }
16937
16938 pub fn is_empty(&self, cx: &App) -> bool {
16939 self.buffer.read(cx).read(cx).is_empty()
16940 }
16941
16942 pub fn text_option(&self, cx: &App) -> Option<String> {
16943 let text = self.text(cx);
16944 let text = text.trim();
16945
16946 if text.is_empty() {
16947 return None;
16948 }
16949
16950 Some(text.to_string())
16951 }
16952
16953 pub fn set_text(
16954 &mut self,
16955 text: impl Into<Arc<str>>,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 self.transact(window, cx, |this, _, cx| {
16960 this.buffer
16961 .read(cx)
16962 .as_singleton()
16963 .expect("you can only call set_text on editors for singleton buffers")
16964 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16965 });
16966 }
16967
16968 pub fn display_text(&self, cx: &mut App) -> String {
16969 self.display_map
16970 .update(cx, |map, cx| map.snapshot(cx))
16971 .text()
16972 }
16973
16974 fn create_minimap(
16975 &self,
16976 minimap_settings: MinimapSettings,
16977 window: &mut Window,
16978 cx: &mut Context<Self>,
16979 ) -> Option<Entity<Self>> {
16980 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16981 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16982 }
16983
16984 fn initialize_new_minimap(
16985 &self,
16986 minimap_settings: MinimapSettings,
16987 window: &mut Window,
16988 cx: &mut Context<Self>,
16989 ) -> Entity<Self> {
16990 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16991
16992 let mut minimap = Editor::new_internal(
16993 EditorMode::Minimap {
16994 parent: cx.weak_entity(),
16995 },
16996 self.buffer.clone(),
16997 self.project.clone(),
16998 Some(self.display_map.clone()),
16999 window,
17000 cx,
17001 );
17002 minimap.scroll_manager.clone_state(&self.scroll_manager);
17003 minimap.set_text_style_refinement(TextStyleRefinement {
17004 font_size: Some(MINIMAP_FONT_SIZE),
17005 font_weight: Some(MINIMAP_FONT_WEIGHT),
17006 ..Default::default()
17007 });
17008 minimap.update_minimap_configuration(minimap_settings, cx);
17009 cx.new(|_| minimap)
17010 }
17011
17012 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17013 let current_line_highlight = minimap_settings
17014 .current_line_highlight
17015 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17016 self.set_current_line_highlight(Some(current_line_highlight));
17017 }
17018
17019 pub fn minimap(&self) -> Option<&Entity<Self>> {
17020 self.minimap
17021 .as_ref()
17022 .filter(|_| self.minimap_visibility.visible())
17023 }
17024
17025 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17026 let mut wrap_guides = smallvec![];
17027
17028 if self.show_wrap_guides == Some(false) {
17029 return wrap_guides;
17030 }
17031
17032 let settings = self.buffer.read(cx).language_settings(cx);
17033 if settings.show_wrap_guides {
17034 match self.soft_wrap_mode(cx) {
17035 SoftWrap::Column(soft_wrap) => {
17036 wrap_guides.push((soft_wrap as usize, true));
17037 }
17038 SoftWrap::Bounded(soft_wrap) => {
17039 wrap_guides.push((soft_wrap as usize, true));
17040 }
17041 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17042 }
17043 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17044 }
17045
17046 wrap_guides
17047 }
17048
17049 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17050 let settings = self.buffer.read(cx).language_settings(cx);
17051 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17052 match mode {
17053 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17054 SoftWrap::None
17055 }
17056 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17057 language_settings::SoftWrap::PreferredLineLength => {
17058 SoftWrap::Column(settings.preferred_line_length)
17059 }
17060 language_settings::SoftWrap::Bounded => {
17061 SoftWrap::Bounded(settings.preferred_line_length)
17062 }
17063 }
17064 }
17065
17066 pub fn set_soft_wrap_mode(
17067 &mut self,
17068 mode: language_settings::SoftWrap,
17069
17070 cx: &mut Context<Self>,
17071 ) {
17072 self.soft_wrap_mode_override = Some(mode);
17073 cx.notify();
17074 }
17075
17076 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17077 self.hard_wrap = hard_wrap;
17078 cx.notify();
17079 }
17080
17081 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17082 self.text_style_refinement = Some(style);
17083 }
17084
17085 /// called by the Element so we know what style we were most recently rendered with.
17086 pub(crate) fn set_style(
17087 &mut self,
17088 style: EditorStyle,
17089 window: &mut Window,
17090 cx: &mut Context<Self>,
17091 ) {
17092 // We intentionally do not inform the display map about the minimap style
17093 // so that wrapping is not recalculated and stays consistent for the editor
17094 // and its linked minimap.
17095 if !self.mode.is_minimap() {
17096 let rem_size = window.rem_size();
17097 self.display_map.update(cx, |map, cx| {
17098 map.set_font(
17099 style.text.font(),
17100 style.text.font_size.to_pixels(rem_size),
17101 cx,
17102 )
17103 });
17104 }
17105 self.style = Some(style);
17106 }
17107
17108 pub fn style(&self) -> Option<&EditorStyle> {
17109 self.style.as_ref()
17110 }
17111
17112 // Called by the element. This method is not designed to be called outside of the editor
17113 // element's layout code because it does not notify when rewrapping is computed synchronously.
17114 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17115 self.display_map
17116 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17117 }
17118
17119 pub fn set_soft_wrap(&mut self) {
17120 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17121 }
17122
17123 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17124 if self.soft_wrap_mode_override.is_some() {
17125 self.soft_wrap_mode_override.take();
17126 } else {
17127 let soft_wrap = match self.soft_wrap_mode(cx) {
17128 SoftWrap::GitDiff => return,
17129 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17130 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17131 language_settings::SoftWrap::None
17132 }
17133 };
17134 self.soft_wrap_mode_override = Some(soft_wrap);
17135 }
17136 cx.notify();
17137 }
17138
17139 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17140 let Some(workspace) = self.workspace() else {
17141 return;
17142 };
17143 let fs = workspace.read(cx).app_state().fs.clone();
17144 let current_show = TabBarSettings::get_global(cx).show;
17145 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17146 setting.show = Some(!current_show);
17147 });
17148 }
17149
17150 pub fn toggle_indent_guides(
17151 &mut self,
17152 _: &ToggleIndentGuides,
17153 _: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17157 self.buffer
17158 .read(cx)
17159 .language_settings(cx)
17160 .indent_guides
17161 .enabled
17162 });
17163 self.show_indent_guides = Some(!currently_enabled);
17164 cx.notify();
17165 }
17166
17167 fn should_show_indent_guides(&self) -> Option<bool> {
17168 self.show_indent_guides
17169 }
17170
17171 pub fn toggle_line_numbers(
17172 &mut self,
17173 _: &ToggleLineNumbers,
17174 _: &mut Window,
17175 cx: &mut Context<Self>,
17176 ) {
17177 let mut editor_settings = EditorSettings::get_global(cx).clone();
17178 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17179 EditorSettings::override_global(editor_settings, cx);
17180 }
17181
17182 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17183 if let Some(show_line_numbers) = self.show_line_numbers {
17184 return show_line_numbers;
17185 }
17186 EditorSettings::get_global(cx).gutter.line_numbers
17187 }
17188
17189 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17190 self.use_relative_line_numbers
17191 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17192 }
17193
17194 pub fn toggle_relative_line_numbers(
17195 &mut self,
17196 _: &ToggleRelativeLineNumbers,
17197 _: &mut Window,
17198 cx: &mut Context<Self>,
17199 ) {
17200 let is_relative = self.should_use_relative_line_numbers(cx);
17201 self.set_relative_line_number(Some(!is_relative), cx)
17202 }
17203
17204 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17205 self.use_relative_line_numbers = is_relative;
17206 cx.notify();
17207 }
17208
17209 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17210 self.show_gutter = show_gutter;
17211 cx.notify();
17212 }
17213
17214 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17215 self.show_scrollbars = ScrollbarAxes {
17216 horizontal: show,
17217 vertical: show,
17218 };
17219 cx.notify();
17220 }
17221
17222 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17223 self.show_scrollbars.vertical = show;
17224 cx.notify();
17225 }
17226
17227 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17228 self.show_scrollbars.horizontal = show;
17229 cx.notify();
17230 }
17231
17232 pub fn set_minimap_visibility(
17233 &mut self,
17234 minimap_visibility: MinimapVisibility,
17235 window: &mut Window,
17236 cx: &mut Context<Self>,
17237 ) {
17238 if self.minimap_visibility != minimap_visibility {
17239 if minimap_visibility.visible() && self.minimap.is_none() {
17240 let minimap_settings = EditorSettings::get_global(cx).minimap;
17241 self.minimap =
17242 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17243 }
17244 self.minimap_visibility = minimap_visibility;
17245 cx.notify();
17246 }
17247 }
17248
17249 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17250 self.set_show_scrollbars(false, cx);
17251 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17252 }
17253
17254 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17255 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17256 }
17257
17258 /// Normally the text in full mode and auto height editors is padded on the
17259 /// left side by roughly half a character width for improved hit testing.
17260 ///
17261 /// Use this method to disable this for cases where this is not wanted (e.g.
17262 /// if you want to align the editor text with some other text above or below)
17263 /// or if you want to add this padding to single-line editors.
17264 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17265 self.offset_content = offset_content;
17266 cx.notify();
17267 }
17268
17269 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17270 self.show_line_numbers = Some(show_line_numbers);
17271 cx.notify();
17272 }
17273
17274 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17275 self.disable_expand_excerpt_buttons = true;
17276 cx.notify();
17277 }
17278
17279 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17280 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17281 cx.notify();
17282 }
17283
17284 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17285 self.show_code_actions = Some(show_code_actions);
17286 cx.notify();
17287 }
17288
17289 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17290 self.show_runnables = Some(show_runnables);
17291 cx.notify();
17292 }
17293
17294 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17295 self.show_breakpoints = Some(show_breakpoints);
17296 cx.notify();
17297 }
17298
17299 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17300 if self.display_map.read(cx).masked != masked {
17301 self.display_map.update(cx, |map, _| map.masked = masked);
17302 }
17303 cx.notify()
17304 }
17305
17306 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17307 self.show_wrap_guides = Some(show_wrap_guides);
17308 cx.notify();
17309 }
17310
17311 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17312 self.show_indent_guides = Some(show_indent_guides);
17313 cx.notify();
17314 }
17315
17316 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17317 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17318 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17319 if let Some(dir) = file.abs_path(cx).parent() {
17320 return Some(dir.to_owned());
17321 }
17322 }
17323
17324 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17325 return Some(project_path.path.to_path_buf());
17326 }
17327 }
17328
17329 None
17330 }
17331
17332 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17333 self.active_excerpt(cx)?
17334 .1
17335 .read(cx)
17336 .file()
17337 .and_then(|f| f.as_local())
17338 }
17339
17340 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17341 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17342 let buffer = buffer.read(cx);
17343 if let Some(project_path) = buffer.project_path(cx) {
17344 let project = self.project.as_ref()?.read(cx);
17345 project.absolute_path(&project_path, cx)
17346 } else {
17347 buffer
17348 .file()
17349 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17350 }
17351 })
17352 }
17353
17354 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17355 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17356 let project_path = buffer.read(cx).project_path(cx)?;
17357 let project = self.project.as_ref()?.read(cx);
17358 let entry = project.entry_for_path(&project_path, cx)?;
17359 let path = entry.path.to_path_buf();
17360 Some(path)
17361 })
17362 }
17363
17364 pub fn reveal_in_finder(
17365 &mut self,
17366 _: &RevealInFileManager,
17367 _window: &mut Window,
17368 cx: &mut Context<Self>,
17369 ) {
17370 if let Some(target) = self.target_file(cx) {
17371 cx.reveal_path(&target.abs_path(cx));
17372 }
17373 }
17374
17375 pub fn copy_path(
17376 &mut self,
17377 _: &zed_actions::workspace::CopyPath,
17378 _window: &mut Window,
17379 cx: &mut Context<Self>,
17380 ) {
17381 if let Some(path) = self.target_file_abs_path(cx) {
17382 if let Some(path) = path.to_str() {
17383 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17384 }
17385 }
17386 }
17387
17388 pub fn copy_relative_path(
17389 &mut self,
17390 _: &zed_actions::workspace::CopyRelativePath,
17391 _window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) {
17394 if let Some(path) = self.target_file_path(cx) {
17395 if let Some(path) = path.to_str() {
17396 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17397 }
17398 }
17399 }
17400
17401 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17402 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17403 buffer.read(cx).project_path(cx)
17404 } else {
17405 None
17406 }
17407 }
17408
17409 // Returns true if the editor handled a go-to-line request
17410 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17411 maybe!({
17412 let breakpoint_store = self.breakpoint_store.as_ref()?;
17413
17414 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17415 else {
17416 self.clear_row_highlights::<ActiveDebugLine>();
17417 return None;
17418 };
17419
17420 let position = active_stack_frame.position;
17421 let buffer_id = position.buffer_id?;
17422 let snapshot = self
17423 .project
17424 .as_ref()?
17425 .read(cx)
17426 .buffer_for_id(buffer_id, cx)?
17427 .read(cx)
17428 .snapshot();
17429
17430 let mut handled = false;
17431 for (id, ExcerptRange { context, .. }) in
17432 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17433 {
17434 if context.start.cmp(&position, &snapshot).is_ge()
17435 || context.end.cmp(&position, &snapshot).is_lt()
17436 {
17437 continue;
17438 }
17439 let snapshot = self.buffer.read(cx).snapshot(cx);
17440 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17441
17442 handled = true;
17443 self.clear_row_highlights::<ActiveDebugLine>();
17444
17445 self.go_to_line::<ActiveDebugLine>(
17446 multibuffer_anchor,
17447 Some(cx.theme().colors().editor_debugger_active_line_background),
17448 window,
17449 cx,
17450 );
17451
17452 cx.notify();
17453 }
17454
17455 handled.then_some(())
17456 })
17457 .is_some()
17458 }
17459
17460 pub fn copy_file_name_without_extension(
17461 &mut self,
17462 _: &CopyFileNameWithoutExtension,
17463 _: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) {
17466 if let Some(file) = self.target_file(cx) {
17467 if let Some(file_stem) = file.path().file_stem() {
17468 if let Some(name) = file_stem.to_str() {
17469 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17470 }
17471 }
17472 }
17473 }
17474
17475 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17476 if let Some(file) = self.target_file(cx) {
17477 if let Some(file_name) = file.path().file_name() {
17478 if let Some(name) = file_name.to_str() {
17479 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17480 }
17481 }
17482 }
17483 }
17484
17485 pub fn toggle_git_blame(
17486 &mut self,
17487 _: &::git::Blame,
17488 window: &mut Window,
17489 cx: &mut Context<Self>,
17490 ) {
17491 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17492
17493 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17494 self.start_git_blame(true, window, cx);
17495 }
17496
17497 cx.notify();
17498 }
17499
17500 pub fn toggle_git_blame_inline(
17501 &mut self,
17502 _: &ToggleGitBlameInline,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 self.toggle_git_blame_inline_internal(true, window, cx);
17507 cx.notify();
17508 }
17509
17510 pub fn open_git_blame_commit(
17511 &mut self,
17512 _: &OpenGitBlameCommit,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 self.open_git_blame_commit_internal(window, cx);
17517 }
17518
17519 fn open_git_blame_commit_internal(
17520 &mut self,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) -> Option<()> {
17524 let blame = self.blame.as_ref()?;
17525 let snapshot = self.snapshot(window, cx);
17526 let cursor = self.selections.newest::<Point>(cx).head();
17527 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17528 let blame_entry = blame
17529 .update(cx, |blame, cx| {
17530 blame
17531 .blame_for_rows(
17532 &[RowInfo {
17533 buffer_id: Some(buffer.remote_id()),
17534 buffer_row: Some(point.row),
17535 ..Default::default()
17536 }],
17537 cx,
17538 )
17539 .next()
17540 })
17541 .flatten()?;
17542 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17543 let repo = blame.read(cx).repository(cx)?;
17544 let workspace = self.workspace()?.downgrade();
17545 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17546 None
17547 }
17548
17549 pub fn git_blame_inline_enabled(&self) -> bool {
17550 self.git_blame_inline_enabled
17551 }
17552
17553 pub fn toggle_selection_menu(
17554 &mut self,
17555 _: &ToggleSelectionMenu,
17556 _: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) {
17559 self.show_selection_menu = self
17560 .show_selection_menu
17561 .map(|show_selections_menu| !show_selections_menu)
17562 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17563
17564 cx.notify();
17565 }
17566
17567 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17568 self.show_selection_menu
17569 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17570 }
17571
17572 fn start_git_blame(
17573 &mut self,
17574 user_triggered: bool,
17575 window: &mut Window,
17576 cx: &mut Context<Self>,
17577 ) {
17578 if let Some(project) = self.project.as_ref() {
17579 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17580 return;
17581 };
17582
17583 if buffer.read(cx).file().is_none() {
17584 return;
17585 }
17586
17587 let focused = self.focus_handle(cx).contains_focused(window, cx);
17588
17589 let project = project.clone();
17590 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17591 self.blame_subscription =
17592 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17593 self.blame = Some(blame);
17594 }
17595 }
17596
17597 fn toggle_git_blame_inline_internal(
17598 &mut self,
17599 user_triggered: bool,
17600 window: &mut Window,
17601 cx: &mut Context<Self>,
17602 ) {
17603 if self.git_blame_inline_enabled {
17604 self.git_blame_inline_enabled = false;
17605 self.show_git_blame_inline = false;
17606 self.show_git_blame_inline_delay_task.take();
17607 } else {
17608 self.git_blame_inline_enabled = true;
17609 self.start_git_blame_inline(user_triggered, window, cx);
17610 }
17611
17612 cx.notify();
17613 }
17614
17615 fn start_git_blame_inline(
17616 &mut self,
17617 user_triggered: bool,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 self.start_git_blame(user_triggered, window, cx);
17622
17623 if ProjectSettings::get_global(cx)
17624 .git
17625 .inline_blame_delay()
17626 .is_some()
17627 {
17628 self.start_inline_blame_timer(window, cx);
17629 } else {
17630 self.show_git_blame_inline = true
17631 }
17632 }
17633
17634 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17635 self.blame.as_ref()
17636 }
17637
17638 pub fn show_git_blame_gutter(&self) -> bool {
17639 self.show_git_blame_gutter
17640 }
17641
17642 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17643 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17644 }
17645
17646 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17647 self.show_git_blame_inline
17648 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17649 && !self.newest_selection_head_on_empty_line(cx)
17650 && self.has_blame_entries(cx)
17651 }
17652
17653 fn has_blame_entries(&self, cx: &App) -> bool {
17654 self.blame()
17655 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17656 }
17657
17658 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17659 let cursor_anchor = self.selections.newest_anchor().head();
17660
17661 let snapshot = self.buffer.read(cx).snapshot(cx);
17662 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17663
17664 snapshot.line_len(buffer_row) == 0
17665 }
17666
17667 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17668 let buffer_and_selection = maybe!({
17669 let selection = self.selections.newest::<Point>(cx);
17670 let selection_range = selection.range();
17671
17672 let multi_buffer = self.buffer().read(cx);
17673 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17674 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17675
17676 let (buffer, range, _) = if selection.reversed {
17677 buffer_ranges.first()
17678 } else {
17679 buffer_ranges.last()
17680 }?;
17681
17682 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17683 ..text::ToPoint::to_point(&range.end, &buffer).row;
17684 Some((
17685 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17686 selection,
17687 ))
17688 });
17689
17690 let Some((buffer, selection)) = buffer_and_selection else {
17691 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17692 };
17693
17694 let Some(project) = self.project.as_ref() else {
17695 return Task::ready(Err(anyhow!("editor does not have project")));
17696 };
17697
17698 project.update(cx, |project, cx| {
17699 project.get_permalink_to_line(&buffer, selection, cx)
17700 })
17701 }
17702
17703 pub fn copy_permalink_to_line(
17704 &mut self,
17705 _: &CopyPermalinkToLine,
17706 window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 let permalink_task = self.get_permalink_to_line(cx);
17710 let workspace = self.workspace();
17711
17712 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17713 Ok(permalink) => {
17714 cx.update(|_, cx| {
17715 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17716 })
17717 .ok();
17718 }
17719 Err(err) => {
17720 let message = format!("Failed to copy permalink: {err}");
17721
17722 anyhow::Result::<()>::Err(err).log_err();
17723
17724 if let Some(workspace) = workspace {
17725 workspace
17726 .update_in(cx, |workspace, _, cx| {
17727 struct CopyPermalinkToLine;
17728
17729 workspace.show_toast(
17730 Toast::new(
17731 NotificationId::unique::<CopyPermalinkToLine>(),
17732 message,
17733 ),
17734 cx,
17735 )
17736 })
17737 .ok();
17738 }
17739 }
17740 })
17741 .detach();
17742 }
17743
17744 pub fn copy_file_location(
17745 &mut self,
17746 _: &CopyFileLocation,
17747 _: &mut Window,
17748 cx: &mut Context<Self>,
17749 ) {
17750 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17751 if let Some(file) = self.target_file(cx) {
17752 if let Some(path) = file.path().to_str() {
17753 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17754 }
17755 }
17756 }
17757
17758 pub fn open_permalink_to_line(
17759 &mut self,
17760 _: &OpenPermalinkToLine,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 let permalink_task = self.get_permalink_to_line(cx);
17765 let workspace = self.workspace();
17766
17767 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17768 Ok(permalink) => {
17769 cx.update(|_, cx| {
17770 cx.open_url(permalink.as_ref());
17771 })
17772 .ok();
17773 }
17774 Err(err) => {
17775 let message = format!("Failed to open permalink: {err}");
17776
17777 anyhow::Result::<()>::Err(err).log_err();
17778
17779 if let Some(workspace) = workspace {
17780 workspace
17781 .update(cx, |workspace, cx| {
17782 struct OpenPermalinkToLine;
17783
17784 workspace.show_toast(
17785 Toast::new(
17786 NotificationId::unique::<OpenPermalinkToLine>(),
17787 message,
17788 ),
17789 cx,
17790 )
17791 })
17792 .ok();
17793 }
17794 }
17795 })
17796 .detach();
17797 }
17798
17799 pub fn insert_uuid_v4(
17800 &mut self,
17801 _: &InsertUuidV4,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 self.insert_uuid(UuidVersion::V4, window, cx);
17806 }
17807
17808 pub fn insert_uuid_v7(
17809 &mut self,
17810 _: &InsertUuidV7,
17811 window: &mut Window,
17812 cx: &mut Context<Self>,
17813 ) {
17814 self.insert_uuid(UuidVersion::V7, window, cx);
17815 }
17816
17817 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17818 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17819 self.transact(window, cx, |this, window, cx| {
17820 let edits = this
17821 .selections
17822 .all::<Point>(cx)
17823 .into_iter()
17824 .map(|selection| {
17825 let uuid = match version {
17826 UuidVersion::V4 => uuid::Uuid::new_v4(),
17827 UuidVersion::V7 => uuid::Uuid::now_v7(),
17828 };
17829
17830 (selection.range(), uuid.to_string())
17831 });
17832 this.edit(edits, cx);
17833 this.refresh_inline_completion(true, false, window, cx);
17834 });
17835 }
17836
17837 pub fn open_selections_in_multibuffer(
17838 &mut self,
17839 _: &OpenSelectionsInMultibuffer,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 let multibuffer = self.buffer.read(cx);
17844
17845 let Some(buffer) = multibuffer.as_singleton() else {
17846 return;
17847 };
17848
17849 let Some(workspace) = self.workspace() else {
17850 return;
17851 };
17852
17853 let locations = self
17854 .selections
17855 .disjoint_anchors()
17856 .iter()
17857 .map(|selection| {
17858 let range = if selection.reversed {
17859 selection.end.text_anchor..selection.start.text_anchor
17860 } else {
17861 selection.start.text_anchor..selection.end.text_anchor
17862 };
17863 Location {
17864 buffer: buffer.clone(),
17865 range,
17866 }
17867 })
17868 .collect::<Vec<_>>();
17869
17870 let title = multibuffer.title(cx).to_string();
17871
17872 cx.spawn_in(window, async move |_, cx| {
17873 workspace.update_in(cx, |workspace, window, cx| {
17874 Self::open_locations_in_multibuffer(
17875 workspace,
17876 locations,
17877 format!("Selections for '{title}'"),
17878 false,
17879 MultibufferSelectionMode::All,
17880 window,
17881 cx,
17882 );
17883 })
17884 })
17885 .detach();
17886 }
17887
17888 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17889 /// last highlight added will be used.
17890 ///
17891 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17892 pub fn highlight_rows<T: 'static>(
17893 &mut self,
17894 range: Range<Anchor>,
17895 color: Hsla,
17896 options: RowHighlightOptions,
17897 cx: &mut Context<Self>,
17898 ) {
17899 let snapshot = self.buffer().read(cx).snapshot(cx);
17900 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17901 let ix = row_highlights.binary_search_by(|highlight| {
17902 Ordering::Equal
17903 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17904 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17905 });
17906
17907 if let Err(mut ix) = ix {
17908 let index = post_inc(&mut self.highlight_order);
17909
17910 // If this range intersects with the preceding highlight, then merge it with
17911 // the preceding highlight. Otherwise insert a new highlight.
17912 let mut merged = false;
17913 if ix > 0 {
17914 let prev_highlight = &mut row_highlights[ix - 1];
17915 if prev_highlight
17916 .range
17917 .end
17918 .cmp(&range.start, &snapshot)
17919 .is_ge()
17920 {
17921 ix -= 1;
17922 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17923 prev_highlight.range.end = range.end;
17924 }
17925 merged = true;
17926 prev_highlight.index = index;
17927 prev_highlight.color = color;
17928 prev_highlight.options = options;
17929 }
17930 }
17931
17932 if !merged {
17933 row_highlights.insert(
17934 ix,
17935 RowHighlight {
17936 range: range.clone(),
17937 index,
17938 color,
17939 options,
17940 type_id: TypeId::of::<T>(),
17941 },
17942 );
17943 }
17944
17945 // If any of the following highlights intersect with this one, merge them.
17946 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17947 let highlight = &row_highlights[ix];
17948 if next_highlight
17949 .range
17950 .start
17951 .cmp(&highlight.range.end, &snapshot)
17952 .is_le()
17953 {
17954 if next_highlight
17955 .range
17956 .end
17957 .cmp(&highlight.range.end, &snapshot)
17958 .is_gt()
17959 {
17960 row_highlights[ix].range.end = next_highlight.range.end;
17961 }
17962 row_highlights.remove(ix + 1);
17963 } else {
17964 break;
17965 }
17966 }
17967 }
17968 }
17969
17970 /// Remove any highlighted row ranges of the given type that intersect the
17971 /// given ranges.
17972 pub fn remove_highlighted_rows<T: 'static>(
17973 &mut self,
17974 ranges_to_remove: Vec<Range<Anchor>>,
17975 cx: &mut Context<Self>,
17976 ) {
17977 let snapshot = self.buffer().read(cx).snapshot(cx);
17978 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17979 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17980 row_highlights.retain(|highlight| {
17981 while let Some(range_to_remove) = ranges_to_remove.peek() {
17982 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17983 Ordering::Less | Ordering::Equal => {
17984 ranges_to_remove.next();
17985 }
17986 Ordering::Greater => {
17987 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17988 Ordering::Less | Ordering::Equal => {
17989 return false;
17990 }
17991 Ordering::Greater => break,
17992 }
17993 }
17994 }
17995 }
17996
17997 true
17998 })
17999 }
18000
18001 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18002 pub fn clear_row_highlights<T: 'static>(&mut self) {
18003 self.highlighted_rows.remove(&TypeId::of::<T>());
18004 }
18005
18006 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18007 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18008 self.highlighted_rows
18009 .get(&TypeId::of::<T>())
18010 .map_or(&[] as &[_], |vec| vec.as_slice())
18011 .iter()
18012 .map(|highlight| (highlight.range.clone(), highlight.color))
18013 }
18014
18015 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18016 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18017 /// Allows to ignore certain kinds of highlights.
18018 pub fn highlighted_display_rows(
18019 &self,
18020 window: &mut Window,
18021 cx: &mut App,
18022 ) -> BTreeMap<DisplayRow, LineHighlight> {
18023 let snapshot = self.snapshot(window, cx);
18024 let mut used_highlight_orders = HashMap::default();
18025 self.highlighted_rows
18026 .iter()
18027 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18028 .fold(
18029 BTreeMap::<DisplayRow, LineHighlight>::new(),
18030 |mut unique_rows, highlight| {
18031 let start = highlight.range.start.to_display_point(&snapshot);
18032 let end = highlight.range.end.to_display_point(&snapshot);
18033 let start_row = start.row().0;
18034 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18035 && end.column() == 0
18036 {
18037 end.row().0.saturating_sub(1)
18038 } else {
18039 end.row().0
18040 };
18041 for row in start_row..=end_row {
18042 let used_index =
18043 used_highlight_orders.entry(row).or_insert(highlight.index);
18044 if highlight.index >= *used_index {
18045 *used_index = highlight.index;
18046 unique_rows.insert(
18047 DisplayRow(row),
18048 LineHighlight {
18049 include_gutter: highlight.options.include_gutter,
18050 border: None,
18051 background: highlight.color.into(),
18052 type_id: Some(highlight.type_id),
18053 },
18054 );
18055 }
18056 }
18057 unique_rows
18058 },
18059 )
18060 }
18061
18062 pub fn highlighted_display_row_for_autoscroll(
18063 &self,
18064 snapshot: &DisplaySnapshot,
18065 ) -> Option<DisplayRow> {
18066 self.highlighted_rows
18067 .values()
18068 .flat_map(|highlighted_rows| highlighted_rows.iter())
18069 .filter_map(|highlight| {
18070 if highlight.options.autoscroll {
18071 Some(highlight.range.start.to_display_point(snapshot).row())
18072 } else {
18073 None
18074 }
18075 })
18076 .min()
18077 }
18078
18079 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18080 self.highlight_background::<SearchWithinRange>(
18081 ranges,
18082 |colors| colors.editor_document_highlight_read_background,
18083 cx,
18084 )
18085 }
18086
18087 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18088 self.breadcrumb_header = Some(new_header);
18089 }
18090
18091 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18092 self.clear_background_highlights::<SearchWithinRange>(cx);
18093 }
18094
18095 pub fn highlight_background<T: 'static>(
18096 &mut self,
18097 ranges: &[Range<Anchor>],
18098 color_fetcher: fn(&ThemeColors) -> Hsla,
18099 cx: &mut Context<Self>,
18100 ) {
18101 self.background_highlights
18102 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18103 self.scrollbar_marker_state.dirty = true;
18104 cx.notify();
18105 }
18106
18107 pub fn clear_background_highlights<T: 'static>(
18108 &mut self,
18109 cx: &mut Context<Self>,
18110 ) -> Option<BackgroundHighlight> {
18111 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18112 if !text_highlights.1.is_empty() {
18113 self.scrollbar_marker_state.dirty = true;
18114 cx.notify();
18115 }
18116 Some(text_highlights)
18117 }
18118
18119 pub fn highlight_gutter<T: 'static>(
18120 &mut self,
18121 ranges: &[Range<Anchor>],
18122 color_fetcher: fn(&App) -> Hsla,
18123 cx: &mut Context<Self>,
18124 ) {
18125 self.gutter_highlights
18126 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18127 cx.notify();
18128 }
18129
18130 pub fn clear_gutter_highlights<T: 'static>(
18131 &mut self,
18132 cx: &mut Context<Self>,
18133 ) -> Option<GutterHighlight> {
18134 cx.notify();
18135 self.gutter_highlights.remove(&TypeId::of::<T>())
18136 }
18137
18138 #[cfg(feature = "test-support")]
18139 pub fn all_text_background_highlights(
18140 &self,
18141 window: &mut Window,
18142 cx: &mut Context<Self>,
18143 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18144 let snapshot = self.snapshot(window, cx);
18145 let buffer = &snapshot.buffer_snapshot;
18146 let start = buffer.anchor_before(0);
18147 let end = buffer.anchor_after(buffer.len());
18148 let theme = cx.theme().colors();
18149 self.background_highlights_in_range(start..end, &snapshot, theme)
18150 }
18151
18152 #[cfg(feature = "test-support")]
18153 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18154 let snapshot = self.buffer().read(cx).snapshot(cx);
18155
18156 let highlights = self
18157 .background_highlights
18158 .get(&TypeId::of::<items::BufferSearchHighlights>());
18159
18160 if let Some((_color, ranges)) = highlights {
18161 ranges
18162 .iter()
18163 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18164 .collect_vec()
18165 } else {
18166 vec![]
18167 }
18168 }
18169
18170 fn document_highlights_for_position<'a>(
18171 &'a self,
18172 position: Anchor,
18173 buffer: &'a MultiBufferSnapshot,
18174 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18175 let read_highlights = self
18176 .background_highlights
18177 .get(&TypeId::of::<DocumentHighlightRead>())
18178 .map(|h| &h.1);
18179 let write_highlights = self
18180 .background_highlights
18181 .get(&TypeId::of::<DocumentHighlightWrite>())
18182 .map(|h| &h.1);
18183 let left_position = position.bias_left(buffer);
18184 let right_position = position.bias_right(buffer);
18185 read_highlights
18186 .into_iter()
18187 .chain(write_highlights)
18188 .flat_map(move |ranges| {
18189 let start_ix = match ranges.binary_search_by(|probe| {
18190 let cmp = probe.end.cmp(&left_position, buffer);
18191 if cmp.is_ge() {
18192 Ordering::Greater
18193 } else {
18194 Ordering::Less
18195 }
18196 }) {
18197 Ok(i) | Err(i) => i,
18198 };
18199
18200 ranges[start_ix..]
18201 .iter()
18202 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18203 })
18204 }
18205
18206 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18207 self.background_highlights
18208 .get(&TypeId::of::<T>())
18209 .map_or(false, |(_, highlights)| !highlights.is_empty())
18210 }
18211
18212 pub fn background_highlights_in_range(
18213 &self,
18214 search_range: Range<Anchor>,
18215 display_snapshot: &DisplaySnapshot,
18216 theme: &ThemeColors,
18217 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18218 let mut results = Vec::new();
18219 for (color_fetcher, ranges) in self.background_highlights.values() {
18220 let color = color_fetcher(theme);
18221 let start_ix = match ranges.binary_search_by(|probe| {
18222 let cmp = probe
18223 .end
18224 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18225 if cmp.is_gt() {
18226 Ordering::Greater
18227 } else {
18228 Ordering::Less
18229 }
18230 }) {
18231 Ok(i) | Err(i) => i,
18232 };
18233 for range in &ranges[start_ix..] {
18234 if range
18235 .start
18236 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18237 .is_ge()
18238 {
18239 break;
18240 }
18241
18242 let start = range.start.to_display_point(display_snapshot);
18243 let end = range.end.to_display_point(display_snapshot);
18244 results.push((start..end, color))
18245 }
18246 }
18247 results
18248 }
18249
18250 pub fn background_highlight_row_ranges<T: 'static>(
18251 &self,
18252 search_range: Range<Anchor>,
18253 display_snapshot: &DisplaySnapshot,
18254 count: usize,
18255 ) -> Vec<RangeInclusive<DisplayPoint>> {
18256 let mut results = Vec::new();
18257 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18258 return vec![];
18259 };
18260
18261 let start_ix = match ranges.binary_search_by(|probe| {
18262 let cmp = probe
18263 .end
18264 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18265 if cmp.is_gt() {
18266 Ordering::Greater
18267 } else {
18268 Ordering::Less
18269 }
18270 }) {
18271 Ok(i) | Err(i) => i,
18272 };
18273 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18274 if let (Some(start_display), Some(end_display)) = (start, end) {
18275 results.push(
18276 start_display.to_display_point(display_snapshot)
18277 ..=end_display.to_display_point(display_snapshot),
18278 );
18279 }
18280 };
18281 let mut start_row: Option<Point> = None;
18282 let mut end_row: Option<Point> = None;
18283 if ranges.len() > count {
18284 return Vec::new();
18285 }
18286 for range in &ranges[start_ix..] {
18287 if range
18288 .start
18289 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18290 .is_ge()
18291 {
18292 break;
18293 }
18294 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18295 if let Some(current_row) = &end_row {
18296 if end.row == current_row.row {
18297 continue;
18298 }
18299 }
18300 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18301 if start_row.is_none() {
18302 assert_eq!(end_row, None);
18303 start_row = Some(start);
18304 end_row = Some(end);
18305 continue;
18306 }
18307 if let Some(current_end) = end_row.as_mut() {
18308 if start.row > current_end.row + 1 {
18309 push_region(start_row, end_row);
18310 start_row = Some(start);
18311 end_row = Some(end);
18312 } else {
18313 // Merge two hunks.
18314 *current_end = end;
18315 }
18316 } else {
18317 unreachable!();
18318 }
18319 }
18320 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18321 push_region(start_row, end_row);
18322 results
18323 }
18324
18325 pub fn gutter_highlights_in_range(
18326 &self,
18327 search_range: Range<Anchor>,
18328 display_snapshot: &DisplaySnapshot,
18329 cx: &App,
18330 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18331 let mut results = Vec::new();
18332 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18333 let color = color_fetcher(cx);
18334 let start_ix = match ranges.binary_search_by(|probe| {
18335 let cmp = probe
18336 .end
18337 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18338 if cmp.is_gt() {
18339 Ordering::Greater
18340 } else {
18341 Ordering::Less
18342 }
18343 }) {
18344 Ok(i) | Err(i) => i,
18345 };
18346 for range in &ranges[start_ix..] {
18347 if range
18348 .start
18349 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18350 .is_ge()
18351 {
18352 break;
18353 }
18354
18355 let start = range.start.to_display_point(display_snapshot);
18356 let end = range.end.to_display_point(display_snapshot);
18357 results.push((start..end, color))
18358 }
18359 }
18360 results
18361 }
18362
18363 /// Get the text ranges corresponding to the redaction query
18364 pub fn redacted_ranges(
18365 &self,
18366 search_range: Range<Anchor>,
18367 display_snapshot: &DisplaySnapshot,
18368 cx: &App,
18369 ) -> Vec<Range<DisplayPoint>> {
18370 display_snapshot
18371 .buffer_snapshot
18372 .redacted_ranges(search_range, |file| {
18373 if let Some(file) = file {
18374 file.is_private()
18375 && EditorSettings::get(
18376 Some(SettingsLocation {
18377 worktree_id: file.worktree_id(cx),
18378 path: file.path().as_ref(),
18379 }),
18380 cx,
18381 )
18382 .redact_private_values
18383 } else {
18384 false
18385 }
18386 })
18387 .map(|range| {
18388 range.start.to_display_point(display_snapshot)
18389 ..range.end.to_display_point(display_snapshot)
18390 })
18391 .collect()
18392 }
18393
18394 pub fn highlight_text<T: 'static>(
18395 &mut self,
18396 ranges: Vec<Range<Anchor>>,
18397 style: HighlightStyle,
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.display_map.update(cx, |map, _| {
18401 map.highlight_text(TypeId::of::<T>(), ranges, style)
18402 });
18403 cx.notify();
18404 }
18405
18406 pub(crate) fn highlight_inlays<T: 'static>(
18407 &mut self,
18408 highlights: Vec<InlayHighlight>,
18409 style: HighlightStyle,
18410 cx: &mut Context<Self>,
18411 ) {
18412 self.display_map.update(cx, |map, _| {
18413 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18414 });
18415 cx.notify();
18416 }
18417
18418 pub fn text_highlights<'a, T: 'static>(
18419 &'a self,
18420 cx: &'a App,
18421 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18422 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18423 }
18424
18425 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18426 let cleared = self
18427 .display_map
18428 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18429 if cleared {
18430 cx.notify();
18431 }
18432 }
18433
18434 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18435 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18436 && self.focus_handle.is_focused(window)
18437 }
18438
18439 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18440 self.show_cursor_when_unfocused = is_enabled;
18441 cx.notify();
18442 }
18443
18444 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18445 cx.notify();
18446 }
18447
18448 fn on_debug_session_event(
18449 &mut self,
18450 _session: Entity<Session>,
18451 event: &SessionEvent,
18452 cx: &mut Context<Self>,
18453 ) {
18454 match event {
18455 SessionEvent::InvalidateInlineValue => {
18456 self.refresh_inline_values(cx);
18457 }
18458 _ => {}
18459 }
18460 }
18461
18462 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18463 let Some(project) = self.project.clone() else {
18464 return;
18465 };
18466
18467 if !self.inline_value_cache.enabled {
18468 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18469 self.splice_inlays(&inlays, Vec::new(), cx);
18470 return;
18471 }
18472
18473 let current_execution_position = self
18474 .highlighted_rows
18475 .get(&TypeId::of::<ActiveDebugLine>())
18476 .and_then(|lines| lines.last().map(|line| line.range.start));
18477
18478 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18479 let inline_values = editor
18480 .update(cx, |editor, cx| {
18481 let Some(current_execution_position) = current_execution_position else {
18482 return Some(Task::ready(Ok(Vec::new())));
18483 };
18484
18485 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18486 let snapshot = buffer.snapshot(cx);
18487
18488 let excerpt = snapshot.excerpt_containing(
18489 current_execution_position..current_execution_position,
18490 )?;
18491
18492 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18493 })?;
18494
18495 let range =
18496 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18497
18498 project.inline_values(buffer, range, cx)
18499 })
18500 .ok()
18501 .flatten()?
18502 .await
18503 .context("refreshing debugger inlays")
18504 .log_err()?;
18505
18506 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18507
18508 for (buffer_id, inline_value) in inline_values
18509 .into_iter()
18510 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18511 {
18512 buffer_inline_values
18513 .entry(buffer_id)
18514 .or_default()
18515 .push(inline_value);
18516 }
18517
18518 editor
18519 .update(cx, |editor, cx| {
18520 let snapshot = editor.buffer.read(cx).snapshot(cx);
18521 let mut new_inlays = Vec::default();
18522
18523 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18524 let buffer_id = buffer_snapshot.remote_id();
18525 buffer_inline_values
18526 .get(&buffer_id)
18527 .into_iter()
18528 .flatten()
18529 .for_each(|hint| {
18530 let inlay = Inlay::debugger_hint(
18531 post_inc(&mut editor.next_inlay_id),
18532 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18533 hint.text(),
18534 );
18535
18536 new_inlays.push(inlay);
18537 });
18538 }
18539
18540 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18541 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18542
18543 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18544 })
18545 .ok()?;
18546 Some(())
18547 });
18548 }
18549
18550 fn on_buffer_event(
18551 &mut self,
18552 multibuffer: &Entity<MultiBuffer>,
18553 event: &multi_buffer::Event,
18554 window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 match event {
18558 multi_buffer::Event::Edited {
18559 singleton_buffer_edited,
18560 edited_buffer: buffer_edited,
18561 } => {
18562 self.scrollbar_marker_state.dirty = true;
18563 self.active_indent_guides_state.dirty = true;
18564 self.refresh_active_diagnostics(cx);
18565 self.refresh_code_actions(window, cx);
18566 self.refresh_selected_text_highlights(true, window, cx);
18567 refresh_matching_bracket_highlights(self, window, cx);
18568 if self.has_active_inline_completion() {
18569 self.update_visible_inline_completion(window, cx);
18570 }
18571 if let Some(buffer) = buffer_edited {
18572 let buffer_id = buffer.read(cx).remote_id();
18573 if !self.registered_buffers.contains_key(&buffer_id) {
18574 if let Some(project) = self.project.as_ref() {
18575 project.update(cx, |project, cx| {
18576 self.registered_buffers.insert(
18577 buffer_id,
18578 project.register_buffer_with_language_servers(&buffer, cx),
18579 );
18580 })
18581 }
18582 }
18583 }
18584 cx.emit(EditorEvent::BufferEdited);
18585 cx.emit(SearchEvent::MatchesInvalidated);
18586 if *singleton_buffer_edited {
18587 if let Some(project) = &self.project {
18588 #[allow(clippy::mutable_key_type)]
18589 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18590 multibuffer
18591 .all_buffers()
18592 .into_iter()
18593 .filter_map(|buffer| {
18594 buffer.update(cx, |buffer, cx| {
18595 let language = buffer.language()?;
18596 let should_discard = project.update(cx, |project, cx| {
18597 project.is_local()
18598 && !project.has_language_servers_for(buffer, cx)
18599 });
18600 should_discard.not().then_some(language.clone())
18601 })
18602 })
18603 .collect::<HashSet<_>>()
18604 });
18605 if !languages_affected.is_empty() {
18606 self.refresh_inlay_hints(
18607 InlayHintRefreshReason::BufferEdited(languages_affected),
18608 cx,
18609 );
18610 }
18611 }
18612 }
18613
18614 let Some(project) = &self.project else { return };
18615 let (telemetry, is_via_ssh) = {
18616 let project = project.read(cx);
18617 let telemetry = project.client().telemetry().clone();
18618 let is_via_ssh = project.is_via_ssh();
18619 (telemetry, is_via_ssh)
18620 };
18621 refresh_linked_ranges(self, window, cx);
18622 telemetry.log_edit_event("editor", is_via_ssh);
18623 }
18624 multi_buffer::Event::ExcerptsAdded {
18625 buffer,
18626 predecessor,
18627 excerpts,
18628 } => {
18629 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18630 let buffer_id = buffer.read(cx).remote_id();
18631 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18632 if let Some(project) = &self.project {
18633 update_uncommitted_diff_for_buffer(
18634 cx.entity(),
18635 project,
18636 [buffer.clone()],
18637 self.buffer.clone(),
18638 cx,
18639 )
18640 .detach();
18641 }
18642 }
18643 cx.emit(EditorEvent::ExcerptsAdded {
18644 buffer: buffer.clone(),
18645 predecessor: *predecessor,
18646 excerpts: excerpts.clone(),
18647 });
18648 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18649 }
18650 multi_buffer::Event::ExcerptsRemoved {
18651 ids,
18652 removed_buffer_ids,
18653 } => {
18654 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18655 let buffer = self.buffer.read(cx);
18656 self.registered_buffers
18657 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18658 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18659 cx.emit(EditorEvent::ExcerptsRemoved {
18660 ids: ids.clone(),
18661 removed_buffer_ids: removed_buffer_ids.clone(),
18662 })
18663 }
18664 multi_buffer::Event::ExcerptsEdited {
18665 excerpt_ids,
18666 buffer_ids,
18667 } => {
18668 self.display_map.update(cx, |map, cx| {
18669 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18670 });
18671 cx.emit(EditorEvent::ExcerptsEdited {
18672 ids: excerpt_ids.clone(),
18673 })
18674 }
18675 multi_buffer::Event::ExcerptsExpanded { ids } => {
18676 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18677 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18678 }
18679 multi_buffer::Event::Reparsed(buffer_id) => {
18680 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18681 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18682
18683 cx.emit(EditorEvent::Reparsed(*buffer_id));
18684 }
18685 multi_buffer::Event::DiffHunksToggled => {
18686 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18687 }
18688 multi_buffer::Event::LanguageChanged(buffer_id) => {
18689 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18690 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18691 cx.emit(EditorEvent::Reparsed(*buffer_id));
18692 cx.notify();
18693 }
18694 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18695 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18696 multi_buffer::Event::FileHandleChanged
18697 | multi_buffer::Event::Reloaded
18698 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18699 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18700 multi_buffer::Event::DiagnosticsUpdated => {
18701 self.refresh_active_diagnostics(cx);
18702 self.refresh_inline_diagnostics(true, window, cx);
18703 self.scrollbar_marker_state.dirty = true;
18704 cx.notify();
18705 }
18706 _ => {}
18707 };
18708 }
18709
18710 pub fn start_temporary_diff_override(&mut self) {
18711 self.load_diff_task.take();
18712 self.temporary_diff_override = true;
18713 }
18714
18715 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18716 self.temporary_diff_override = false;
18717 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18718 self.buffer.update(cx, |buffer, cx| {
18719 buffer.set_all_diff_hunks_collapsed(cx);
18720 });
18721
18722 if let Some(project) = self.project.clone() {
18723 self.load_diff_task = Some(
18724 update_uncommitted_diff_for_buffer(
18725 cx.entity(),
18726 &project,
18727 self.buffer.read(cx).all_buffers(),
18728 self.buffer.clone(),
18729 cx,
18730 )
18731 .shared(),
18732 );
18733 }
18734 }
18735
18736 fn on_display_map_changed(
18737 &mut self,
18738 _: Entity<DisplayMap>,
18739 _: &mut Window,
18740 cx: &mut Context<Self>,
18741 ) {
18742 cx.notify();
18743 }
18744
18745 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18746 let new_severity = if self.diagnostics_enabled() {
18747 EditorSettings::get_global(cx)
18748 .diagnostics_max_severity
18749 .unwrap_or(DiagnosticSeverity::Hint)
18750 } else {
18751 DiagnosticSeverity::Off
18752 };
18753 self.set_max_diagnostics_severity(new_severity, cx);
18754 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18755 self.update_edit_prediction_settings(cx);
18756 self.refresh_inline_completion(true, false, window, cx);
18757 self.refresh_inlay_hints(
18758 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18759 self.selections.newest_anchor().head(),
18760 &self.buffer.read(cx).snapshot(cx),
18761 cx,
18762 )),
18763 cx,
18764 );
18765
18766 let old_cursor_shape = self.cursor_shape;
18767
18768 {
18769 let editor_settings = EditorSettings::get_global(cx);
18770 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18771 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18772 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18773 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18774 }
18775
18776 if old_cursor_shape != self.cursor_shape {
18777 cx.emit(EditorEvent::CursorShapeChanged);
18778 }
18779
18780 let project_settings = ProjectSettings::get_global(cx);
18781 self.serialize_dirty_buffers =
18782 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18783
18784 if self.mode.is_full() {
18785 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18786 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18787 if self.show_inline_diagnostics != show_inline_diagnostics {
18788 self.show_inline_diagnostics = show_inline_diagnostics;
18789 self.refresh_inline_diagnostics(false, window, cx);
18790 }
18791
18792 if self.git_blame_inline_enabled != inline_blame_enabled {
18793 self.toggle_git_blame_inline_internal(false, window, cx);
18794 }
18795
18796 let minimap_settings = EditorSettings::get_global(cx).minimap;
18797 if self.minimap_visibility != MinimapVisibility::Disabled {
18798 if self.minimap_visibility.settings_visibility()
18799 != minimap_settings.minimap_enabled()
18800 {
18801 self.set_minimap_visibility(
18802 MinimapVisibility::for_mode(self.mode(), cx),
18803 window,
18804 cx,
18805 );
18806 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18807 minimap_entity.update(cx, |minimap_editor, cx| {
18808 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18809 })
18810 }
18811 }
18812 }
18813
18814 cx.notify();
18815 }
18816
18817 pub fn set_searchable(&mut self, searchable: bool) {
18818 self.searchable = searchable;
18819 }
18820
18821 pub fn searchable(&self) -> bool {
18822 self.searchable
18823 }
18824
18825 fn open_proposed_changes_editor(
18826 &mut self,
18827 _: &OpenProposedChangesEditor,
18828 window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 let Some(workspace) = self.workspace() else {
18832 cx.propagate();
18833 return;
18834 };
18835
18836 let selections = self.selections.all::<usize>(cx);
18837 let multi_buffer = self.buffer.read(cx);
18838 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18839 let mut new_selections_by_buffer = HashMap::default();
18840 for selection in selections {
18841 for (buffer, range, _) in
18842 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18843 {
18844 let mut range = range.to_point(buffer);
18845 range.start.column = 0;
18846 range.end.column = buffer.line_len(range.end.row);
18847 new_selections_by_buffer
18848 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18849 .or_insert(Vec::new())
18850 .push(range)
18851 }
18852 }
18853
18854 let proposed_changes_buffers = new_selections_by_buffer
18855 .into_iter()
18856 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18857 .collect::<Vec<_>>();
18858 let proposed_changes_editor = cx.new(|cx| {
18859 ProposedChangesEditor::new(
18860 "Proposed changes",
18861 proposed_changes_buffers,
18862 self.project.clone(),
18863 window,
18864 cx,
18865 )
18866 });
18867
18868 window.defer(cx, move |window, cx| {
18869 workspace.update(cx, |workspace, cx| {
18870 workspace.active_pane().update(cx, |pane, cx| {
18871 pane.add_item(
18872 Box::new(proposed_changes_editor),
18873 true,
18874 true,
18875 None,
18876 window,
18877 cx,
18878 );
18879 });
18880 });
18881 });
18882 }
18883
18884 pub fn open_excerpts_in_split(
18885 &mut self,
18886 _: &OpenExcerptsSplit,
18887 window: &mut Window,
18888 cx: &mut Context<Self>,
18889 ) {
18890 self.open_excerpts_common(None, true, window, cx)
18891 }
18892
18893 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18894 self.open_excerpts_common(None, false, window, cx)
18895 }
18896
18897 fn open_excerpts_common(
18898 &mut self,
18899 jump_data: Option<JumpData>,
18900 split: bool,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 let Some(workspace) = self.workspace() else {
18905 cx.propagate();
18906 return;
18907 };
18908
18909 if self.buffer.read(cx).is_singleton() {
18910 cx.propagate();
18911 return;
18912 }
18913
18914 let mut new_selections_by_buffer = HashMap::default();
18915 match &jump_data {
18916 Some(JumpData::MultiBufferPoint {
18917 excerpt_id,
18918 position,
18919 anchor,
18920 line_offset_from_top,
18921 }) => {
18922 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18923 if let Some(buffer) = multi_buffer_snapshot
18924 .buffer_id_for_excerpt(*excerpt_id)
18925 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18926 {
18927 let buffer_snapshot = buffer.read(cx).snapshot();
18928 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18929 language::ToPoint::to_point(anchor, &buffer_snapshot)
18930 } else {
18931 buffer_snapshot.clip_point(*position, Bias::Left)
18932 };
18933 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18934 new_selections_by_buffer.insert(
18935 buffer,
18936 (
18937 vec![jump_to_offset..jump_to_offset],
18938 Some(*line_offset_from_top),
18939 ),
18940 );
18941 }
18942 }
18943 Some(JumpData::MultiBufferRow {
18944 row,
18945 line_offset_from_top,
18946 }) => {
18947 let point = MultiBufferPoint::new(row.0, 0);
18948 if let Some((buffer, buffer_point, _)) =
18949 self.buffer.read(cx).point_to_buffer_point(point, cx)
18950 {
18951 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18952 new_selections_by_buffer
18953 .entry(buffer)
18954 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18955 .0
18956 .push(buffer_offset..buffer_offset)
18957 }
18958 }
18959 None => {
18960 let selections = self.selections.all::<usize>(cx);
18961 let multi_buffer = self.buffer.read(cx);
18962 for selection in selections {
18963 for (snapshot, range, _, anchor) in multi_buffer
18964 .snapshot(cx)
18965 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18966 {
18967 if let Some(anchor) = anchor {
18968 // selection is in a deleted hunk
18969 let Some(buffer_id) = anchor.buffer_id else {
18970 continue;
18971 };
18972 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18973 continue;
18974 };
18975 let offset = text::ToOffset::to_offset(
18976 &anchor.text_anchor,
18977 &buffer_handle.read(cx).snapshot(),
18978 );
18979 let range = offset..offset;
18980 new_selections_by_buffer
18981 .entry(buffer_handle)
18982 .or_insert((Vec::new(), None))
18983 .0
18984 .push(range)
18985 } else {
18986 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18987 else {
18988 continue;
18989 };
18990 new_selections_by_buffer
18991 .entry(buffer_handle)
18992 .or_insert((Vec::new(), None))
18993 .0
18994 .push(range)
18995 }
18996 }
18997 }
18998 }
18999 }
19000
19001 new_selections_by_buffer
19002 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19003
19004 if new_selections_by_buffer.is_empty() {
19005 return;
19006 }
19007
19008 // We defer the pane interaction because we ourselves are a workspace item
19009 // and activating a new item causes the pane to call a method on us reentrantly,
19010 // which panics if we're on the stack.
19011 window.defer(cx, move |window, cx| {
19012 workspace.update(cx, |workspace, cx| {
19013 let pane = if split {
19014 workspace.adjacent_pane(window, cx)
19015 } else {
19016 workspace.active_pane().clone()
19017 };
19018
19019 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19020 let editor = buffer
19021 .read(cx)
19022 .file()
19023 .is_none()
19024 .then(|| {
19025 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19026 // so `workspace.open_project_item` will never find them, always opening a new editor.
19027 // Instead, we try to activate the existing editor in the pane first.
19028 let (editor, pane_item_index) =
19029 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19030 let editor = item.downcast::<Editor>()?;
19031 let singleton_buffer =
19032 editor.read(cx).buffer().read(cx).as_singleton()?;
19033 if singleton_buffer == buffer {
19034 Some((editor, i))
19035 } else {
19036 None
19037 }
19038 })?;
19039 pane.update(cx, |pane, cx| {
19040 pane.activate_item(pane_item_index, true, true, window, cx)
19041 });
19042 Some(editor)
19043 })
19044 .flatten()
19045 .unwrap_or_else(|| {
19046 workspace.open_project_item::<Self>(
19047 pane.clone(),
19048 buffer,
19049 true,
19050 true,
19051 window,
19052 cx,
19053 )
19054 });
19055
19056 editor.update(cx, |editor, cx| {
19057 let autoscroll = match scroll_offset {
19058 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19059 None => Autoscroll::newest(),
19060 };
19061 let nav_history = editor.nav_history.take();
19062 editor.change_selections(Some(autoscroll), window, cx, |s| {
19063 s.select_ranges(ranges);
19064 });
19065 editor.nav_history = nav_history;
19066 });
19067 }
19068 })
19069 });
19070 }
19071
19072 // For now, don't allow opening excerpts in buffers that aren't backed by
19073 // regular project files.
19074 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19075 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19076 }
19077
19078 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19079 let snapshot = self.buffer.read(cx).read(cx);
19080 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19081 Some(
19082 ranges
19083 .iter()
19084 .map(move |range| {
19085 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19086 })
19087 .collect(),
19088 )
19089 }
19090
19091 fn selection_replacement_ranges(
19092 &self,
19093 range: Range<OffsetUtf16>,
19094 cx: &mut App,
19095 ) -> Vec<Range<OffsetUtf16>> {
19096 let selections = self.selections.all::<OffsetUtf16>(cx);
19097 let newest_selection = selections
19098 .iter()
19099 .max_by_key(|selection| selection.id)
19100 .unwrap();
19101 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19102 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19103 let snapshot = self.buffer.read(cx).read(cx);
19104 selections
19105 .into_iter()
19106 .map(|mut selection| {
19107 selection.start.0 =
19108 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19109 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19110 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19111 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19112 })
19113 .collect()
19114 }
19115
19116 fn report_editor_event(
19117 &self,
19118 event_type: &'static str,
19119 file_extension: Option<String>,
19120 cx: &App,
19121 ) {
19122 if cfg!(any(test, feature = "test-support")) {
19123 return;
19124 }
19125
19126 let Some(project) = &self.project else { return };
19127
19128 // If None, we are in a file without an extension
19129 let file = self
19130 .buffer
19131 .read(cx)
19132 .as_singleton()
19133 .and_then(|b| b.read(cx).file());
19134 let file_extension = file_extension.or(file
19135 .as_ref()
19136 .and_then(|file| Path::new(file.file_name(cx)).extension())
19137 .and_then(|e| e.to_str())
19138 .map(|a| a.to_string()));
19139
19140 let vim_mode = vim_enabled(cx);
19141
19142 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19143 let copilot_enabled = edit_predictions_provider
19144 == language::language_settings::EditPredictionProvider::Copilot;
19145 let copilot_enabled_for_language = self
19146 .buffer
19147 .read(cx)
19148 .language_settings(cx)
19149 .show_edit_predictions;
19150
19151 let project = project.read(cx);
19152 telemetry::event!(
19153 event_type,
19154 file_extension,
19155 vim_mode,
19156 copilot_enabled,
19157 copilot_enabled_for_language,
19158 edit_predictions_provider,
19159 is_via_ssh = project.is_via_ssh(),
19160 );
19161 }
19162
19163 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19164 /// with each line being an array of {text, highlight} objects.
19165 fn copy_highlight_json(
19166 &mut self,
19167 _: &CopyHighlightJson,
19168 window: &mut Window,
19169 cx: &mut Context<Self>,
19170 ) {
19171 #[derive(Serialize)]
19172 struct Chunk<'a> {
19173 text: String,
19174 highlight: Option<&'a str>,
19175 }
19176
19177 let snapshot = self.buffer.read(cx).snapshot(cx);
19178 let range = self
19179 .selected_text_range(false, window, cx)
19180 .and_then(|selection| {
19181 if selection.range.is_empty() {
19182 None
19183 } else {
19184 Some(selection.range)
19185 }
19186 })
19187 .unwrap_or_else(|| 0..snapshot.len());
19188
19189 let chunks = snapshot.chunks(range, true);
19190 let mut lines = Vec::new();
19191 let mut line: VecDeque<Chunk> = VecDeque::new();
19192
19193 let Some(style) = self.style.as_ref() else {
19194 return;
19195 };
19196
19197 for chunk in chunks {
19198 let highlight = chunk
19199 .syntax_highlight_id
19200 .and_then(|id| id.name(&style.syntax));
19201 let mut chunk_lines = chunk.text.split('\n').peekable();
19202 while let Some(text) = chunk_lines.next() {
19203 let mut merged_with_last_token = false;
19204 if let Some(last_token) = line.back_mut() {
19205 if last_token.highlight == highlight {
19206 last_token.text.push_str(text);
19207 merged_with_last_token = true;
19208 }
19209 }
19210
19211 if !merged_with_last_token {
19212 line.push_back(Chunk {
19213 text: text.into(),
19214 highlight,
19215 });
19216 }
19217
19218 if chunk_lines.peek().is_some() {
19219 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19220 line.pop_front();
19221 }
19222 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19223 line.pop_back();
19224 }
19225
19226 lines.push(mem::take(&mut line));
19227 }
19228 }
19229 }
19230
19231 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19232 return;
19233 };
19234 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19235 }
19236
19237 pub fn open_context_menu(
19238 &mut self,
19239 _: &OpenContextMenu,
19240 window: &mut Window,
19241 cx: &mut Context<Self>,
19242 ) {
19243 self.request_autoscroll(Autoscroll::newest(), cx);
19244 let position = self.selections.newest_display(cx).start;
19245 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19246 }
19247
19248 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19249 &self.inlay_hint_cache
19250 }
19251
19252 pub fn replay_insert_event(
19253 &mut self,
19254 text: &str,
19255 relative_utf16_range: Option<Range<isize>>,
19256 window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 if !self.input_enabled {
19260 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19261 return;
19262 }
19263 if let Some(relative_utf16_range) = relative_utf16_range {
19264 let selections = self.selections.all::<OffsetUtf16>(cx);
19265 self.change_selections(None, window, cx, |s| {
19266 let new_ranges = selections.into_iter().map(|range| {
19267 let start = OffsetUtf16(
19268 range
19269 .head()
19270 .0
19271 .saturating_add_signed(relative_utf16_range.start),
19272 );
19273 let end = OffsetUtf16(
19274 range
19275 .head()
19276 .0
19277 .saturating_add_signed(relative_utf16_range.end),
19278 );
19279 start..end
19280 });
19281 s.select_ranges(new_ranges);
19282 });
19283 }
19284
19285 self.handle_input(text, window, cx);
19286 }
19287
19288 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19289 let Some(provider) = self.semantics_provider.as_ref() else {
19290 return false;
19291 };
19292
19293 let mut supports = false;
19294 self.buffer().update(cx, |this, cx| {
19295 this.for_each_buffer(|buffer| {
19296 supports |= provider.supports_inlay_hints(buffer, cx);
19297 });
19298 });
19299
19300 supports
19301 }
19302
19303 pub fn is_focused(&self, window: &Window) -> bool {
19304 self.focus_handle.is_focused(window)
19305 }
19306
19307 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19308 cx.emit(EditorEvent::Focused);
19309
19310 if let Some(descendant) = self
19311 .last_focused_descendant
19312 .take()
19313 .and_then(|descendant| descendant.upgrade())
19314 {
19315 window.focus(&descendant);
19316 } else {
19317 if let Some(blame) = self.blame.as_ref() {
19318 blame.update(cx, GitBlame::focus)
19319 }
19320
19321 self.blink_manager.update(cx, BlinkManager::enable);
19322 self.show_cursor_names(window, cx);
19323 self.buffer.update(cx, |buffer, cx| {
19324 buffer.finalize_last_transaction(cx);
19325 if self.leader_id.is_none() {
19326 buffer.set_active_selections(
19327 &self.selections.disjoint_anchors(),
19328 self.selections.line_mode,
19329 self.cursor_shape,
19330 cx,
19331 );
19332 }
19333 });
19334 }
19335 }
19336
19337 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19338 cx.emit(EditorEvent::FocusedIn)
19339 }
19340
19341 fn handle_focus_out(
19342 &mut self,
19343 event: FocusOutEvent,
19344 _window: &mut Window,
19345 cx: &mut Context<Self>,
19346 ) {
19347 if event.blurred != self.focus_handle {
19348 self.last_focused_descendant = Some(event.blurred);
19349 }
19350 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19351 }
19352
19353 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19354 self.blink_manager.update(cx, BlinkManager::disable);
19355 self.buffer
19356 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19357
19358 if let Some(blame) = self.blame.as_ref() {
19359 blame.update(cx, GitBlame::blur)
19360 }
19361 if !self.hover_state.focused(window, cx) {
19362 hide_hover(self, cx);
19363 }
19364 if !self
19365 .context_menu
19366 .borrow()
19367 .as_ref()
19368 .is_some_and(|context_menu| context_menu.focused(window, cx))
19369 {
19370 self.hide_context_menu(window, cx);
19371 }
19372 self.discard_inline_completion(false, cx);
19373 cx.emit(EditorEvent::Blurred);
19374 cx.notify();
19375 }
19376
19377 pub fn register_action<A: Action>(
19378 &mut self,
19379 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19380 ) -> Subscription {
19381 let id = self.next_editor_action_id.post_inc();
19382 let listener = Arc::new(listener);
19383 self.editor_actions.borrow_mut().insert(
19384 id,
19385 Box::new(move |window, _| {
19386 let listener = listener.clone();
19387 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19388 let action = action.downcast_ref().unwrap();
19389 if phase == DispatchPhase::Bubble {
19390 listener(action, window, cx)
19391 }
19392 })
19393 }),
19394 );
19395
19396 let editor_actions = self.editor_actions.clone();
19397 Subscription::new(move || {
19398 editor_actions.borrow_mut().remove(&id);
19399 })
19400 }
19401
19402 pub fn file_header_size(&self) -> u32 {
19403 FILE_HEADER_HEIGHT
19404 }
19405
19406 pub fn restore(
19407 &mut self,
19408 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19409 window: &mut Window,
19410 cx: &mut Context<Self>,
19411 ) {
19412 let workspace = self.workspace();
19413 let project = self.project.as_ref();
19414 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19415 let mut tasks = Vec::new();
19416 for (buffer_id, changes) in revert_changes {
19417 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19418 buffer.update(cx, |buffer, cx| {
19419 buffer.edit(
19420 changes
19421 .into_iter()
19422 .map(|(range, text)| (range, text.to_string())),
19423 None,
19424 cx,
19425 );
19426 });
19427
19428 if let Some(project) =
19429 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19430 {
19431 project.update(cx, |project, cx| {
19432 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19433 })
19434 }
19435 }
19436 }
19437 tasks
19438 });
19439 cx.spawn_in(window, async move |_, cx| {
19440 for (buffer, task) in save_tasks {
19441 let result = task.await;
19442 if result.is_err() {
19443 let Some(path) = buffer
19444 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19445 .ok()
19446 else {
19447 continue;
19448 };
19449 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19450 let Some(task) = cx
19451 .update_window_entity(&workspace, |workspace, window, cx| {
19452 workspace
19453 .open_path_preview(path, None, false, false, false, window, cx)
19454 })
19455 .ok()
19456 else {
19457 continue;
19458 };
19459 task.await.log_err();
19460 }
19461 }
19462 }
19463 })
19464 .detach();
19465 self.change_selections(None, window, cx, |selections| selections.refresh());
19466 }
19467
19468 pub fn to_pixel_point(
19469 &self,
19470 source: multi_buffer::Anchor,
19471 editor_snapshot: &EditorSnapshot,
19472 window: &mut Window,
19473 ) -> Option<gpui::Point<Pixels>> {
19474 let source_point = source.to_display_point(editor_snapshot);
19475 self.display_to_pixel_point(source_point, editor_snapshot, window)
19476 }
19477
19478 pub fn display_to_pixel_point(
19479 &self,
19480 source: DisplayPoint,
19481 editor_snapshot: &EditorSnapshot,
19482 window: &mut Window,
19483 ) -> Option<gpui::Point<Pixels>> {
19484 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19485 let text_layout_details = self.text_layout_details(window);
19486 let scroll_top = text_layout_details
19487 .scroll_anchor
19488 .scroll_position(editor_snapshot)
19489 .y;
19490
19491 if source.row().as_f32() < scroll_top.floor() {
19492 return None;
19493 }
19494 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19495 let source_y = line_height * (source.row().as_f32() - scroll_top);
19496 Some(gpui::Point::new(source_x, source_y))
19497 }
19498
19499 pub fn has_visible_completions_menu(&self) -> bool {
19500 !self.edit_prediction_preview_is_active()
19501 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19502 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19503 })
19504 }
19505
19506 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19507 if self.mode.is_minimap() {
19508 return;
19509 }
19510 self.addons
19511 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19512 }
19513
19514 pub fn unregister_addon<T: Addon>(&mut self) {
19515 self.addons.remove(&std::any::TypeId::of::<T>());
19516 }
19517
19518 pub fn addon<T: Addon>(&self) -> Option<&T> {
19519 let type_id = std::any::TypeId::of::<T>();
19520 self.addons
19521 .get(&type_id)
19522 .and_then(|item| item.to_any().downcast_ref::<T>())
19523 }
19524
19525 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19526 let type_id = std::any::TypeId::of::<T>();
19527 self.addons
19528 .get_mut(&type_id)
19529 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19530 }
19531
19532 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19533 let text_layout_details = self.text_layout_details(window);
19534 let style = &text_layout_details.editor_style;
19535 let font_id = window.text_system().resolve_font(&style.text.font());
19536 let font_size = style.text.font_size.to_pixels(window.rem_size());
19537 let line_height = style.text.line_height_in_pixels(window.rem_size());
19538 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19539
19540 gpui::Size::new(em_width, line_height)
19541 }
19542
19543 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19544 self.load_diff_task.clone()
19545 }
19546
19547 fn read_metadata_from_db(
19548 &mut self,
19549 item_id: u64,
19550 workspace_id: WorkspaceId,
19551 window: &mut Window,
19552 cx: &mut Context<Editor>,
19553 ) {
19554 if self.is_singleton(cx)
19555 && !self.mode.is_minimap()
19556 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19557 {
19558 let buffer_snapshot = OnceCell::new();
19559
19560 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19561 if !folds.is_empty() {
19562 let snapshot =
19563 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19564 self.fold_ranges(
19565 folds
19566 .into_iter()
19567 .map(|(start, end)| {
19568 snapshot.clip_offset(start, Bias::Left)
19569 ..snapshot.clip_offset(end, Bias::Right)
19570 })
19571 .collect(),
19572 false,
19573 window,
19574 cx,
19575 );
19576 }
19577 }
19578
19579 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19580 if !selections.is_empty() {
19581 let snapshot =
19582 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19583 self.change_selections(None, window, cx, |s| {
19584 s.select_ranges(selections.into_iter().map(|(start, end)| {
19585 snapshot.clip_offset(start, Bias::Left)
19586 ..snapshot.clip_offset(end, Bias::Right)
19587 }));
19588 });
19589 }
19590 };
19591 }
19592
19593 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19594 }
19595}
19596
19597fn vim_enabled(cx: &App) -> bool {
19598 cx.global::<SettingsStore>()
19599 .raw_user_settings()
19600 .get("vim_mode")
19601 == Some(&serde_json::Value::Bool(true))
19602}
19603
19604fn process_completion_for_edit(
19605 completion: &Completion,
19606 intent: CompletionIntent,
19607 buffer: &Entity<Buffer>,
19608 cursor_position: &text::Anchor,
19609 cx: &mut Context<Editor>,
19610) -> CompletionEdit {
19611 let buffer = buffer.read(cx);
19612 let buffer_snapshot = buffer.snapshot();
19613 let (snippet, new_text) = if completion.is_snippet() {
19614 let mut snippet_source = completion.new_text.clone();
19615 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19616 if scope.prefers_label_for_snippet_in_completion() {
19617 if let Some(label) = completion.label() {
19618 if matches!(
19619 completion.kind(),
19620 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19621 ) {
19622 snippet_source = label;
19623 }
19624 }
19625 }
19626 }
19627 match Snippet::parse(&snippet_source).log_err() {
19628 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19629 None => (None, completion.new_text.clone()),
19630 }
19631 } else {
19632 (None, completion.new_text.clone())
19633 };
19634
19635 let mut range_to_replace = {
19636 let replace_range = &completion.replace_range;
19637 if let CompletionSource::Lsp {
19638 insert_range: Some(insert_range),
19639 ..
19640 } = &completion.source
19641 {
19642 debug_assert_eq!(
19643 insert_range.start, replace_range.start,
19644 "insert_range and replace_range should start at the same position"
19645 );
19646 debug_assert!(
19647 insert_range
19648 .start
19649 .cmp(&cursor_position, &buffer_snapshot)
19650 .is_le(),
19651 "insert_range should start before or at cursor position"
19652 );
19653 debug_assert!(
19654 replace_range
19655 .start
19656 .cmp(&cursor_position, &buffer_snapshot)
19657 .is_le(),
19658 "replace_range should start before or at cursor position"
19659 );
19660 debug_assert!(
19661 insert_range
19662 .end
19663 .cmp(&cursor_position, &buffer_snapshot)
19664 .is_le(),
19665 "insert_range should end before or at cursor position"
19666 );
19667
19668 let should_replace = match intent {
19669 CompletionIntent::CompleteWithInsert => false,
19670 CompletionIntent::CompleteWithReplace => true,
19671 CompletionIntent::Complete | CompletionIntent::Compose => {
19672 let insert_mode =
19673 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19674 .completions
19675 .lsp_insert_mode;
19676 match insert_mode {
19677 LspInsertMode::Insert => false,
19678 LspInsertMode::Replace => true,
19679 LspInsertMode::ReplaceSubsequence => {
19680 let mut text_to_replace = buffer.chars_for_range(
19681 buffer.anchor_before(replace_range.start)
19682 ..buffer.anchor_after(replace_range.end),
19683 );
19684 let mut current_needle = text_to_replace.next();
19685 for haystack_ch in completion.label.text.chars() {
19686 if let Some(needle_ch) = current_needle {
19687 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19688 current_needle = text_to_replace.next();
19689 }
19690 }
19691 }
19692 current_needle.is_none()
19693 }
19694 LspInsertMode::ReplaceSuffix => {
19695 if replace_range
19696 .end
19697 .cmp(&cursor_position, &buffer_snapshot)
19698 .is_gt()
19699 {
19700 let range_after_cursor = *cursor_position..replace_range.end;
19701 let text_after_cursor = buffer
19702 .text_for_range(
19703 buffer.anchor_before(range_after_cursor.start)
19704 ..buffer.anchor_after(range_after_cursor.end),
19705 )
19706 .collect::<String>()
19707 .to_ascii_lowercase();
19708 completion
19709 .label
19710 .text
19711 .to_ascii_lowercase()
19712 .ends_with(&text_after_cursor)
19713 } else {
19714 true
19715 }
19716 }
19717 }
19718 }
19719 };
19720
19721 if should_replace {
19722 replace_range.clone()
19723 } else {
19724 insert_range.clone()
19725 }
19726 } else {
19727 replace_range.clone()
19728 }
19729 };
19730
19731 if range_to_replace
19732 .end
19733 .cmp(&cursor_position, &buffer_snapshot)
19734 .is_lt()
19735 {
19736 range_to_replace.end = *cursor_position;
19737 }
19738
19739 CompletionEdit {
19740 new_text,
19741 replace_range: range_to_replace.to_offset(&buffer),
19742 snippet,
19743 }
19744}
19745
19746struct CompletionEdit {
19747 new_text: String,
19748 replace_range: Range<usize>,
19749 snippet: Option<Snippet>,
19750}
19751
19752fn insert_extra_newline_brackets(
19753 buffer: &MultiBufferSnapshot,
19754 range: Range<usize>,
19755 language: &language::LanguageScope,
19756) -> bool {
19757 let leading_whitespace_len = buffer
19758 .reversed_chars_at(range.start)
19759 .take_while(|c| c.is_whitespace() && *c != '\n')
19760 .map(|c| c.len_utf8())
19761 .sum::<usize>();
19762 let trailing_whitespace_len = buffer
19763 .chars_at(range.end)
19764 .take_while(|c| c.is_whitespace() && *c != '\n')
19765 .map(|c| c.len_utf8())
19766 .sum::<usize>();
19767 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19768
19769 language.brackets().any(|(pair, enabled)| {
19770 let pair_start = pair.start.trim_end();
19771 let pair_end = pair.end.trim_start();
19772
19773 enabled
19774 && pair.newline
19775 && buffer.contains_str_at(range.end, pair_end)
19776 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19777 })
19778}
19779
19780fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19781 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19782 [(buffer, range, _)] => (*buffer, range.clone()),
19783 _ => return false,
19784 };
19785 let pair = {
19786 let mut result: Option<BracketMatch> = None;
19787
19788 for pair in buffer
19789 .all_bracket_ranges(range.clone())
19790 .filter(move |pair| {
19791 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19792 })
19793 {
19794 let len = pair.close_range.end - pair.open_range.start;
19795
19796 if let Some(existing) = &result {
19797 let existing_len = existing.close_range.end - existing.open_range.start;
19798 if len > existing_len {
19799 continue;
19800 }
19801 }
19802
19803 result = Some(pair);
19804 }
19805
19806 result
19807 };
19808 let Some(pair) = pair else {
19809 return false;
19810 };
19811 pair.newline_only
19812 && buffer
19813 .chars_for_range(pair.open_range.end..range.start)
19814 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19815 .all(|c| c.is_whitespace() && c != '\n')
19816}
19817
19818fn update_uncommitted_diff_for_buffer(
19819 editor: Entity<Editor>,
19820 project: &Entity<Project>,
19821 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19822 buffer: Entity<MultiBuffer>,
19823 cx: &mut App,
19824) -> Task<()> {
19825 let mut tasks = Vec::new();
19826 project.update(cx, |project, cx| {
19827 for buffer in buffers {
19828 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19829 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19830 }
19831 }
19832 });
19833 cx.spawn(async move |cx| {
19834 let diffs = future::join_all(tasks).await;
19835 if editor
19836 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19837 .unwrap_or(false)
19838 {
19839 return;
19840 }
19841
19842 buffer
19843 .update(cx, |buffer, cx| {
19844 for diff in diffs.into_iter().flatten() {
19845 buffer.add_diff(diff, cx);
19846 }
19847 })
19848 .ok();
19849 })
19850}
19851
19852fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19853 let tab_size = tab_size.get() as usize;
19854 let mut width = offset;
19855
19856 for ch in text.chars() {
19857 width += if ch == '\t' {
19858 tab_size - (width % tab_size)
19859 } else {
19860 1
19861 };
19862 }
19863
19864 width - offset
19865}
19866
19867#[cfg(test)]
19868mod tests {
19869 use super::*;
19870
19871 #[test]
19872 fn test_string_size_with_expanded_tabs() {
19873 let nz = |val| NonZeroU32::new(val).unwrap();
19874 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19875 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19876 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19877 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19878 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19879 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19880 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19881 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19882 }
19883}
19884
19885/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19886struct WordBreakingTokenizer<'a> {
19887 input: &'a str,
19888}
19889
19890impl<'a> WordBreakingTokenizer<'a> {
19891 fn new(input: &'a str) -> Self {
19892 Self { input }
19893 }
19894}
19895
19896fn is_char_ideographic(ch: char) -> bool {
19897 use unicode_script::Script::*;
19898 use unicode_script::UnicodeScript;
19899 matches!(ch.script(), Han | Tangut | Yi)
19900}
19901
19902fn is_grapheme_ideographic(text: &str) -> bool {
19903 text.chars().any(is_char_ideographic)
19904}
19905
19906fn is_grapheme_whitespace(text: &str) -> bool {
19907 text.chars().any(|x| x.is_whitespace())
19908}
19909
19910fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19911 text.chars().next().map_or(false, |ch| {
19912 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19913 })
19914}
19915
19916#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19917enum WordBreakToken<'a> {
19918 Word { token: &'a str, grapheme_len: usize },
19919 InlineWhitespace { token: &'a str, grapheme_len: usize },
19920 Newline,
19921}
19922
19923impl<'a> Iterator for WordBreakingTokenizer<'a> {
19924 /// Yields a span, the count of graphemes in the token, and whether it was
19925 /// whitespace. Note that it also breaks at word boundaries.
19926 type Item = WordBreakToken<'a>;
19927
19928 fn next(&mut self) -> Option<Self::Item> {
19929 use unicode_segmentation::UnicodeSegmentation;
19930 if self.input.is_empty() {
19931 return None;
19932 }
19933
19934 let mut iter = self.input.graphemes(true).peekable();
19935 let mut offset = 0;
19936 let mut grapheme_len = 0;
19937 if let Some(first_grapheme) = iter.next() {
19938 let is_newline = first_grapheme == "\n";
19939 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19940 offset += first_grapheme.len();
19941 grapheme_len += 1;
19942 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19943 if let Some(grapheme) = iter.peek().copied() {
19944 if should_stay_with_preceding_ideograph(grapheme) {
19945 offset += grapheme.len();
19946 grapheme_len += 1;
19947 }
19948 }
19949 } else {
19950 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19951 let mut next_word_bound = words.peek().copied();
19952 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19953 next_word_bound = words.next();
19954 }
19955 while let Some(grapheme) = iter.peek().copied() {
19956 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19957 break;
19958 };
19959 if is_grapheme_whitespace(grapheme) != is_whitespace
19960 || (grapheme == "\n") != is_newline
19961 {
19962 break;
19963 };
19964 offset += grapheme.len();
19965 grapheme_len += 1;
19966 iter.next();
19967 }
19968 }
19969 let token = &self.input[..offset];
19970 self.input = &self.input[offset..];
19971 if token == "\n" {
19972 Some(WordBreakToken::Newline)
19973 } else if is_whitespace {
19974 Some(WordBreakToken::InlineWhitespace {
19975 token,
19976 grapheme_len,
19977 })
19978 } else {
19979 Some(WordBreakToken::Word {
19980 token,
19981 grapheme_len,
19982 })
19983 }
19984 } else {
19985 None
19986 }
19987 }
19988}
19989
19990#[test]
19991fn test_word_breaking_tokenizer() {
19992 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19993 ("", &[]),
19994 (" ", &[whitespace(" ", 2)]),
19995 ("Ʒ", &[word("Ʒ", 1)]),
19996 ("Ǽ", &[word("Ǽ", 1)]),
19997 ("⋑", &[word("⋑", 1)]),
19998 ("⋑⋑", &[word("⋑⋑", 2)]),
19999 (
20000 "原理,进而",
20001 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20002 ),
20003 (
20004 "hello world",
20005 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20006 ),
20007 (
20008 "hello, world",
20009 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20010 ),
20011 (
20012 " hello world",
20013 &[
20014 whitespace(" ", 2),
20015 word("hello", 5),
20016 whitespace(" ", 1),
20017 word("world", 5),
20018 ],
20019 ),
20020 (
20021 "这是什么 \n 钢笔",
20022 &[
20023 word("这", 1),
20024 word("是", 1),
20025 word("什", 1),
20026 word("么", 1),
20027 whitespace(" ", 1),
20028 newline(),
20029 whitespace(" ", 1),
20030 word("钢", 1),
20031 word("笔", 1),
20032 ],
20033 ),
20034 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20035 ];
20036
20037 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20038 WordBreakToken::Word {
20039 token,
20040 grapheme_len,
20041 }
20042 }
20043
20044 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20045 WordBreakToken::InlineWhitespace {
20046 token,
20047 grapheme_len,
20048 }
20049 }
20050
20051 fn newline() -> WordBreakToken<'static> {
20052 WordBreakToken::Newline
20053 }
20054
20055 for (input, result) in tests {
20056 assert_eq!(
20057 WordBreakingTokenizer::new(input)
20058 .collect::<Vec<_>>()
20059 .as_slice(),
20060 *result,
20061 );
20062 }
20063}
20064
20065fn wrap_with_prefix(
20066 line_prefix: String,
20067 unwrapped_text: String,
20068 wrap_column: usize,
20069 tab_size: NonZeroU32,
20070 preserve_existing_whitespace: bool,
20071) -> String {
20072 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20073 let mut wrapped_text = String::new();
20074 let mut current_line = line_prefix.clone();
20075
20076 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20077 let mut current_line_len = line_prefix_len;
20078 let mut in_whitespace = false;
20079 for token in tokenizer {
20080 let have_preceding_whitespace = in_whitespace;
20081 match token {
20082 WordBreakToken::Word {
20083 token,
20084 grapheme_len,
20085 } => {
20086 in_whitespace = false;
20087 if current_line_len + grapheme_len > wrap_column
20088 && current_line_len != line_prefix_len
20089 {
20090 wrapped_text.push_str(current_line.trim_end());
20091 wrapped_text.push('\n');
20092 current_line.truncate(line_prefix.len());
20093 current_line_len = line_prefix_len;
20094 }
20095 current_line.push_str(token);
20096 current_line_len += grapheme_len;
20097 }
20098 WordBreakToken::InlineWhitespace {
20099 mut token,
20100 mut grapheme_len,
20101 } => {
20102 in_whitespace = true;
20103 if have_preceding_whitespace && !preserve_existing_whitespace {
20104 continue;
20105 }
20106 if !preserve_existing_whitespace {
20107 token = " ";
20108 grapheme_len = 1;
20109 }
20110 if current_line_len + grapheme_len > wrap_column {
20111 wrapped_text.push_str(current_line.trim_end());
20112 wrapped_text.push('\n');
20113 current_line.truncate(line_prefix.len());
20114 current_line_len = line_prefix_len;
20115 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20116 current_line.push_str(token);
20117 current_line_len += grapheme_len;
20118 }
20119 }
20120 WordBreakToken::Newline => {
20121 in_whitespace = true;
20122 if preserve_existing_whitespace {
20123 wrapped_text.push_str(current_line.trim_end());
20124 wrapped_text.push('\n');
20125 current_line.truncate(line_prefix.len());
20126 current_line_len = line_prefix_len;
20127 } else if have_preceding_whitespace {
20128 continue;
20129 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20130 {
20131 wrapped_text.push_str(current_line.trim_end());
20132 wrapped_text.push('\n');
20133 current_line.truncate(line_prefix.len());
20134 current_line_len = line_prefix_len;
20135 } else if current_line_len != line_prefix_len {
20136 current_line.push(' ');
20137 current_line_len += 1;
20138 }
20139 }
20140 }
20141 }
20142
20143 if !current_line.is_empty() {
20144 wrapped_text.push_str(¤t_line);
20145 }
20146 wrapped_text
20147}
20148
20149#[test]
20150fn test_wrap_with_prefix() {
20151 assert_eq!(
20152 wrap_with_prefix(
20153 "# ".to_string(),
20154 "abcdefg".to_string(),
20155 4,
20156 NonZeroU32::new(4).unwrap(),
20157 false,
20158 ),
20159 "# abcdefg"
20160 );
20161 assert_eq!(
20162 wrap_with_prefix(
20163 "".to_string(),
20164 "\thello world".to_string(),
20165 8,
20166 NonZeroU32::new(4).unwrap(),
20167 false,
20168 ),
20169 "hello\nworld"
20170 );
20171 assert_eq!(
20172 wrap_with_prefix(
20173 "// ".to_string(),
20174 "xx \nyy zz aa bb cc".to_string(),
20175 12,
20176 NonZeroU32::new(4).unwrap(),
20177 false,
20178 ),
20179 "// xx yy zz\n// aa bb cc"
20180 );
20181 assert_eq!(
20182 wrap_with_prefix(
20183 String::new(),
20184 "这是什么 \n 钢笔".to_string(),
20185 3,
20186 NonZeroU32::new(4).unwrap(),
20187 false,
20188 ),
20189 "这是什\n么 钢\n笔"
20190 );
20191}
20192
20193pub trait CollaborationHub {
20194 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20195 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20196 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20197}
20198
20199impl CollaborationHub for Entity<Project> {
20200 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20201 self.read(cx).collaborators()
20202 }
20203
20204 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20205 self.read(cx).user_store().read(cx).participant_indices()
20206 }
20207
20208 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20209 let this = self.read(cx);
20210 let user_ids = this.collaborators().values().map(|c| c.user_id);
20211 this.user_store().read(cx).participant_names(user_ids, cx)
20212 }
20213}
20214
20215pub trait SemanticsProvider {
20216 fn hover(
20217 &self,
20218 buffer: &Entity<Buffer>,
20219 position: text::Anchor,
20220 cx: &mut App,
20221 ) -> Option<Task<Vec<project::Hover>>>;
20222
20223 fn inline_values(
20224 &self,
20225 buffer_handle: Entity<Buffer>,
20226 range: Range<text::Anchor>,
20227 cx: &mut App,
20228 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20229
20230 fn inlay_hints(
20231 &self,
20232 buffer_handle: Entity<Buffer>,
20233 range: Range<text::Anchor>,
20234 cx: &mut App,
20235 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20236
20237 fn resolve_inlay_hint(
20238 &self,
20239 hint: InlayHint,
20240 buffer_handle: Entity<Buffer>,
20241 server_id: LanguageServerId,
20242 cx: &mut App,
20243 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20244
20245 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20246
20247 fn document_highlights(
20248 &self,
20249 buffer: &Entity<Buffer>,
20250 position: text::Anchor,
20251 cx: &mut App,
20252 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20253
20254 fn definitions(
20255 &self,
20256 buffer: &Entity<Buffer>,
20257 position: text::Anchor,
20258 kind: GotoDefinitionKind,
20259 cx: &mut App,
20260 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20261
20262 fn range_for_rename(
20263 &self,
20264 buffer: &Entity<Buffer>,
20265 position: text::Anchor,
20266 cx: &mut App,
20267 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20268
20269 fn perform_rename(
20270 &self,
20271 buffer: &Entity<Buffer>,
20272 position: text::Anchor,
20273 new_name: String,
20274 cx: &mut App,
20275 ) -> Option<Task<Result<ProjectTransaction>>>;
20276}
20277
20278pub trait CompletionProvider {
20279 fn completions(
20280 &self,
20281 excerpt_id: ExcerptId,
20282 buffer: &Entity<Buffer>,
20283 buffer_position: text::Anchor,
20284 trigger: CompletionContext,
20285 window: &mut Window,
20286 cx: &mut Context<Editor>,
20287 ) -> Task<Result<Vec<CompletionResponse>>>;
20288
20289 fn resolve_completions(
20290 &self,
20291 buffer: Entity<Buffer>,
20292 completion_indices: Vec<usize>,
20293 completions: Rc<RefCell<Box<[Completion]>>>,
20294 cx: &mut Context<Editor>,
20295 ) -> Task<Result<bool>>;
20296
20297 fn apply_additional_edits_for_completion(
20298 &self,
20299 _buffer: Entity<Buffer>,
20300 _completions: Rc<RefCell<Box<[Completion]>>>,
20301 _completion_index: usize,
20302 _push_to_history: bool,
20303 _cx: &mut Context<Editor>,
20304 ) -> Task<Result<Option<language::Transaction>>> {
20305 Task::ready(Ok(None))
20306 }
20307
20308 fn is_completion_trigger(
20309 &self,
20310 buffer: &Entity<Buffer>,
20311 position: language::Anchor,
20312 text: &str,
20313 trigger_in_words: bool,
20314 menu_is_open: bool,
20315 cx: &mut Context<Editor>,
20316 ) -> bool;
20317
20318 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20319
20320 fn sort_completions(&self) -> bool {
20321 true
20322 }
20323
20324 fn filter_completions(&self) -> bool {
20325 true
20326 }
20327}
20328
20329pub trait CodeActionProvider {
20330 fn id(&self) -> Arc<str>;
20331
20332 fn code_actions(
20333 &self,
20334 buffer: &Entity<Buffer>,
20335 range: Range<text::Anchor>,
20336 window: &mut Window,
20337 cx: &mut App,
20338 ) -> Task<Result<Vec<CodeAction>>>;
20339
20340 fn apply_code_action(
20341 &self,
20342 buffer_handle: Entity<Buffer>,
20343 action: CodeAction,
20344 excerpt_id: ExcerptId,
20345 push_to_history: bool,
20346 window: &mut Window,
20347 cx: &mut App,
20348 ) -> Task<Result<ProjectTransaction>>;
20349}
20350
20351impl CodeActionProvider for Entity<Project> {
20352 fn id(&self) -> Arc<str> {
20353 "project".into()
20354 }
20355
20356 fn code_actions(
20357 &self,
20358 buffer: &Entity<Buffer>,
20359 range: Range<text::Anchor>,
20360 _window: &mut Window,
20361 cx: &mut App,
20362 ) -> Task<Result<Vec<CodeAction>>> {
20363 self.update(cx, |project, cx| {
20364 let code_lens = project.code_lens(buffer, range.clone(), cx);
20365 let code_actions = project.code_actions(buffer, range, None, cx);
20366 cx.background_spawn(async move {
20367 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20368 Ok(code_lens
20369 .context("code lens fetch")?
20370 .into_iter()
20371 .chain(code_actions.context("code action fetch")?)
20372 .collect())
20373 })
20374 })
20375 }
20376
20377 fn apply_code_action(
20378 &self,
20379 buffer_handle: Entity<Buffer>,
20380 action: CodeAction,
20381 _excerpt_id: ExcerptId,
20382 push_to_history: bool,
20383 _window: &mut Window,
20384 cx: &mut App,
20385 ) -> Task<Result<ProjectTransaction>> {
20386 self.update(cx, |project, cx| {
20387 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20388 })
20389 }
20390}
20391
20392fn snippet_completions(
20393 project: &Project,
20394 buffer: &Entity<Buffer>,
20395 buffer_position: text::Anchor,
20396 cx: &mut App,
20397) -> Task<Result<CompletionResponse>> {
20398 let languages = buffer.read(cx).languages_at(buffer_position);
20399 let snippet_store = project.snippets().read(cx);
20400
20401 let scopes: Vec<_> = languages
20402 .iter()
20403 .filter_map(|language| {
20404 let language_name = language.lsp_id();
20405 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20406
20407 if snippets.is_empty() {
20408 None
20409 } else {
20410 Some((language.default_scope(), snippets))
20411 }
20412 })
20413 .collect();
20414
20415 if scopes.is_empty() {
20416 return Task::ready(Ok(CompletionResponse {
20417 completions: vec![],
20418 is_incomplete: false,
20419 }));
20420 }
20421
20422 let snapshot = buffer.read(cx).text_snapshot();
20423 let chars: String = snapshot
20424 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20425 .collect();
20426 let executor = cx.background_executor().clone();
20427
20428 cx.background_spawn(async move {
20429 let mut is_incomplete = false;
20430 let mut completions: Vec<Completion> = Vec::new();
20431 for (scope, snippets) in scopes.into_iter() {
20432 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20433 let mut last_word = chars
20434 .chars()
20435 .take_while(|c| classifier.is_word(*c))
20436 .collect::<String>();
20437 last_word = last_word.chars().rev().collect();
20438
20439 if last_word.is_empty() {
20440 return Ok(CompletionResponse {
20441 completions: vec![],
20442 is_incomplete: true,
20443 });
20444 }
20445
20446 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20447 let to_lsp = |point: &text::Anchor| {
20448 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20449 point_to_lsp(end)
20450 };
20451 let lsp_end = to_lsp(&buffer_position);
20452
20453 let candidates = snippets
20454 .iter()
20455 .enumerate()
20456 .flat_map(|(ix, snippet)| {
20457 snippet
20458 .prefix
20459 .iter()
20460 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20461 })
20462 .collect::<Vec<StringMatchCandidate>>();
20463
20464 const MAX_RESULTS: usize = 100;
20465 let mut matches = fuzzy::match_strings(
20466 &candidates,
20467 &last_word,
20468 last_word.chars().any(|c| c.is_uppercase()),
20469 MAX_RESULTS,
20470 &Default::default(),
20471 executor.clone(),
20472 )
20473 .await;
20474
20475 if matches.len() >= MAX_RESULTS {
20476 is_incomplete = true;
20477 }
20478
20479 // Remove all candidates where the query's start does not match the start of any word in the candidate
20480 if let Some(query_start) = last_word.chars().next() {
20481 matches.retain(|string_match| {
20482 split_words(&string_match.string).any(|word| {
20483 // Check that the first codepoint of the word as lowercase matches the first
20484 // codepoint of the query as lowercase
20485 word.chars()
20486 .flat_map(|codepoint| codepoint.to_lowercase())
20487 .zip(query_start.to_lowercase())
20488 .all(|(word_cp, query_cp)| word_cp == query_cp)
20489 })
20490 });
20491 }
20492
20493 let matched_strings = matches
20494 .into_iter()
20495 .map(|m| m.string)
20496 .collect::<HashSet<_>>();
20497
20498 completions.extend(snippets.iter().filter_map(|snippet| {
20499 let matching_prefix = snippet
20500 .prefix
20501 .iter()
20502 .find(|prefix| matched_strings.contains(*prefix))?;
20503 let start = as_offset - last_word.len();
20504 let start = snapshot.anchor_before(start);
20505 let range = start..buffer_position;
20506 let lsp_start = to_lsp(&start);
20507 let lsp_range = lsp::Range {
20508 start: lsp_start,
20509 end: lsp_end,
20510 };
20511 Some(Completion {
20512 replace_range: range,
20513 new_text: snippet.body.clone(),
20514 source: CompletionSource::Lsp {
20515 insert_range: None,
20516 server_id: LanguageServerId(usize::MAX),
20517 resolved: true,
20518 lsp_completion: Box::new(lsp::CompletionItem {
20519 label: snippet.prefix.first().unwrap().clone(),
20520 kind: Some(CompletionItemKind::SNIPPET),
20521 label_details: snippet.description.as_ref().map(|description| {
20522 lsp::CompletionItemLabelDetails {
20523 detail: Some(description.clone()),
20524 description: None,
20525 }
20526 }),
20527 insert_text_format: Some(InsertTextFormat::SNIPPET),
20528 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20529 lsp::InsertReplaceEdit {
20530 new_text: snippet.body.clone(),
20531 insert: lsp_range,
20532 replace: lsp_range,
20533 },
20534 )),
20535 filter_text: Some(snippet.body.clone()),
20536 sort_text: Some(char::MAX.to_string()),
20537 ..lsp::CompletionItem::default()
20538 }),
20539 lsp_defaults: None,
20540 },
20541 label: CodeLabel {
20542 text: matching_prefix.clone(),
20543 runs: Vec::new(),
20544 filter_range: 0..matching_prefix.len(),
20545 },
20546 icon_path: None,
20547 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20548 single_line: snippet.name.clone().into(),
20549 plain_text: snippet
20550 .description
20551 .clone()
20552 .map(|description| description.into()),
20553 }),
20554 insert_text_mode: None,
20555 confirm: None,
20556 })
20557 }))
20558 }
20559
20560 Ok(CompletionResponse {
20561 completions,
20562 is_incomplete,
20563 })
20564 })
20565}
20566
20567impl CompletionProvider for Entity<Project> {
20568 fn completions(
20569 &self,
20570 _excerpt_id: ExcerptId,
20571 buffer: &Entity<Buffer>,
20572 buffer_position: text::Anchor,
20573 options: CompletionContext,
20574 _window: &mut Window,
20575 cx: &mut Context<Editor>,
20576 ) -> Task<Result<Vec<CompletionResponse>>> {
20577 self.update(cx, |project, cx| {
20578 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20579 let project_completions = project.completions(buffer, buffer_position, options, cx);
20580 cx.background_spawn(async move {
20581 let mut responses = project_completions.await?;
20582 let snippets = snippets.await?;
20583 if !snippets.completions.is_empty() {
20584 responses.push(snippets);
20585 }
20586 Ok(responses)
20587 })
20588 })
20589 }
20590
20591 fn resolve_completions(
20592 &self,
20593 buffer: Entity<Buffer>,
20594 completion_indices: Vec<usize>,
20595 completions: Rc<RefCell<Box<[Completion]>>>,
20596 cx: &mut Context<Editor>,
20597 ) -> Task<Result<bool>> {
20598 self.update(cx, |project, cx| {
20599 project.lsp_store().update(cx, |lsp_store, cx| {
20600 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20601 })
20602 })
20603 }
20604
20605 fn apply_additional_edits_for_completion(
20606 &self,
20607 buffer: Entity<Buffer>,
20608 completions: Rc<RefCell<Box<[Completion]>>>,
20609 completion_index: usize,
20610 push_to_history: bool,
20611 cx: &mut Context<Editor>,
20612 ) -> Task<Result<Option<language::Transaction>>> {
20613 self.update(cx, |project, cx| {
20614 project.lsp_store().update(cx, |lsp_store, cx| {
20615 lsp_store.apply_additional_edits_for_completion(
20616 buffer,
20617 completions,
20618 completion_index,
20619 push_to_history,
20620 cx,
20621 )
20622 })
20623 })
20624 }
20625
20626 fn is_completion_trigger(
20627 &self,
20628 buffer: &Entity<Buffer>,
20629 position: language::Anchor,
20630 text: &str,
20631 trigger_in_words: bool,
20632 menu_is_open: bool,
20633 cx: &mut Context<Editor>,
20634 ) -> bool {
20635 let mut chars = text.chars();
20636 let char = if let Some(char) = chars.next() {
20637 char
20638 } else {
20639 return false;
20640 };
20641 if chars.next().is_some() {
20642 return false;
20643 }
20644
20645 let buffer = buffer.read(cx);
20646 let snapshot = buffer.snapshot();
20647 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
20648 return false;
20649 }
20650 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20651 if trigger_in_words && classifier.is_word(char) {
20652 return true;
20653 }
20654
20655 buffer.completion_triggers().contains(text)
20656 }
20657}
20658
20659impl SemanticsProvider for Entity<Project> {
20660 fn hover(
20661 &self,
20662 buffer: &Entity<Buffer>,
20663 position: text::Anchor,
20664 cx: &mut App,
20665 ) -> Option<Task<Vec<project::Hover>>> {
20666 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20667 }
20668
20669 fn document_highlights(
20670 &self,
20671 buffer: &Entity<Buffer>,
20672 position: text::Anchor,
20673 cx: &mut App,
20674 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20675 Some(self.update(cx, |project, cx| {
20676 project.document_highlights(buffer, position, cx)
20677 }))
20678 }
20679
20680 fn definitions(
20681 &self,
20682 buffer: &Entity<Buffer>,
20683 position: text::Anchor,
20684 kind: GotoDefinitionKind,
20685 cx: &mut App,
20686 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20687 Some(self.update(cx, |project, cx| match kind {
20688 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20689 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20690 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20691 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20692 }))
20693 }
20694
20695 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20696 // TODO: make this work for remote projects
20697 self.update(cx, |project, cx| {
20698 if project
20699 .active_debug_session(cx)
20700 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20701 {
20702 return true;
20703 }
20704
20705 buffer.update(cx, |buffer, cx| {
20706 project.any_language_server_supports_inlay_hints(buffer, cx)
20707 })
20708 })
20709 }
20710
20711 fn inline_values(
20712 &self,
20713 buffer_handle: Entity<Buffer>,
20714
20715 range: Range<text::Anchor>,
20716 cx: &mut App,
20717 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20718 self.update(cx, |project, cx| {
20719 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20720
20721 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20722 })
20723 }
20724
20725 fn inlay_hints(
20726 &self,
20727 buffer_handle: Entity<Buffer>,
20728 range: Range<text::Anchor>,
20729 cx: &mut App,
20730 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20731 Some(self.update(cx, |project, cx| {
20732 project.inlay_hints(buffer_handle, range, cx)
20733 }))
20734 }
20735
20736 fn resolve_inlay_hint(
20737 &self,
20738 hint: InlayHint,
20739 buffer_handle: Entity<Buffer>,
20740 server_id: LanguageServerId,
20741 cx: &mut App,
20742 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20743 Some(self.update(cx, |project, cx| {
20744 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20745 }))
20746 }
20747
20748 fn range_for_rename(
20749 &self,
20750 buffer: &Entity<Buffer>,
20751 position: text::Anchor,
20752 cx: &mut App,
20753 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20754 Some(self.update(cx, |project, cx| {
20755 let buffer = buffer.clone();
20756 let task = project.prepare_rename(buffer.clone(), position, cx);
20757 cx.spawn(async move |_, cx| {
20758 Ok(match task.await? {
20759 PrepareRenameResponse::Success(range) => Some(range),
20760 PrepareRenameResponse::InvalidPosition => None,
20761 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20762 // Fallback on using TreeSitter info to determine identifier range
20763 buffer.read_with(cx, |buffer, _| {
20764 let snapshot = buffer.snapshot();
20765 let (range, kind) = snapshot.surrounding_word(position);
20766 if kind != Some(CharKind::Word) {
20767 return None;
20768 }
20769 Some(
20770 snapshot.anchor_before(range.start)
20771 ..snapshot.anchor_after(range.end),
20772 )
20773 })?
20774 }
20775 })
20776 })
20777 }))
20778 }
20779
20780 fn perform_rename(
20781 &self,
20782 buffer: &Entity<Buffer>,
20783 position: text::Anchor,
20784 new_name: String,
20785 cx: &mut App,
20786 ) -> Option<Task<Result<ProjectTransaction>>> {
20787 Some(self.update(cx, |project, cx| {
20788 project.perform_rename(buffer.clone(), position, new_name, cx)
20789 }))
20790 }
20791}
20792
20793fn inlay_hint_settings(
20794 location: Anchor,
20795 snapshot: &MultiBufferSnapshot,
20796 cx: &mut Context<Editor>,
20797) -> InlayHintSettings {
20798 let file = snapshot.file_at(location);
20799 let language = snapshot.language_at(location).map(|l| l.name());
20800 language_settings(language, file, cx).inlay_hints
20801}
20802
20803fn consume_contiguous_rows(
20804 contiguous_row_selections: &mut Vec<Selection<Point>>,
20805 selection: &Selection<Point>,
20806 display_map: &DisplaySnapshot,
20807 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20808) -> (MultiBufferRow, MultiBufferRow) {
20809 contiguous_row_selections.push(selection.clone());
20810 let start_row = MultiBufferRow(selection.start.row);
20811 let mut end_row = ending_row(selection, display_map);
20812
20813 while let Some(next_selection) = selections.peek() {
20814 if next_selection.start.row <= end_row.0 {
20815 end_row = ending_row(next_selection, display_map);
20816 contiguous_row_selections.push(selections.next().unwrap().clone());
20817 } else {
20818 break;
20819 }
20820 }
20821 (start_row, end_row)
20822}
20823
20824fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20825 if next_selection.end.column > 0 || next_selection.is_empty() {
20826 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20827 } else {
20828 MultiBufferRow(next_selection.end.row)
20829 }
20830}
20831
20832impl EditorSnapshot {
20833 pub fn remote_selections_in_range<'a>(
20834 &'a self,
20835 range: &'a Range<Anchor>,
20836 collaboration_hub: &dyn CollaborationHub,
20837 cx: &'a App,
20838 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20839 let participant_names = collaboration_hub.user_names(cx);
20840 let participant_indices = collaboration_hub.user_participant_indices(cx);
20841 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20842 let collaborators_by_replica_id = collaborators_by_peer_id
20843 .values()
20844 .map(|collaborator| (collaborator.replica_id, collaborator))
20845 .collect::<HashMap<_, _>>();
20846 self.buffer_snapshot
20847 .selections_in_range(range, false)
20848 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20849 if replica_id == AGENT_REPLICA_ID {
20850 Some(RemoteSelection {
20851 replica_id,
20852 selection,
20853 cursor_shape,
20854 line_mode,
20855 collaborator_id: CollaboratorId::Agent,
20856 user_name: Some("Agent".into()),
20857 color: cx.theme().players().agent(),
20858 })
20859 } else {
20860 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20861 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20862 let user_name = participant_names.get(&collaborator.user_id).cloned();
20863 Some(RemoteSelection {
20864 replica_id,
20865 selection,
20866 cursor_shape,
20867 line_mode,
20868 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20869 user_name,
20870 color: if let Some(index) = participant_index {
20871 cx.theme().players().color_for_participant(index.0)
20872 } else {
20873 cx.theme().players().absent()
20874 },
20875 })
20876 }
20877 })
20878 }
20879
20880 pub fn hunks_for_ranges(
20881 &self,
20882 ranges: impl IntoIterator<Item = Range<Point>>,
20883 ) -> Vec<MultiBufferDiffHunk> {
20884 let mut hunks = Vec::new();
20885 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20886 HashMap::default();
20887 for query_range in ranges {
20888 let query_rows =
20889 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20890 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20891 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20892 ) {
20893 // Include deleted hunks that are adjacent to the query range, because
20894 // otherwise they would be missed.
20895 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20896 if hunk.status().is_deleted() {
20897 intersects_range |= hunk.row_range.start == query_rows.end;
20898 intersects_range |= hunk.row_range.end == query_rows.start;
20899 }
20900 if intersects_range {
20901 if !processed_buffer_rows
20902 .entry(hunk.buffer_id)
20903 .or_default()
20904 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20905 {
20906 continue;
20907 }
20908 hunks.push(hunk);
20909 }
20910 }
20911 }
20912
20913 hunks
20914 }
20915
20916 fn display_diff_hunks_for_rows<'a>(
20917 &'a self,
20918 display_rows: Range<DisplayRow>,
20919 folded_buffers: &'a HashSet<BufferId>,
20920 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20921 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20922 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20923
20924 self.buffer_snapshot
20925 .diff_hunks_in_range(buffer_start..buffer_end)
20926 .filter_map(|hunk| {
20927 if folded_buffers.contains(&hunk.buffer_id) {
20928 return None;
20929 }
20930
20931 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20932 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20933
20934 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20935 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20936
20937 let display_hunk = if hunk_display_start.column() != 0 {
20938 DisplayDiffHunk::Folded {
20939 display_row: hunk_display_start.row(),
20940 }
20941 } else {
20942 let mut end_row = hunk_display_end.row();
20943 if hunk_display_end.column() > 0 {
20944 end_row.0 += 1;
20945 }
20946 let is_created_file = hunk.is_created_file();
20947 DisplayDiffHunk::Unfolded {
20948 status: hunk.status(),
20949 diff_base_byte_range: hunk.diff_base_byte_range,
20950 display_row_range: hunk_display_start.row()..end_row,
20951 multi_buffer_range: Anchor::range_in_buffer(
20952 hunk.excerpt_id,
20953 hunk.buffer_id,
20954 hunk.buffer_range,
20955 ),
20956 is_created_file,
20957 }
20958 };
20959
20960 Some(display_hunk)
20961 })
20962 }
20963
20964 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20965 self.display_snapshot.buffer_snapshot.language_at(position)
20966 }
20967
20968 pub fn is_focused(&self) -> bool {
20969 self.is_focused
20970 }
20971
20972 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20973 self.placeholder_text.as_ref()
20974 }
20975
20976 pub fn scroll_position(&self) -> gpui::Point<f32> {
20977 self.scroll_anchor.scroll_position(&self.display_snapshot)
20978 }
20979
20980 fn gutter_dimensions(
20981 &self,
20982 font_id: FontId,
20983 font_size: Pixels,
20984 max_line_number_width: Pixels,
20985 cx: &App,
20986 ) -> Option<GutterDimensions> {
20987 if !self.show_gutter {
20988 return None;
20989 }
20990
20991 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20992 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20993
20994 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20995 matches!(
20996 ProjectSettings::get_global(cx).git.git_gutter,
20997 Some(GitGutterSetting::TrackedFiles)
20998 )
20999 });
21000 let gutter_settings = EditorSettings::get_global(cx).gutter;
21001 let show_line_numbers = self
21002 .show_line_numbers
21003 .unwrap_or(gutter_settings.line_numbers);
21004 let line_gutter_width = if show_line_numbers {
21005 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21006 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21007 max_line_number_width.max(min_width_for_number_on_gutter)
21008 } else {
21009 0.0.into()
21010 };
21011
21012 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21013 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21014
21015 let git_blame_entries_width =
21016 self.git_blame_gutter_max_author_length
21017 .map(|max_author_length| {
21018 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21019 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21020
21021 /// The number of characters to dedicate to gaps and margins.
21022 const SPACING_WIDTH: usize = 4;
21023
21024 let max_char_count = max_author_length.min(renderer.max_author_length())
21025 + ::git::SHORT_SHA_LENGTH
21026 + MAX_RELATIVE_TIMESTAMP.len()
21027 + SPACING_WIDTH;
21028
21029 em_advance * max_char_count
21030 });
21031
21032 let is_singleton = self.buffer_snapshot.is_singleton();
21033
21034 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21035 left_padding += if !is_singleton {
21036 em_width * 4.0
21037 } else if show_runnables || show_breakpoints {
21038 em_width * 3.0
21039 } else if show_git_gutter && show_line_numbers {
21040 em_width * 2.0
21041 } else if show_git_gutter || show_line_numbers {
21042 em_width
21043 } else {
21044 px(0.)
21045 };
21046
21047 let shows_folds = is_singleton && gutter_settings.folds;
21048
21049 let right_padding = if shows_folds && show_line_numbers {
21050 em_width * 4.0
21051 } else if shows_folds || (!is_singleton && show_line_numbers) {
21052 em_width * 3.0
21053 } else if show_line_numbers {
21054 em_width
21055 } else {
21056 px(0.)
21057 };
21058
21059 Some(GutterDimensions {
21060 left_padding,
21061 right_padding,
21062 width: line_gutter_width + left_padding + right_padding,
21063 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21064 git_blame_entries_width,
21065 })
21066 }
21067
21068 pub fn render_crease_toggle(
21069 &self,
21070 buffer_row: MultiBufferRow,
21071 row_contains_cursor: bool,
21072 editor: Entity<Editor>,
21073 window: &mut Window,
21074 cx: &mut App,
21075 ) -> Option<AnyElement> {
21076 let folded = self.is_line_folded(buffer_row);
21077 let mut is_foldable = false;
21078
21079 if let Some(crease) = self
21080 .crease_snapshot
21081 .query_row(buffer_row, &self.buffer_snapshot)
21082 {
21083 is_foldable = true;
21084 match crease {
21085 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21086 if let Some(render_toggle) = render_toggle {
21087 let toggle_callback =
21088 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21089 if folded {
21090 editor.update(cx, |editor, cx| {
21091 editor.fold_at(buffer_row, window, cx)
21092 });
21093 } else {
21094 editor.update(cx, |editor, cx| {
21095 editor.unfold_at(buffer_row, window, cx)
21096 });
21097 }
21098 });
21099 return Some((render_toggle)(
21100 buffer_row,
21101 folded,
21102 toggle_callback,
21103 window,
21104 cx,
21105 ));
21106 }
21107 }
21108 }
21109 }
21110
21111 is_foldable |= self.starts_indent(buffer_row);
21112
21113 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21114 Some(
21115 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21116 .toggle_state(folded)
21117 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21118 if folded {
21119 this.unfold_at(buffer_row, window, cx);
21120 } else {
21121 this.fold_at(buffer_row, window, cx);
21122 }
21123 }))
21124 .into_any_element(),
21125 )
21126 } else {
21127 None
21128 }
21129 }
21130
21131 pub fn render_crease_trailer(
21132 &self,
21133 buffer_row: MultiBufferRow,
21134 window: &mut Window,
21135 cx: &mut App,
21136 ) -> Option<AnyElement> {
21137 let folded = self.is_line_folded(buffer_row);
21138 if let Crease::Inline { render_trailer, .. } = self
21139 .crease_snapshot
21140 .query_row(buffer_row, &self.buffer_snapshot)?
21141 {
21142 let render_trailer = render_trailer.as_ref()?;
21143 Some(render_trailer(buffer_row, folded, window, cx))
21144 } else {
21145 None
21146 }
21147 }
21148}
21149
21150impl Deref for EditorSnapshot {
21151 type Target = DisplaySnapshot;
21152
21153 fn deref(&self) -> &Self::Target {
21154 &self.display_snapshot
21155 }
21156}
21157
21158#[derive(Clone, Debug, PartialEq, Eq)]
21159pub enum EditorEvent {
21160 InputIgnored {
21161 text: Arc<str>,
21162 },
21163 InputHandled {
21164 utf16_range_to_replace: Option<Range<isize>>,
21165 text: Arc<str>,
21166 },
21167 ExcerptsAdded {
21168 buffer: Entity<Buffer>,
21169 predecessor: ExcerptId,
21170 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21171 },
21172 ExcerptsRemoved {
21173 ids: Vec<ExcerptId>,
21174 removed_buffer_ids: Vec<BufferId>,
21175 },
21176 BufferFoldToggled {
21177 ids: Vec<ExcerptId>,
21178 folded: bool,
21179 },
21180 ExcerptsEdited {
21181 ids: Vec<ExcerptId>,
21182 },
21183 ExcerptsExpanded {
21184 ids: Vec<ExcerptId>,
21185 },
21186 BufferEdited,
21187 Edited {
21188 transaction_id: clock::Lamport,
21189 },
21190 Reparsed(BufferId),
21191 Focused,
21192 FocusedIn,
21193 Blurred,
21194 DirtyChanged,
21195 Saved,
21196 TitleChanged,
21197 DiffBaseChanged,
21198 SelectionsChanged {
21199 local: bool,
21200 },
21201 ScrollPositionChanged {
21202 local: bool,
21203 autoscroll: bool,
21204 },
21205 Closed,
21206 TransactionUndone {
21207 transaction_id: clock::Lamport,
21208 },
21209 TransactionBegun {
21210 transaction_id: clock::Lamport,
21211 },
21212 Reloaded,
21213 CursorShapeChanged,
21214 PushedToNavHistory {
21215 anchor: Anchor,
21216 is_deactivate: bool,
21217 },
21218}
21219
21220impl EventEmitter<EditorEvent> for Editor {}
21221
21222impl Focusable for Editor {
21223 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21224 self.focus_handle.clone()
21225 }
21226}
21227
21228impl Render for Editor {
21229 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21230 let settings = ThemeSettings::get_global(cx);
21231
21232 let mut text_style = match self.mode {
21233 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21234 color: cx.theme().colors().editor_foreground,
21235 font_family: settings.ui_font.family.clone(),
21236 font_features: settings.ui_font.features.clone(),
21237 font_fallbacks: settings.ui_font.fallbacks.clone(),
21238 font_size: rems(0.875).into(),
21239 font_weight: settings.ui_font.weight,
21240 line_height: relative(settings.buffer_line_height.value()),
21241 ..Default::default()
21242 },
21243 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21244 color: cx.theme().colors().editor_foreground,
21245 font_family: settings.buffer_font.family.clone(),
21246 font_features: settings.buffer_font.features.clone(),
21247 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21248 font_size: settings.buffer_font_size(cx).into(),
21249 font_weight: settings.buffer_font.weight,
21250 line_height: relative(settings.buffer_line_height.value()),
21251 ..Default::default()
21252 },
21253 };
21254 if let Some(text_style_refinement) = &self.text_style_refinement {
21255 text_style.refine(text_style_refinement)
21256 }
21257
21258 let background = match self.mode {
21259 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21260 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21261 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21262 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21263 };
21264
21265 EditorElement::new(
21266 &cx.entity(),
21267 EditorStyle {
21268 background,
21269 local_player: cx.theme().players().local(),
21270 text: text_style,
21271 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21272 syntax: cx.theme().syntax().clone(),
21273 status: cx.theme().status().clone(),
21274 inlay_hints_style: make_inlay_hints_style(cx),
21275 inline_completion_styles: make_suggestion_styles(cx),
21276 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21277 show_underlines: !self.mode.is_minimap(),
21278 },
21279 )
21280 }
21281}
21282
21283impl EntityInputHandler for Editor {
21284 fn text_for_range(
21285 &mut self,
21286 range_utf16: Range<usize>,
21287 adjusted_range: &mut Option<Range<usize>>,
21288 _: &mut Window,
21289 cx: &mut Context<Self>,
21290 ) -> Option<String> {
21291 let snapshot = self.buffer.read(cx).read(cx);
21292 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21293 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21294 if (start.0..end.0) != range_utf16 {
21295 adjusted_range.replace(start.0..end.0);
21296 }
21297 Some(snapshot.text_for_range(start..end).collect())
21298 }
21299
21300 fn selected_text_range(
21301 &mut self,
21302 ignore_disabled_input: bool,
21303 _: &mut Window,
21304 cx: &mut Context<Self>,
21305 ) -> Option<UTF16Selection> {
21306 // Prevent the IME menu from appearing when holding down an alphabetic key
21307 // while input is disabled.
21308 if !ignore_disabled_input && !self.input_enabled {
21309 return None;
21310 }
21311
21312 let selection = self.selections.newest::<OffsetUtf16>(cx);
21313 let range = selection.range();
21314
21315 Some(UTF16Selection {
21316 range: range.start.0..range.end.0,
21317 reversed: selection.reversed,
21318 })
21319 }
21320
21321 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21322 let snapshot = self.buffer.read(cx).read(cx);
21323 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21324 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21325 }
21326
21327 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21328 self.clear_highlights::<InputComposition>(cx);
21329 self.ime_transaction.take();
21330 }
21331
21332 fn replace_text_in_range(
21333 &mut self,
21334 range_utf16: Option<Range<usize>>,
21335 text: &str,
21336 window: &mut Window,
21337 cx: &mut Context<Self>,
21338 ) {
21339 if !self.input_enabled {
21340 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21341 return;
21342 }
21343
21344 self.transact(window, cx, |this, window, cx| {
21345 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21346 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21347 Some(this.selection_replacement_ranges(range_utf16, cx))
21348 } else {
21349 this.marked_text_ranges(cx)
21350 };
21351
21352 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21353 let newest_selection_id = this.selections.newest_anchor().id;
21354 this.selections
21355 .all::<OffsetUtf16>(cx)
21356 .iter()
21357 .zip(ranges_to_replace.iter())
21358 .find_map(|(selection, range)| {
21359 if selection.id == newest_selection_id {
21360 Some(
21361 (range.start.0 as isize - selection.head().0 as isize)
21362 ..(range.end.0 as isize - selection.head().0 as isize),
21363 )
21364 } else {
21365 None
21366 }
21367 })
21368 });
21369
21370 cx.emit(EditorEvent::InputHandled {
21371 utf16_range_to_replace: range_to_replace,
21372 text: text.into(),
21373 });
21374
21375 if let Some(new_selected_ranges) = new_selected_ranges {
21376 this.change_selections(None, window, cx, |selections| {
21377 selections.select_ranges(new_selected_ranges)
21378 });
21379 this.backspace(&Default::default(), window, cx);
21380 }
21381
21382 this.handle_input(text, window, cx);
21383 });
21384
21385 if let Some(transaction) = self.ime_transaction {
21386 self.buffer.update(cx, |buffer, cx| {
21387 buffer.group_until_transaction(transaction, cx);
21388 });
21389 }
21390
21391 self.unmark_text(window, cx);
21392 }
21393
21394 fn replace_and_mark_text_in_range(
21395 &mut self,
21396 range_utf16: Option<Range<usize>>,
21397 text: &str,
21398 new_selected_range_utf16: Option<Range<usize>>,
21399 window: &mut Window,
21400 cx: &mut Context<Self>,
21401 ) {
21402 if !self.input_enabled {
21403 return;
21404 }
21405
21406 let transaction = self.transact(window, cx, |this, window, cx| {
21407 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21408 let snapshot = this.buffer.read(cx).read(cx);
21409 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21410 for marked_range in &mut marked_ranges {
21411 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21412 marked_range.start.0 += relative_range_utf16.start;
21413 marked_range.start =
21414 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21415 marked_range.end =
21416 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21417 }
21418 }
21419 Some(marked_ranges)
21420 } else if let Some(range_utf16) = range_utf16 {
21421 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21422 Some(this.selection_replacement_ranges(range_utf16, cx))
21423 } else {
21424 None
21425 };
21426
21427 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21428 let newest_selection_id = this.selections.newest_anchor().id;
21429 this.selections
21430 .all::<OffsetUtf16>(cx)
21431 .iter()
21432 .zip(ranges_to_replace.iter())
21433 .find_map(|(selection, range)| {
21434 if selection.id == newest_selection_id {
21435 Some(
21436 (range.start.0 as isize - selection.head().0 as isize)
21437 ..(range.end.0 as isize - selection.head().0 as isize),
21438 )
21439 } else {
21440 None
21441 }
21442 })
21443 });
21444
21445 cx.emit(EditorEvent::InputHandled {
21446 utf16_range_to_replace: range_to_replace,
21447 text: text.into(),
21448 });
21449
21450 if let Some(ranges) = ranges_to_replace {
21451 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21452 }
21453
21454 let marked_ranges = {
21455 let snapshot = this.buffer.read(cx).read(cx);
21456 this.selections
21457 .disjoint_anchors()
21458 .iter()
21459 .map(|selection| {
21460 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21461 })
21462 .collect::<Vec<_>>()
21463 };
21464
21465 if text.is_empty() {
21466 this.unmark_text(window, cx);
21467 } else {
21468 this.highlight_text::<InputComposition>(
21469 marked_ranges.clone(),
21470 HighlightStyle {
21471 underline: Some(UnderlineStyle {
21472 thickness: px(1.),
21473 color: None,
21474 wavy: false,
21475 }),
21476 ..Default::default()
21477 },
21478 cx,
21479 );
21480 }
21481
21482 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21483 let use_autoclose = this.use_autoclose;
21484 let use_auto_surround = this.use_auto_surround;
21485 this.set_use_autoclose(false);
21486 this.set_use_auto_surround(false);
21487 this.handle_input(text, window, cx);
21488 this.set_use_autoclose(use_autoclose);
21489 this.set_use_auto_surround(use_auto_surround);
21490
21491 if let Some(new_selected_range) = new_selected_range_utf16 {
21492 let snapshot = this.buffer.read(cx).read(cx);
21493 let new_selected_ranges = marked_ranges
21494 .into_iter()
21495 .map(|marked_range| {
21496 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21497 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21498 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21499 snapshot.clip_offset_utf16(new_start, Bias::Left)
21500 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21501 })
21502 .collect::<Vec<_>>();
21503
21504 drop(snapshot);
21505 this.change_selections(None, window, cx, |selections| {
21506 selections.select_ranges(new_selected_ranges)
21507 });
21508 }
21509 });
21510
21511 self.ime_transaction = self.ime_transaction.or(transaction);
21512 if let Some(transaction) = self.ime_transaction {
21513 self.buffer.update(cx, |buffer, cx| {
21514 buffer.group_until_transaction(transaction, cx);
21515 });
21516 }
21517
21518 if self.text_highlights::<InputComposition>(cx).is_none() {
21519 self.ime_transaction.take();
21520 }
21521 }
21522
21523 fn bounds_for_range(
21524 &mut self,
21525 range_utf16: Range<usize>,
21526 element_bounds: gpui::Bounds<Pixels>,
21527 window: &mut Window,
21528 cx: &mut Context<Self>,
21529 ) -> Option<gpui::Bounds<Pixels>> {
21530 let text_layout_details = self.text_layout_details(window);
21531 let gpui::Size {
21532 width: em_width,
21533 height: line_height,
21534 } = self.character_size(window);
21535
21536 let snapshot = self.snapshot(window, cx);
21537 let scroll_position = snapshot.scroll_position();
21538 let scroll_left = scroll_position.x * em_width;
21539
21540 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21541 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21542 + self.gutter_dimensions.width
21543 + self.gutter_dimensions.margin;
21544 let y = line_height * (start.row().as_f32() - scroll_position.y);
21545
21546 Some(Bounds {
21547 origin: element_bounds.origin + point(x, y),
21548 size: size(em_width, line_height),
21549 })
21550 }
21551
21552 fn character_index_for_point(
21553 &mut self,
21554 point: gpui::Point<Pixels>,
21555 _window: &mut Window,
21556 _cx: &mut Context<Self>,
21557 ) -> Option<usize> {
21558 let position_map = self.last_position_map.as_ref()?;
21559 if !position_map.text_hitbox.contains(&point) {
21560 return None;
21561 }
21562 let display_point = position_map.point_for_position(point).previous_valid;
21563 let anchor = position_map
21564 .snapshot
21565 .display_point_to_anchor(display_point, Bias::Left);
21566 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21567 Some(utf16_offset.0)
21568 }
21569}
21570
21571trait SelectionExt {
21572 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21573 fn spanned_rows(
21574 &self,
21575 include_end_if_at_line_start: bool,
21576 map: &DisplaySnapshot,
21577 ) -> Range<MultiBufferRow>;
21578}
21579
21580impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21581 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21582 let start = self
21583 .start
21584 .to_point(&map.buffer_snapshot)
21585 .to_display_point(map);
21586 let end = self
21587 .end
21588 .to_point(&map.buffer_snapshot)
21589 .to_display_point(map);
21590 if self.reversed {
21591 end..start
21592 } else {
21593 start..end
21594 }
21595 }
21596
21597 fn spanned_rows(
21598 &self,
21599 include_end_if_at_line_start: bool,
21600 map: &DisplaySnapshot,
21601 ) -> Range<MultiBufferRow> {
21602 let start = self.start.to_point(&map.buffer_snapshot);
21603 let mut end = self.end.to_point(&map.buffer_snapshot);
21604 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21605 end.row -= 1;
21606 }
21607
21608 let buffer_start = map.prev_line_boundary(start).0;
21609 let buffer_end = map.next_line_boundary(end).0;
21610 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21611 }
21612}
21613
21614impl<T: InvalidationRegion> InvalidationStack<T> {
21615 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21616 where
21617 S: Clone + ToOffset,
21618 {
21619 while let Some(region) = self.last() {
21620 let all_selections_inside_invalidation_ranges =
21621 if selections.len() == region.ranges().len() {
21622 selections
21623 .iter()
21624 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21625 .all(|(selection, invalidation_range)| {
21626 let head = selection.head().to_offset(buffer);
21627 invalidation_range.start <= head && invalidation_range.end >= head
21628 })
21629 } else {
21630 false
21631 };
21632
21633 if all_selections_inside_invalidation_ranges {
21634 break;
21635 } else {
21636 self.pop();
21637 }
21638 }
21639 }
21640}
21641
21642impl<T> Default for InvalidationStack<T> {
21643 fn default() -> Self {
21644 Self(Default::default())
21645 }
21646}
21647
21648impl<T> Deref for InvalidationStack<T> {
21649 type Target = Vec<T>;
21650
21651 fn deref(&self) -> &Self::Target {
21652 &self.0
21653 }
21654}
21655
21656impl<T> DerefMut for InvalidationStack<T> {
21657 fn deref_mut(&mut self) -> &mut Self::Target {
21658 &mut self.0
21659 }
21660}
21661
21662impl InvalidationRegion for SnippetState {
21663 fn ranges(&self) -> &[Range<Anchor>] {
21664 &self.ranges[self.active_index]
21665 }
21666}
21667
21668fn inline_completion_edit_text(
21669 current_snapshot: &BufferSnapshot,
21670 edits: &[(Range<Anchor>, String)],
21671 edit_preview: &EditPreview,
21672 include_deletions: bool,
21673 cx: &App,
21674) -> HighlightedText {
21675 let edits = edits
21676 .iter()
21677 .map(|(anchor, text)| {
21678 (
21679 anchor.start.text_anchor..anchor.end.text_anchor,
21680 text.clone(),
21681 )
21682 })
21683 .collect::<Vec<_>>();
21684
21685 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21686}
21687
21688pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21689 match severity {
21690 lsp::DiagnosticSeverity::ERROR => colors.error,
21691 lsp::DiagnosticSeverity::WARNING => colors.warning,
21692 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21693 lsp::DiagnosticSeverity::HINT => colors.info,
21694 _ => colors.ignored,
21695 }
21696}
21697
21698pub fn styled_runs_for_code_label<'a>(
21699 label: &'a CodeLabel,
21700 syntax_theme: &'a theme::SyntaxTheme,
21701) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21702 let fade_out = HighlightStyle {
21703 fade_out: Some(0.35),
21704 ..Default::default()
21705 };
21706
21707 let mut prev_end = label.filter_range.end;
21708 label
21709 .runs
21710 .iter()
21711 .enumerate()
21712 .flat_map(move |(ix, (range, highlight_id))| {
21713 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21714 style
21715 } else {
21716 return Default::default();
21717 };
21718 let mut muted_style = style;
21719 muted_style.highlight(fade_out);
21720
21721 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21722 if range.start >= label.filter_range.end {
21723 if range.start > prev_end {
21724 runs.push((prev_end..range.start, fade_out));
21725 }
21726 runs.push((range.clone(), muted_style));
21727 } else if range.end <= label.filter_range.end {
21728 runs.push((range.clone(), style));
21729 } else {
21730 runs.push((range.start..label.filter_range.end, style));
21731 runs.push((label.filter_range.end..range.end, muted_style));
21732 }
21733 prev_end = cmp::max(prev_end, range.end);
21734
21735 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21736 runs.push((prev_end..label.text.len(), fade_out));
21737 }
21738
21739 runs
21740 })
21741}
21742
21743pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21744 let mut prev_index = 0;
21745 let mut prev_codepoint: Option<char> = None;
21746 text.char_indices()
21747 .chain([(text.len(), '\0')])
21748 .filter_map(move |(index, codepoint)| {
21749 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21750 let is_boundary = index == text.len()
21751 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21752 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21753 if is_boundary {
21754 let chunk = &text[prev_index..index];
21755 prev_index = index;
21756 Some(chunk)
21757 } else {
21758 None
21759 }
21760 })
21761}
21762
21763pub trait RangeToAnchorExt: Sized {
21764 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21765
21766 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21767 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21768 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21769 }
21770}
21771
21772impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21773 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21774 let start_offset = self.start.to_offset(snapshot);
21775 let end_offset = self.end.to_offset(snapshot);
21776 if start_offset == end_offset {
21777 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21778 } else {
21779 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21780 }
21781 }
21782}
21783
21784pub trait RowExt {
21785 fn as_f32(&self) -> f32;
21786
21787 fn next_row(&self) -> Self;
21788
21789 fn previous_row(&self) -> Self;
21790
21791 fn minus(&self, other: Self) -> u32;
21792}
21793
21794impl RowExt for DisplayRow {
21795 fn as_f32(&self) -> f32 {
21796 self.0 as f32
21797 }
21798
21799 fn next_row(&self) -> Self {
21800 Self(self.0 + 1)
21801 }
21802
21803 fn previous_row(&self) -> Self {
21804 Self(self.0.saturating_sub(1))
21805 }
21806
21807 fn minus(&self, other: Self) -> u32 {
21808 self.0 - other.0
21809 }
21810}
21811
21812impl RowExt for MultiBufferRow {
21813 fn as_f32(&self) -> f32 {
21814 self.0 as f32
21815 }
21816
21817 fn next_row(&self) -> Self {
21818 Self(self.0 + 1)
21819 }
21820
21821 fn previous_row(&self) -> Self {
21822 Self(self.0.saturating_sub(1))
21823 }
21824
21825 fn minus(&self, other: Self) -> u32 {
21826 self.0 - other.0
21827 }
21828}
21829
21830trait RowRangeExt {
21831 type Row;
21832
21833 fn len(&self) -> usize;
21834
21835 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21836}
21837
21838impl RowRangeExt for Range<MultiBufferRow> {
21839 type Row = MultiBufferRow;
21840
21841 fn len(&self) -> usize {
21842 (self.end.0 - self.start.0) as usize
21843 }
21844
21845 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21846 (self.start.0..self.end.0).map(MultiBufferRow)
21847 }
21848}
21849
21850impl RowRangeExt for Range<DisplayRow> {
21851 type Row = DisplayRow;
21852
21853 fn len(&self) -> usize {
21854 (self.end.0 - self.start.0) as usize
21855 }
21856
21857 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21858 (self.start.0..self.end.0).map(DisplayRow)
21859 }
21860}
21861
21862/// If select range has more than one line, we
21863/// just point the cursor to range.start.
21864fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21865 if range.start.row == range.end.row {
21866 range
21867 } else {
21868 range.start..range.start
21869 }
21870}
21871pub struct KillRing(ClipboardItem);
21872impl Global for KillRing {}
21873
21874const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21875
21876enum BreakpointPromptEditAction {
21877 Log,
21878 Condition,
21879 HitCondition,
21880}
21881
21882struct BreakpointPromptEditor {
21883 pub(crate) prompt: Entity<Editor>,
21884 editor: WeakEntity<Editor>,
21885 breakpoint_anchor: Anchor,
21886 breakpoint: Breakpoint,
21887 edit_action: BreakpointPromptEditAction,
21888 block_ids: HashSet<CustomBlockId>,
21889 editor_margins: Arc<Mutex<EditorMargins>>,
21890 _subscriptions: Vec<Subscription>,
21891}
21892
21893impl BreakpointPromptEditor {
21894 const MAX_LINES: u8 = 4;
21895
21896 fn new(
21897 editor: WeakEntity<Editor>,
21898 breakpoint_anchor: Anchor,
21899 breakpoint: Breakpoint,
21900 edit_action: BreakpointPromptEditAction,
21901 window: &mut Window,
21902 cx: &mut Context<Self>,
21903 ) -> Self {
21904 let base_text = match edit_action {
21905 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21906 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21907 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21908 }
21909 .map(|msg| msg.to_string())
21910 .unwrap_or_default();
21911
21912 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21913 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21914
21915 let prompt = cx.new(|cx| {
21916 let mut prompt = Editor::new(
21917 EditorMode::AutoHeight {
21918 max_lines: Self::MAX_LINES as usize,
21919 },
21920 buffer,
21921 None,
21922 window,
21923 cx,
21924 );
21925 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21926 prompt.set_show_cursor_when_unfocused(false, cx);
21927 prompt.set_placeholder_text(
21928 match edit_action {
21929 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21930 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21931 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21932 },
21933 cx,
21934 );
21935
21936 prompt
21937 });
21938
21939 Self {
21940 prompt,
21941 editor,
21942 breakpoint_anchor,
21943 breakpoint,
21944 edit_action,
21945 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21946 block_ids: Default::default(),
21947 _subscriptions: vec![],
21948 }
21949 }
21950
21951 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21952 self.block_ids.extend(block_ids)
21953 }
21954
21955 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21956 if let Some(editor) = self.editor.upgrade() {
21957 let message = self
21958 .prompt
21959 .read(cx)
21960 .buffer
21961 .read(cx)
21962 .as_singleton()
21963 .expect("A multi buffer in breakpoint prompt isn't possible")
21964 .read(cx)
21965 .as_rope()
21966 .to_string();
21967
21968 editor.update(cx, |editor, cx| {
21969 editor.edit_breakpoint_at_anchor(
21970 self.breakpoint_anchor,
21971 self.breakpoint.clone(),
21972 match self.edit_action {
21973 BreakpointPromptEditAction::Log => {
21974 BreakpointEditAction::EditLogMessage(message.into())
21975 }
21976 BreakpointPromptEditAction::Condition => {
21977 BreakpointEditAction::EditCondition(message.into())
21978 }
21979 BreakpointPromptEditAction::HitCondition => {
21980 BreakpointEditAction::EditHitCondition(message.into())
21981 }
21982 },
21983 cx,
21984 );
21985
21986 editor.remove_blocks(self.block_ids.clone(), None, cx);
21987 cx.focus_self(window);
21988 });
21989 }
21990 }
21991
21992 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21993 self.editor
21994 .update(cx, |editor, cx| {
21995 editor.remove_blocks(self.block_ids.clone(), None, cx);
21996 window.focus(&editor.focus_handle);
21997 })
21998 .log_err();
21999 }
22000
22001 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22002 let settings = ThemeSettings::get_global(cx);
22003 let text_style = TextStyle {
22004 color: if self.prompt.read(cx).read_only(cx) {
22005 cx.theme().colors().text_disabled
22006 } else {
22007 cx.theme().colors().text
22008 },
22009 font_family: settings.buffer_font.family.clone(),
22010 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22011 font_size: settings.buffer_font_size(cx).into(),
22012 font_weight: settings.buffer_font.weight,
22013 line_height: relative(settings.buffer_line_height.value()),
22014 ..Default::default()
22015 };
22016 EditorElement::new(
22017 &self.prompt,
22018 EditorStyle {
22019 background: cx.theme().colors().editor_background,
22020 local_player: cx.theme().players().local(),
22021 text: text_style,
22022 ..Default::default()
22023 },
22024 )
22025 }
22026}
22027
22028impl Render for BreakpointPromptEditor {
22029 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22030 let editor_margins = *self.editor_margins.lock();
22031 let gutter_dimensions = editor_margins.gutter;
22032 h_flex()
22033 .key_context("Editor")
22034 .bg(cx.theme().colors().editor_background)
22035 .border_y_1()
22036 .border_color(cx.theme().status().info_border)
22037 .size_full()
22038 .py(window.line_height() / 2.5)
22039 .on_action(cx.listener(Self::confirm))
22040 .on_action(cx.listener(Self::cancel))
22041 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22042 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22043 }
22044}
22045
22046impl Focusable for BreakpointPromptEditor {
22047 fn focus_handle(&self, cx: &App) -> FocusHandle {
22048 self.prompt.focus_handle(cx)
22049 }
22050}
22051
22052fn all_edits_insertions_or_deletions(
22053 edits: &Vec<(Range<Anchor>, String)>,
22054 snapshot: &MultiBufferSnapshot,
22055) -> bool {
22056 let mut all_insertions = true;
22057 let mut all_deletions = true;
22058
22059 for (range, new_text) in edits.iter() {
22060 let range_is_empty = range.to_offset(&snapshot).is_empty();
22061 let text_is_empty = new_text.is_empty();
22062
22063 if range_is_empty != text_is_empty {
22064 if range_is_empty {
22065 all_deletions = false;
22066 } else {
22067 all_insertions = false;
22068 }
22069 } else {
22070 return false;
22071 }
22072
22073 if !all_insertions && !all_deletions {
22074 return false;
22075 }
22076 }
22077 all_insertions || all_deletions
22078}
22079
22080struct MissingEditPredictionKeybindingTooltip;
22081
22082impl Render for MissingEditPredictionKeybindingTooltip {
22083 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22084 ui::tooltip_container(window, cx, |container, _, cx| {
22085 container
22086 .flex_shrink_0()
22087 .max_w_80()
22088 .min_h(rems_from_px(124.))
22089 .justify_between()
22090 .child(
22091 v_flex()
22092 .flex_1()
22093 .text_ui_sm(cx)
22094 .child(Label::new("Conflict with Accept Keybinding"))
22095 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22096 )
22097 .child(
22098 h_flex()
22099 .pb_1()
22100 .gap_1()
22101 .items_end()
22102 .w_full()
22103 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22104 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22105 }))
22106 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22107 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22108 })),
22109 )
22110 })
22111 }
22112}
22113
22114#[derive(Debug, Clone, Copy, PartialEq)]
22115pub struct LineHighlight {
22116 pub background: Background,
22117 pub border: Option<gpui::Hsla>,
22118 pub include_gutter: bool,
22119 pub type_id: Option<TypeId>,
22120}
22121
22122fn render_diff_hunk_controls(
22123 row: u32,
22124 status: &DiffHunkStatus,
22125 hunk_range: Range<Anchor>,
22126 is_created_file: bool,
22127 line_height: Pixels,
22128 editor: &Entity<Editor>,
22129 _window: &mut Window,
22130 cx: &mut App,
22131) -> AnyElement {
22132 h_flex()
22133 .h(line_height)
22134 .mr_1()
22135 .gap_1()
22136 .px_0p5()
22137 .pb_1()
22138 .border_x_1()
22139 .border_b_1()
22140 .border_color(cx.theme().colors().border_variant)
22141 .rounded_b_lg()
22142 .bg(cx.theme().colors().editor_background)
22143 .gap_1()
22144 .block_mouse_except_scroll()
22145 .shadow_md()
22146 .child(if status.has_secondary_hunk() {
22147 Button::new(("stage", row as u64), "Stage")
22148 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22149 .tooltip({
22150 let focus_handle = editor.focus_handle(cx);
22151 move |window, cx| {
22152 Tooltip::for_action_in(
22153 "Stage Hunk",
22154 &::git::ToggleStaged,
22155 &focus_handle,
22156 window,
22157 cx,
22158 )
22159 }
22160 })
22161 .on_click({
22162 let editor = editor.clone();
22163 move |_event, _window, cx| {
22164 editor.update(cx, |editor, cx| {
22165 editor.stage_or_unstage_diff_hunks(
22166 true,
22167 vec![hunk_range.start..hunk_range.start],
22168 cx,
22169 );
22170 });
22171 }
22172 })
22173 } else {
22174 Button::new(("unstage", row as u64), "Unstage")
22175 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22176 .tooltip({
22177 let focus_handle = editor.focus_handle(cx);
22178 move |window, cx| {
22179 Tooltip::for_action_in(
22180 "Unstage Hunk",
22181 &::git::ToggleStaged,
22182 &focus_handle,
22183 window,
22184 cx,
22185 )
22186 }
22187 })
22188 .on_click({
22189 let editor = editor.clone();
22190 move |_event, _window, cx| {
22191 editor.update(cx, |editor, cx| {
22192 editor.stage_or_unstage_diff_hunks(
22193 false,
22194 vec![hunk_range.start..hunk_range.start],
22195 cx,
22196 );
22197 });
22198 }
22199 })
22200 })
22201 .child(
22202 Button::new(("restore", row as u64), "Restore")
22203 .tooltip({
22204 let focus_handle = editor.focus_handle(cx);
22205 move |window, cx| {
22206 Tooltip::for_action_in(
22207 "Restore Hunk",
22208 &::git::Restore,
22209 &focus_handle,
22210 window,
22211 cx,
22212 )
22213 }
22214 })
22215 .on_click({
22216 let editor = editor.clone();
22217 move |_event, window, cx| {
22218 editor.update(cx, |editor, cx| {
22219 let snapshot = editor.snapshot(window, cx);
22220 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22221 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22222 });
22223 }
22224 })
22225 .disabled(is_created_file),
22226 )
22227 .when(
22228 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22229 |el| {
22230 el.child(
22231 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22232 .shape(IconButtonShape::Square)
22233 .icon_size(IconSize::Small)
22234 // .disabled(!has_multiple_hunks)
22235 .tooltip({
22236 let focus_handle = editor.focus_handle(cx);
22237 move |window, cx| {
22238 Tooltip::for_action_in(
22239 "Next Hunk",
22240 &GoToHunk,
22241 &focus_handle,
22242 window,
22243 cx,
22244 )
22245 }
22246 })
22247 .on_click({
22248 let editor = editor.clone();
22249 move |_event, window, cx| {
22250 editor.update(cx, |editor, cx| {
22251 let snapshot = editor.snapshot(window, cx);
22252 let position =
22253 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22254 editor.go_to_hunk_before_or_after_position(
22255 &snapshot,
22256 position,
22257 Direction::Next,
22258 window,
22259 cx,
22260 );
22261 editor.expand_selected_diff_hunks(cx);
22262 });
22263 }
22264 }),
22265 )
22266 .child(
22267 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22268 .shape(IconButtonShape::Square)
22269 .icon_size(IconSize::Small)
22270 // .disabled(!has_multiple_hunks)
22271 .tooltip({
22272 let focus_handle = editor.focus_handle(cx);
22273 move |window, cx| {
22274 Tooltip::for_action_in(
22275 "Previous Hunk",
22276 &GoToPreviousHunk,
22277 &focus_handle,
22278 window,
22279 cx,
22280 )
22281 }
22282 })
22283 .on_click({
22284 let editor = editor.clone();
22285 move |_event, window, cx| {
22286 editor.update(cx, |editor, cx| {
22287 let snapshot = editor.snapshot(window, cx);
22288 let point =
22289 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22290 editor.go_to_hunk_before_or_after_position(
22291 &snapshot,
22292 point,
22293 Direction::Prev,
22294 window,
22295 cx,
22296 );
22297 editor.expand_selected_diff_hunks(cx);
22298 });
22299 }
22300 }),
22301 )
22302 },
22303 )
22304 .into_any_element()
22305}